using UnityEngine; namespace BaseGames.Player.States { /// /// 下落状态。 /// - 郊狼跳:CoyoteTimer > 0 时按跳跃 → 一段跳(JumpState,使用 JumpForce)。 /// - 空中跳跃:CoyoteTimer 耗尽后按跳跃且 AirJumpsLeft > 0 → JumpState(使用 DoubleJumpForce)。 /// - 下冲刺:HasAbility(DownDash) && 下方向 + 冲刺键 → DownDashState(优先于普通冲刺)。 /// - 冲刺:HasAbility(Dash) && DashState.CanDashMidAir → DashState(地面与空中统一,空中限一次)。 /// - 抓墙:贴墙时按下朝向墙壁的方向键 → WallSlideState。 /// - 增强下落重力(FallGravityMult)确保下落快于上升,手感紧实。 /// public class FallState : PlayerStateBase { public FallState(PlayerController owner) : base(owner) { } public override void OnStateEnter() { if (AnimCfg?.Fall != null) Anim.Play(AnimCfg.Fall); } public override void OnStateUpdate() { // ── 墙壁土狼跳(优先于地面土狼跳 / 二段跳)────────────────────────── // 离墙后 WallCoyoteTime 秒内按跳跃,视为有效蹬墙跳(背墙跳 / 对墙跳) if (Move.HasWallCoyoteTime && Buffer.ConsumeJump()) { var wjs = Owner.GetState(); if (wjs != null) { Move.ConsumeWallCoyote(); wjs.PrepareEnter(Move.WallCoyoteDir, Input.MoveInput.x); Owner.TransitionTo(wjs); return; } } // ── 跳跃输入(郊狼跳 / 二段跳)──────────────────────────────────── // 先确认有可用跳跃机会,再消耗缓冲,避免无操作时静默吃掉输入 if ((Move.HasCoyoteTime || Owner.AirJumpsLeft > 0) && Buffer.ConsumeJump()) { if (Move.HasCoyoteTime) { // 郊狼跳:一段跳,使用 JumpForce _owner.TransitionTo(_owner.GetState()); return; } // 二段跳:通过 SetDoubleJump 标记 JumpState 使用 DoubleJumpForce Owner.UseAirJump(); Owner.GetState()?.SetDoubleJump(true); _owner.TransitionTo(_owner.GetState()); return; } // ── 下冲刺(下 + 冲刺 → 向下冲刺,优先于普通冲刺)────────────────────── // 按住下方向 + 冲刺键,且已解锁 DownDash 能力、空中冲刺次数未耗尽 var dashState = Owner.GetState(); if (dashState != null && dashState.CanDashMidAir && Input.MoveInput.y < -0.5f && Stats != null && Stats.HasAbility(AbilityType.DownDash) && Buffer.ConsumeDash()) { _owner.TransitionTo(_owner.GetState()); return; } // ── 冲刺(地面/空中统一使用 DashState)──────────────────────────── // 先确认能力与冷却均满足,再消耗缓冲,避免无操作时静默吃掉输入 if (dashState != null && dashState.CanDashMidAir && Stats != null && Stats.HasAbility(AbilityType.Dash) && Buffer.ConsumeDash()) { _owner.TransitionTo(dashState); return; } // ── 空中攻击:Move Y > 0 → 上劈;Y < -0.5 且解锁下劈 → 下劈;其余 → 空中攻击 ── if (Buffer.ConsumeAttack()) { if (Input.MoveInput.y > 0.5f) _owner.TransitionTo(_owner.GetState()); else if (Input.MoveInput.y < -0.5f && Stats != null && Stats.HasAbility(AbilityType.DownSlash)) _owner.TransitionTo(_owner.GetState()); else _owner.TransitionTo(_owner.GetState()); return; } // ── 着地回退(主逻辑已移至 OnStateFixedUpdate,此处仅作极端情况保险)──────────── // 正常情况下状态在 FixedUpdate 中已转换,Update 执行时 _currentState 已是 IdleState/RunState, // 此段不会被执行。仅在初始帧等 FixedUpdate 尚未运行时作补充保障。 if (Move.IsGrounded) { Move.ZeroVelocity(); if (Mathf.Abs(Input.MoveInput.x) > 0.1f) _owner.TransitionTo(_owner.GetState()); else _owner.TransitionTo(_owner.GetState()); return; } // ── 抓墙:贴墙 + 朝向墙壁按键,或蹬墙跳后的自动抓墙────────────── var wd = Owner.WallDetector; if (wd != null && wd.IsTouchingWall) { int wallDir = wd.WallDirection; bool pressingTowardWall = Mathf.Abs(Input.MoveInput.x) > 0.01f && (int)Mathf.Sign(Input.MoveInput.x) == wallDir; if ((pressingTowardWall || Owner.IsPostWallJump) && Stats != null && Stats.HasAbility(AbilityType.WallCling)) { var wss = _owner.GetState(); if (wss != null) { wss.PrepareEnter(wallDir); _owner.TransitionTo(wss); } return; } } } public override void OnStateFixedUpdate() { // ── 着地检测(在 FixedUpdate 内与 CheckGrounded 同帧执行)──────────────────── // PlayerMovement.FixedUpdate(-200) 先于 PlayerController.FixedUpdate(-100) 执行, // 此处读到的 IsGrounded 已是本物理帧最新结果,不存在跨帧读到过期值的问题。 // 落地检测放在 OnStateUpdate(Update 阶段)时,若低帧率导致同帧内多次 FixedUpdate, // 最后一次 FixedUpdate 的 depenetration 弹回可能使 _isGrounded 在 Update 时为 false, // 进而导致无法着地。 if (Move.IsGrounded) { Move.ZeroVelocity(); if (Mathf.Abs(Input.MoveInput.x) > 0.1f) _owner.TransitionTo(_owner.GetState()); else _owner.TransitionTo(_owner.GetState()); return; } // 空中水平移动:有输入时立即覆盖至目标速度;无输入时水平速度立即归零。 // 朝向墙壁时停止施力,防止物理摩擦使角色在空中贴墙悬停。 if (Mathf.Abs(Input.MoveInput.x) > 0.01f) { int inputDir = Input.MoveInput.x > 0 ? 1 : -1; var wd = Owner.WallDetector; if (wd != null && wd.IsTouchingWall && wd.WallDirection == inputDir) { // 按下朝墙方向键 + 在墙上(且未著地)→ 进入抓墙状态 var wss = Owner.GetState(); if (wss != null && !Move.IsGrounded) { wss.PrepareEnter(inputDir); Owner.TransitionTo(wss); return; } Move.ZeroHorizontalVelocity(); } else if (wd != null && wd.HasPartialContact(inputDir)) { // 仅部分射线命中(如检测点高于矮墙顶部),停止施加朝墙方向的水平速度, // 防止角色边角被卡在墙顶而无法继续下落。 Move.ZeroHorizontalVelocity(); } else Move.Move(Input.MoveInput.x * Cfg.RunSpeed); } else Move.ZeroHorizontalVelocity(); // 增强下落重力(FallGravityMult:下落比上升更快,手感更紧实) // 着地时已由上方提前 return,此处无需额外判断 IsGrounded, // 避免持续下压速度与 depenetration 形成振荡导致 IsGrounded 持续为 false if (Move.Rb.velocity.y < 0f) { float extraGrav = Physics2D.gravity.y * (Cfg.FallGravityMult - 1f) * Time.fixedDeltaTime; Move.Rb.velocity = new Vector2( Move.Rb.velocity.x, Mathf.Max(Move.Rb.velocity.y + extraGrav, -Cfg.MaxFallSpeed)); } } } }