Files
zeling_v2/Assets/_Game/Scripts/Player/States/FallState.cs

188 lines
9.2 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using UnityEngine;
namespace BaseGames.Player.States
{
/// <summary>
/// 下落状态。
/// - 郊狼跳CoyoteTimer > 0 时按跳跃 → 一段跳JumpState使用 JumpForce
/// - 空中跳跃CoyoteTimer 耗尽后按跳跃且 AirJumpsLeft > 0 → JumpState使用 DoubleJumpForce
/// - 下冲刺HasAbility(DownDash) &amp;&amp; 下方向 + 冲刺键 → DownDashState优先于普通冲刺
/// - 冲刺HasAbility(Dash) &amp;&amp; DashState.CanDashMidAir → 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()
{
// ── 墙壁土狼跳(优先于地面土狼跳 / 二段跳)──────────────────────────
// 离墙后 WallCoyoteTime 秒内按跳跃,视为有效蹬墙跳(背墙跳 / 对墙跳)
if (Move.HasWallCoyoteTime && Buffer.ConsumeJump())
{
var wjs = Owner.GetState<WallJumpState>();
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<JumpState>());
return;
}
// 二段跳:通过 SetDoubleJump 标记 JumpState 使用 DoubleJumpForce
Owner.UseAirJump();
Owner.GetState<JumpState>()?.SetDoubleJump(true);
_owner.TransitionTo(_owner.GetState<JumpState>());
return;
}
// ── 下冲刺(下 + 冲刺 → 向下冲刺,优先于普通冲刺)──────────────────────
// 按住下方向 + 冲刺键,且已解锁 DownDash 能力、空中冲刺次数未耗尽
var dashState = Owner.GetState<DashState>();
if (dashState != null && dashState.CanDashMidAir
&& Input.MoveInput.y < -0.5f
&& Stats != null && Stats.HasAbility(AbilityType.DownDash)
&& Buffer.ConsumeDash())
{
_owner.TransitionTo(_owner.GetState<DownDashState>());
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<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)
&& Stats != null && Stats.HasAbility(AbilityType.WallCling))
{
var wss = _owner.GetState<WallSlideState>();
if (wss != null)
{
wss.PrepareEnter(wallDir);
_owner.TransitionTo(wss);
}
return;
}
}
}
public override void OnStateFixedUpdate()
{
// ── 着地检测(在 FixedUpdate 内与 CheckGrounded 同帧执行)────────────────────
// PlayerMovement.FixedUpdate(-200) 先于 PlayerController.FixedUpdate(-100) 执行,
// 此处读到的 IsGrounded 已是本物理帧最新结果,不存在跨帧读到过期值的问题。
// 落地检测放在 OnStateUpdateUpdate 阶段)时,若低帧率导致同帧内多次 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));
}
}
}
}