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));
}
}
}
}