Files
zeling_v2/Assets/_Game/Scripts/Enemies/Abilities/ContactChaseAbility.cs
T
joywayer 9eca69a6ba @
feat(enemy): E001 追击改为冲刺+冷却(朝玩家方向冲,撞墙/边缘停,CD后再冲)

按新设计:进入追击→Skill_Start(张口)→起手后按玩家所在侧一次性确定方向→
速度直冲(非 nav,接触伤害开)→撞墙/到平台边缘(WouldBlockAhead)或受阻即停→
Skill_End(收招)→进入冷却(ABL_E001_Chase.cooldown=1s)。一次冲刺=一次能力执行,
AI 追击态在冷却结束时经 EnsureAbility 重触发下一次冲刺。

- ContactChaseAbility:无限跟随改为有限冲刺;方向 committed(会越过跳开的玩家,
  可被躲);加窗口净位移卡死兜底——撞上未在检测层的碰撞体(玩家/边界墙)原地不动
  时结束冲刺转收招+冷却,避免卡死。
- ABL_E001_Chase.cooldown 0→1(可在 SO 调)。

PlayMode 验证(净域重载后):Skill_Start→冲刺(实测冲 7 单位、伤害开)→受阻→
Skill_End→冷却→冷却结束重新起手冲刺,完整循环。速度直冲不依赖 nav,敌人实际
位移正常。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@
2026-07-10 16:46:37 +08:00

109 lines
4.6 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 ClipTransition _startClip; // Skill_Start:张口(一次性)
[SerializeField] private ClipTransition _loopClip; // Skill_Loop:快速爬行追击(循环)
[SerializeField] private ClipTransition _endClip; // Skill_End:收招(一次性)
[Header("接触伤害")]
[SerializeField] private BodyContactDamage _contactDamage;
protected override IEnumerator ExecuteCoroutine()
{
Phase = AbilityRunState.Active;
_enemy.SetEngaged(true);
// 起手:张口(一次性)。期间静止、不伤人;锁住动画防被步态覆盖。
if (_startClip.Clip != null)
{
_enemy.LockAnim(_startClip.Clip.length);
_animancer.Play(_startClip);
yield return EnemyAbilityWaits.Get(_startClip.Clip.length);
}
// 冲刺方向:起手后按玩家所在侧一次性确定(committed 冲锋)
float dir = (_enemy.PlayerTransform != null &&
_enemy.PlayerTransform.position.x < _enemy.transform.position.x) ? -1f : 1f;
float speed = _enemy.Stats != null ? _enemy.Stats.RunSpeed : 0f;
var mv = _enemy.Movement;
// 冲刺:朝该方向直冲、接触伤害开、播 Skill_Loop,直到撞墙/到边缘或玩家消失即停。
if (_loopClip.Clip != null)
_animancer.Play(_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 && _endClip.Clip != null)
{
_enemy.LockAnim(_endClip.Clip.length);
_animancer.Play(_endClip);
}
Cleanup();
}
private IEnumerator PlaySkillEnd()
{
if (_endClip.Clip == null) yield break;
_enemy.LockAnim(_endClip.Clip.length);
_animancer.Play(_endClip);
yield return EnemyAbilityWaits.Get(_endClip.Clip.length);
}
private void DisableContact()
{
if (_contactDamage != null)
_contactDamage.enabled = false;
}
private void Cleanup()
{
_enemy.SetEngaged(false);
_enemy.Locomotion?.Stop();
}
}
}