using BaseGames.Combat; namespace BaseGames.Player.States { /// /// 地面攻击状态(3 段连击)。 /// 由 PlayerController 实例化,AttackEvent 触发切换。 /// 通过 Animancer 帧事件驱动 HitBox 激活/关闭。 /// public class AttackState : PlayerStateBase { private int _comboIndex; public AttackState(PlayerController owner) : base(owner) { } public override void OnStateEnter() { _comboIndex = 0; PlayAttackClip(); Input.AttackEvent += OnAttackInput; } public override void OnStateExit() { Input.AttackEvent -= OnAttackInput; Owner.Combat?.DisableAllWeaponHitBoxes(); } public override void OnStateUpdate() { } public override void OnStateFixedUpdate() { } // ── 内部 ────────────────────────────────────────────────────────────── private void PlayAttackClip() { // ⚠️ 字段名 GroundAttacks(非 AttackChainClips) var clip = AnimCfg.GroundAttacks[_comboIndex]; var state = Anim.Play(clip); var events = state.Events(this); events.OnEnd = OnClipEnd; // HitBox 由 Animancer 归一化时间事件驱动(时间点配置于 PlayerAnimationConfigSO) var timings = AnimCfg?.GroundAttackTimings; float enterTime = timings != null && _comboIndex < timings.Length ? timings[_comboIndex].HitBoxEnter : 0.3f; float exitTime = timings != null && _comboIndex < timings.Length ? timings[_comboIndex].HitBoxExit : 0.6f; events.Add(enterTime, () => Owner.Combat?.EnableWeaponHitBox(AttackDirection.Ground)); events.Add(exitTime, () => Owner.Combat?.DisableAllWeaponHitBoxes()); } private void OnClipEnd() { Owner.Combat?.DisableAllWeaponHitBoxes(); Owner.TransitionTo(Owner.GetState()); } private void OnAttackInput() { // 连击段数从配置 SO 动态读取,无须修改代码即可支持任意段数连击 int maxCombo = AnimCfg?.GroundAttacks?.Length ?? 1; if (_comboIndex < maxCombo - 1) { _comboIndex++; PlayAttackClip(); } } } }