Files
zeling_v2/Assets/_Game/Scripts/Enemies/Abilities/ContactChaseAbility.cs
T
joywayerandClaude Opus 4.8 6f519ec9a9 refactor(enemy): 身体接触伤害改为常驻开启并与能力解耦
所有敌人默认常驻身体接触伤害:存活即开、死亡时经 PerformDeath 统一关碰撞体停止。
- ContactChaseAbility/AnimatedCeilingDropAbility 移除对 BodyContactDamage 的开关引用
- BodyContactDamage 语义改为常驻(更新注释)
- 脚手架 SetupHurtAndContactBoxes 去掉 contactEnabled 参数恒为 true,并把
  ContactDamageZone 的 HitBox._targetLayers 限定为 PlayerHurtBox(避免常驻后敌人互相误伤)
- 7 个敌人预制体:E001 启用 BodyContactDamage;E002/E004/E005/ChaoFeng 补齐
  ContactDamageZone(镜像 HurtBox 身体碰撞体);全部 _targetLayers 收敛为 PlayerHurtBox
- 更新 Docs/Guides/02 敌人搭建指南

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

114 lines
4.8 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 步态不抢动画)。
/// 身体接触伤害与本能力解耦:由常驻的 <see cref="BodyContactDamage"/> 全程负责,本能力不再开关它。
/// </summary>
public class ContactChaseAbility : EnemyAbilityBase
{
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,直到撞墙/到边缘或玩家消失即停。
// (身体接触伤害由常驻 BodyContactDamage 全程负责,此处不再开关。)
if (_cfg.loopClip.Clip != null)
_animancer.Play(_cfg.loopClip);
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 施加冷却。
yield return PlaySkillEnd();
Cleanup();
}
protected override void OnInterrupted(InterruptReason reason)
{
// 丢失目标 = 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 Cleanup()
{
_enemy.SetEngaged(false);
_enemy.Locomotion?.Stop();
}
}
}