# Conflicts: # Assets/_Game/Scripts/Player/PlayerMovement.cs # Assets/_Game/Scripts/Player/States/IdleState.cs # Assets/_Game/Scripts/Player/States/RunState.cs # Assets/_Game/Scripts/Player/States/WallJumpState.cs # Assets/_Game/Scripts/Player/States/WallSlideState.cs
156 lines
7.5 KiB
C#
156 lines
7.5 KiB
C#
using UnityEngine;
|
||
|
||
namespace BaseGames.Player.States
|
||
{
|
||
/// <summary>
|
||
/// 下落状态。
|
||
/// - 郊狼跳:CoyoteTimer > 0 时按跳跃 → 一段跳(JumpState,使用 JumpForce)。
|
||
/// - 空中跳跃:CoyoteTimer 耗尽后按跳跃且 AirJumpsLeft > 0 → JumpState(使用 DoubleJumpForce)。
|
||
/// - 冲刺:HasAbility(Dash) && DashState.CanAirDash → DashState(地面与空中统一,空中限一次)。
|
||
/// - 抓墙:贴墙时按下朝向墙壁的方向键 → WallSlideState。
|
||
/// - 增强下落重力(FallGravityMult)确保下落快于上升,手感紧实。
|
||
/// </summary>
|
||
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()
|
||
{
|
||
// ── 跳跃输入(郊狼跳 / 二段跳)────────────────────────────────────
|
||
// 先确认有可用跳跃机会,再消耗缓冲,避免无操作时静默吃掉输入
|
||
if ((Move.HasCoyoteTime || Owner.AirJumpsLeft > 0) && Buffer.ConsumeJump())
|
||
{
|
||
if (Move.HasCoyoteTime)
|
||
{
|
||
// 郊狼跳:一段跳,使用 JumpForce
|
||
_owner.TransitionTo(_owner.GetState<JumpState>());
|
||
return;
|
||
}
|
||
// 二段跳:通过 SetDoubleJump 标记 JumpState 使用 DoubleJumpForce
|
||
Owner.UseAirJump();
|
||
Owner.GetState<JumpState>()?.SetDoubleJump(true);
|
||
_owner.TransitionTo(_owner.GetState<JumpState>());
|
||
return;
|
||
}
|
||
|
||
// ── 冲刺(地面/空中统一使用 DashState)────────────────────────────
|
||
// 先确认能力与冷却均满足,再消耗缓冲,避免无操作时静默吃掉输入
|
||
var dashState = Owner.GetState<DashState>();
|
||
if (dashState != null && dashState.CanAirDash
|
||
&& 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<UpAttackState>());
|
||
else if (Input.MoveInput.y < -0.5f
|
||
&& Stats != null && Stats.HasAbility(AbilityType.DownSlash))
|
||
_owner.TransitionTo(_owner.GetState<DownAttackState>());
|
||
else
|
||
_owner.TransitionTo(_owner.GetState<AirAttackState>());
|
||
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<RunState>());
|
||
else
|
||
_owner.TransitionTo(_owner.GetState<IdleState>());
|
||
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)
|
||
{
|
||
_owner.TransitionTo(_owner.GetState<WallSlideState>());
|
||
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<RunState>());
|
||
else
|
||
_owner.TransitionTo(_owner.GetState<IdleState>());
|
||
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<WallSlideState>();
|
||
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));
|
||
}
|
||
}
|
||
}
|
||
}
|