Files
zeling_v2/Assets/_Game/Scripts/Enemies/Abilities/ContactChaseAbility.cs
T

125 lines
5.0 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Collections;
using Animancer;
using UnityEngine;
namespace BaseGames.Enemies.Abilities
{
/// <summary>
/// 冲刺追击 + 体接触伤害能力:起手(Skill_Start 张口) → 朝玩家方向直冲(Skill_Loop 快速爬行 +
/// 接触伤害),**直到撞墙/到平台边缘或玩家消失即停** → 收招(Skill_End)。一次冲刺为一次能力执行,
/// 结束后进入冷却(EnemyAbilitySO.cooldown)AI 追击态在冷却结束时经 EnsureAbility 重触发下一次冲刺。
/// 冲刺方向在起手后按玩家所在侧一次性确定(committed 冲锋,会越过跳开的玩家,可被躲)。
/// 追击动画由本能力全权驱动(Approach 步态不抢动画)。
/// </summary>
public class ContactChaseAbility : EnemyAbilityBase
{
[Header("接触伤害")]
[SerializeField] private BodyContactDamage _contactDamage;
private ContactChaseAbilitySO _cfg;
protected override void Awake()
{
base.Awake();
_cfg = ResolveConfig<ContactChaseAbilitySO>();
}
protected override IEnumerator ExecuteCoroutine()
{
if (_cfg == null) yield break;
Phase = AbilityRunState.Active;
_enemy.SetEngaged(true);
// 冲刺方向:按玩家所在侧一次性确定(committed 冲锋)
float dir = (_enemy.PlayerTransform != null &&
_enemy.PlayerTransform.position.x < _enemy.transform.position.x) ? -1f : 1f;
var mv = _enemy.Movement;
// 先转身朝玩家(背对时播 Flip 转身动画),转身完成后再张口
if (mv != null)
{
mv.FaceDirection((int)dir);
while (mv.IsTurning)
yield return null;
}
// 起手:张口(一次性)。期间静止、不伤人;锁住动画防被步态覆盖。
if (_cfg.startClip.Clip != null)
{
_enemy.LockAnim(_cfg.startClip.Clip.length);
_animancer.Play(_cfg.startClip);
yield return EnemyAbilityWaits.Get(_cfg.startClip.Clip.length);
}
// 冲刺速度:优先能力配置的 _cfg.dashSpeed,否则回退 Stats.RunSpeed
float speed = _cfg.dashSpeed > 0f ? _cfg.dashSpeed
: (_enemy.Stats != null ? _enemy.Stats.RunSpeed : 0f);
// 冲刺:朝该方向直冲、接触伤害开、播 Skill_Loop,直到撞墙/到边缘或玩家消失即停。
if (_cfg.loopClip.Clip != null)
_animancer.Play(_cfg.loopClip);
if (_contactDamage != null)
_contactDamage.enabled = true;
float winX = _enemy.transform.position.x;
float winT = 0f;
while (_enemy.PlayerTransform != null && !(mv != null && mv.WouldBlockAhead(dir)))
{
_enemy.MoveInDirectionWithSpeed(dir, speed); // 速度直冲(非 nav)MoveWithSpeed 悬崖夹紧兜底
yield return null;
// 兜底:撞上未在检测层的碰撞体(玩家/边界墙)导致原地不动 → 结束本次冲刺(转收招+冷却)。
// 用窗口净位移判定,避免"贴住微抖"每帧重置。
winT += Time.deltaTime;
if (winT >= 0.15f)
{
if (Mathf.Abs(_enemy.transform.position.x - winX) < 0.05f) break;
winX = _enemy.transform.position.x;
winT = 0f;
}
}
_enemy.StopMovement();
// 收招 Skill_End;随后基类按 EnemyAbilitySO.cooldown 施加冷却。
DisableContact();
yield return PlaySkillEnd();
Cleanup();
}
protected override void OnInterrupted(InterruptReason reason)
{
DisableContact();
// 丢失目标 = AI 主动退出追击态(ExternalRequest) → 播收招 Skill_End
// 受击/死亡/硬直等硬打断不播收招。协程已停,fire-and-forget;动画锁保证 Skill_End
// 在其时长内不被巡逻步态盖掉。
if (reason == InterruptReason.ExternalRequest && _cfg != null && _cfg.endClip.Clip != null)
{
_enemy.LockAnim(_cfg.endClip.Clip.length);
_animancer.Play(_cfg.endClip);
}
Cleanup();
}
private IEnumerator PlaySkillEnd()
{
if (_cfg == null || _cfg.endClip.Clip == null) yield break;
_enemy.LockAnim(_cfg.endClip.Clip.length);
_animancer.Play(_cfg.endClip);
yield return EnemyAbilityWaits.Get(_cfg.endClip.Clip.length);
}
private void DisableContact()
{
if (_contactDamage != null)
_contactDamage.enabled = false;
}
private void Cleanup()
{
_enemy.SetEngaged(false);
_enemy.Locomotion?.Stop();
}
}
}