Files
zeling_v2/Assets/_Game/Scripts/Enemies/Behaviors/EnemyDeathSequence.cs
T
joywayerandClaude Opus 5 822dc90c21 refactor(enemy): 清行为树债——StopBehaviorTree 改名,删 ConsumeParryEvent
StopBehaviorTree → NotifyDecisionStop(方法体早已只是发 Died 信号)。
EnemyDeathSequence 的 _stopBehaviorTree 字段同步改名,带 FormerlySerializedAs 保值。
ConsumeParryEvent 及其 TTL 状态删除:零调用者,且 ForceState(Stagger) +
IsControllable 让位门已是更优表达,不需要第二条受击通道。

顺带清掉这三个文件里残留的行为树插件措辞(BD_* / BD Task / 停行为树)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 16:23:47 +08:00

88 lines
3.3 KiB
C#

using System;
using System.Collections;
using Animancer;
using BaseGames.Combat;
using UnityEngine;
namespace BaseGames.Enemies.Behaviors
{
/// <summary>
/// 死亡前摇无敌演出(零代码替代每敌人专属的 <c>Die()</c> 重写)。
/// <para>
/// <see cref="EnemyBase.Die"/> 会委托本组件:通知决策层终止 / 停移动、停用受击框,播放前摇动画并等待,
/// 期间敌人处于无敌(<see cref="EnemyBase.IsInvincible"/>),演出结束后回调真正的死亡清理。
/// 前摇动画上可放置 SpawnProjectile 动画事件,配合 <see cref="EnemySpawnerOnEvent"/> 在演出中生成小怪。
/// </para>
/// </summary>
[DisallowMultipleComponent]
public class EnemyDeathSequence : MonoBehaviour, IEnemyDeathSequence
{
[Header("前摇动画")]
[Tooltip("死亡前摇动画(无敌演出);为空则跳过演出直接进入死亡清理")]
[SerializeField] private ClipTransition _deathPreClip;
[Tooltip("前摇演出时长(秒)")]
[Min(0f)][SerializeField] private float _duration = 3f;
[Header("演出期间")]
[Tooltip("演出期间停用的受击框(防止演出中被打断或二次受伤);对象池复用时 OnSpawn 自动恢复")]
[SerializeField] private HurtBox[] _hurtBoxesToDisable;
[UnityEngine.Serialization.FormerlySerializedAs("_stopBehaviorTree")]
[Tooltip("死亡演出开始时通知决策层终止,防止 AI 继续覆盖演出")]
[SerializeField] private bool _stopDecisionLayer = true;
[Tooltip("演出开始时停止移动")]
[SerializeField] private bool _stopMovement = true;
private EnemyBase _enemy;
private AnimancerComponent _animancer;
private void Awake()
{
_enemy = GetComponentInParent<EnemyBase>();
_animancer = _enemy != null ? _enemy.Animancer : GetComponentInParent<AnimancerComponent>();
if (_enemy == null)
Debug.LogError($"[EnemyDeathSequence] {name} 找不到 EnemyBase。", this);
}
// 对象池复用:出生时恢复受击框(演出中曾被停用)
private void OnEnable()
{
if (_enemy != null) _enemy.Spawned += RestoreHurtBoxes;
}
private void OnDisable()
{
if (_enemy != null) _enemy.Spawned -= RestoreHurtBoxes;
}
public void Play(Action onComplete)
{
StartCoroutine(Sequence(onComplete));
}
private IEnumerator Sequence(Action onComplete)
{
if (_stopDecisionLayer) _enemy?.NotifyDecisionStop();
if (_stopMovement) _enemy?.StopMovement();
SetHurtBoxesEnabled(false);
if (_deathPreClip.Clip != null && _animancer != null)
{
_animancer.Play(_deathPreClip);
if (_duration > 0f) yield return new WaitForSeconds(_duration);
}
onComplete?.Invoke();
}
private void RestoreHurtBoxes() => SetHurtBoxesEnabled(true);
private void SetHurtBoxesEnabled(bool enabled)
{
if (_hurtBoxesToDisable == null) return;
foreach (var hb in _hurtBoxesToDisable)
if (hb != null) hb.enabled = enabled;
}
}
}