using UnityEngine; using System.Collections; namespace BaseGames.Player.States { /// /// 冲刺状态(架构 05_PlayerModule §2 + §12)。 /// 施加水平位移 + 无敌帧;冲刺期间重力归零,结束后恢复; /// 地面与空中统一使用此状态,空中每次离地限冲刺一次(_dashChargeUsed); /// 需解锁 AbilityType.Dash 才能进入(PlayerController 负责条件检查)。 /// public class DashState : PlayerStateBase { private float _timer; private float _cooldownTimer; /// /// 无敌的独立冷却计时器。由 TickCooldown 每帧减少。 /// private float _invincibilityCooldownTimer; private int _facingDir; /// 本次离地后是否已消耗过冲刺次数。落地或下劈命中(Pogo)时重置。 private bool _dashChargeUsed; public bool CanDash => _cooldownTimer <= 0f; /// 空中冲刺可用条件:冷却就绪 且 本次离地内尚未冲刺过。 public bool CanDashMidAir => _cooldownTimer <= 0f && !_dashChargeUsed; /// 重置冲刺次数(落地或 Pogo 时由 IdleState/RunState/DownAttackState 调用)。 public void ResetDashCharge() => _dashChargeUsed = false; /// 消耗空中冲刺次数(DownDashState 进入时调用,与普通空中冲刺共享次数上限)。 public void ConsumeAirDashCharge() => _dashChargeUsed = true; /// /// 无敌帧是否已冷却,即本次冲刺可以获得无敌。 /// public bool CanGrantInvincibility => _invincibilityCooldownTimer <= 0f; /// 重置无敌冷却(冲刺使用无敌后调用)。 public void ResetInvincibilityCooldown(float cd) => _invincibilityCooldownTimer = cd; // ── IsInvincible 不再在状态层硬编码,改为全面依赖 Stats._invincibleTimer ──────────── // 冲刺无敌窗口 = BeginInvincibility(DashInvincibilityDuration) 设定的时间限 Stats._invincibleTimer // PlayerController.TakeDamage 已改为将 Stats.IsInvincible 纳入硬直判断,居间无敌窗口自然失效。 // 不再需要 override IsInvincible。 public DashState(PlayerController owner) : base(owner) { } public override void OnStateEnter() { Debug.Assert(Cfg != null, "[DashState] MovementConfig 未配置,请在 PlayerController Inspector 中绑定 MovementConfig SO。", _owner); _facingDir = Owner.FacingDirection; _timer = Cfg.DashDuration; // 空中冲刺:记录本次离地已使用冲刺(地面冲刺不消耗,仅空中限制一次) if (!Move.IsGrounded) _dashChargeUsed = true; // 无敌帧: // 条件 1:已解锁 InvincibleDash // 条件 2:无敌冷却已就绪(防止 spam 冲刺连序无敌) // 窗口时长 = DashInvincibilityDuration < DashDuration,冲刺后段无保护 // 在设置冷却计时器前捕获,供后续动画选择使用 bool isInvincibleDash = Stats != null && Stats.HasAbility(AbilityType.InvincibleDash) && CanGrantInvincibility; if (isInvincibleDash) { Stats.BeginInvincibility(Cfg.DashInvincibilityDuration); _invincibilityCooldownTimer = Cfg.DashInvincibilityCooldown; } // 关闭重力,施加冲刺速度 Move?.SetGravityScale(0f); Move?.Dash(new Vector2(_facingDir, 0f), Cfg.DashSpeed); // 播放冲刺动画:无敌冲刺使用专属 Clip,留空时回退到普通冲刺 Clip var dashClip = (isInvincibleDash && AnimCfg?.DashInvincible != null) ? AnimCfg.DashInvincible : AnimCfg?.Dash; if (dashClip != null) Anim?.Play(dashClip); Feedback.TriggerPreset("dash"); } public override void OnStateUpdate() { _timer -= Time.deltaTime; if (_timer <= 0f) { EndDash(); return; } // 跳跃可取消冲刺:冲刺期间按跳跃立即中断并起跳。 // 空中冲刺时若有剩余空中跳跃次数,消耗一次并使用二段跳力度。 if (Buffer.ConsumeJump()) { if (!Move.IsGrounded && Owner.AirJumpsLeft > 0) { Owner.UseAirJump(); Owner.GetState()?.SetDoubleJump(true); } Owner.TransitionTo(Owner.GetState()); return; } // 注:碰墙时不中止冲刺,完成完整冲刺时长(物理阻止位移,但计时继续) } public override void OnStateExit() { // 恢复默认重力 Move?.SetGravityScale(Cfg.DefaultGravityScale); _cooldownTimer = Cfg.DashCooldown; } public override void OnStateFixedUpdate() { // 冲刺期间保持冲刺速度(防止摩擦力减速) if (_timer > 0f) Move?.Dash(new Vector2(_facingDir, 0f), Cfg.DashSpeed); } private void EndDash() { // 双轴速度归零,防止冲刺结束时角色带着 DashSpeed 冲出平台边缘后继续向前飞行。 Move?.ZeroVelocity(); if (Move != null && Move.IsGrounded) Owner.TransitionTo(Owner.GetState()); else Owner.TransitionTo(Owner.GetState()); } /// 每帧减少冒小帮冷却(由 PlayerController.Update 调用)。 public void TickCooldown(float dt) { if (_cooldownTimer > 0f) _cooldownTimer -= dt; if (_invincibilityCooldownTimer > 0f) _invincibilityCooldownTimer -= dt; } } }