181 lines
7.4 KiB
C#
181 lines
7.4 KiB
C#
using UnityEngine;
|
||
|
||
namespace BaseGames.Player.States
|
||
{
|
||
/// <summary>
|
||
/// 抓墙状态(自定义设计,架构 05_PlayerModule §2)。
|
||
///
|
||
/// 触发条件:空中贴墙时玩家按下朝向墙壁的方向键(由 FallState/JumpState 检测并调用 PrepareEnter)。
|
||
/// 维持条件:进入后无需持续按键;按下↓键或落地时解除;跳跃键触发蹬墙跳后由 WallJumpState 接管。
|
||
///
|
||
/// 脱离方式:
|
||
/// - 跳跃键 → 蹬墙跳(WallJumpState,由 OnJumpPressed 响应)
|
||
/// - ↓ 键 / 反方向键 → 主动脱离,进入 FallState;wall coyote 窗口内仍可触发蹬墙跳
|
||
/// - 离开墙面 → 自然下落(FallState)
|
||
/// - 着地 → 闲置(IdleState)
|
||
///
|
||
/// 下滑速度分为两档(均在 PlayerMovementConfigSO 配置):
|
||
/// - 正常模式(低于等于 wallGrabY,可蹬墙跳)→ WallHangSpeed(缓慢下滑)
|
||
/// - 受限模式(高于 wallGrabY,不可蹬墙跳) → WallSlideSpeed(较快下滑)
|
||
///
|
||
/// 高度记忆机制(防止单面墙反复爬升):
|
||
/// - 首次抓墙(或切换到另一侧墙壁)时记录 _wallGrabY。
|
||
/// - 若当前 Y > _wallGrabY + 容差 → 受限模式:持续下滑,不可蹬墙跳。
|
||
/// - 若当前 Y ≤ _wallGrabY + 容差 → 正常模式:静止悬挂,可触发蹬墙跳。
|
||
/// - 重置时机:① 落地时(由 IdleState/RunState 调用 ResetWallGrab);
|
||
/// ② 切换到另一侧墙壁时(OnStateEnter 内自动判断)。
|
||
/// </summary>
|
||
public class WallSlideState : PlayerStateBase
|
||
{
|
||
// ── 运行时状态 ────────────────────────────────────────────────────────
|
||
/// <summary>首次抓住该侧墙壁时记录的 Y 坐标。</summary>
|
||
private float _wallGrabY = float.MinValue;
|
||
/// <summary>上次记录 wallGrabY 时的墙壁方向(+1 右墙 / -1 左墙)。</summary>
|
||
private int _lastGrabDir = 0;
|
||
/// <summary>本次进入时确认的墙壁方向(由 PrepareEnter 设置)。</summary>
|
||
private int _wallDir = 0;
|
||
/// <summary>当前是否处于正常模式(可蹬墙跳)。受限模式(isRestricted)时不可跳。</summary>
|
||
private bool _canJump = false;
|
||
|
||
public WallSlideState(PlayerController owner) : base(owner) { }
|
||
|
||
/// <summary>
|
||
/// 由 FallState / JumpState 在调用 TransitionTo 之前调用,传入已确认的墙壁方向。
|
||
/// </summary>
|
||
public void PrepareEnter(int wallDir) => _wallDir = wallDir;
|
||
|
||
/// <summary>
|
||
/// 落地时由 IdleState / RunState 调用,重置高度记忆,允许下次抓同侧墙时重新计算。
|
||
/// </summary>
|
||
public void ResetWallGrab()
|
||
{
|
||
_wallGrabY = float.MinValue;
|
||
_lastGrabDir = 0;
|
||
}
|
||
|
||
public override void OnStateEnter()
|
||
{
|
||
// 若切换到另一侧墙壁,重置高度记录(视为全新的墙壁)
|
||
if (_wallDir != _lastGrabDir)
|
||
{
|
||
_wallGrabY = Owner.transform.position.y;
|
||
_lastGrabDir = _wallDir;
|
||
}
|
||
|
||
// 锁定自动朝向,防止 LateUpdate 的 UpdateFacing 覆盖手动设置的朝向
|
||
Move?.LockFacing(true);
|
||
// 抓墙时始终面朝墙壁,确保背墙跳的 FlipFacing(-_wallDir) 能正确翻转朝向
|
||
Move?.FlipFacing(_wallDir);
|
||
|
||
// 计算当前是否处于正常模式
|
||
UpdateCanJump();
|
||
|
||
if (AnimCfg?.WallSlide != null)
|
||
Anim?.Play(AnimCfg.WallSlide);
|
||
|
||
// 抓墙时恢复空中冲刺次数(与蹬墙跳一致)
|
||
Owner.GetState<DashState>()?.ResetDashCharge();
|
||
|
||
// 消耗蹬墙跳后的自动抓墙标记
|
||
Owner.SetPostWallJump(false);
|
||
|
||
Input.JumpStartedEvent += OnJumpPressed;
|
||
}
|
||
|
||
public override void OnStateExit()
|
||
{
|
||
Move?.LockFacing(false);
|
||
Input.JumpStartedEvent -= OnJumpPressed;
|
||
}
|
||
|
||
public override void OnStateUpdate()
|
||
{
|
||
var wd = Owner.WallDetector;
|
||
|
||
// 离开墙壁 → 启动墙壁土狼时间后下落
|
||
if (wd == null || !wd.IsTouchingWall)
|
||
{
|
||
Move.StartWallCoyote(_wallDir);
|
||
Owner.TransitionTo(Owner.GetState<FallState>());
|
||
return;
|
||
}
|
||
|
||
// 着地 → 闲置
|
||
if (Move.IsGrounded)
|
||
{
|
||
Owner.TransitionTo(Owner.GetState<IdleState>());
|
||
return;
|
||
}
|
||
|
||
// ── 抓墙攻击:优先于方向键脱离检测,朝离墙方向翻转后进入空中攻击(无土狼时间)──
|
||
if (Buffer.ConsumeAttack())
|
||
{
|
||
Move.FlipFacing(-_wallDir);
|
||
Owner.TransitionTo(Owner.GetState<AirAttackState>());
|
||
return;
|
||
}
|
||
|
||
// ── 抓墙冲刺:优先于方向键脱离检测,朝离墙方向翻转后冲出(无土狼时间)──────────
|
||
var ds = Owner.GetState<DashState>();
|
||
if (ds != null && ds.CanDashMidAir
|
||
&& Stats != null && Stats.HasAbility(AbilityType.Dash)
|
||
&& Buffer.ConsumeDash())
|
||
{
|
||
Move.FlipFacing(-_wallDir);
|
||
Owner.TransitionTo(ds);
|
||
return;
|
||
}
|
||
|
||
// 按下方向键 → 启动墙壁土狼时间后主动脱离,自然下落
|
||
if (Input.MoveInput.y < -0.5f)
|
||
{
|
||
Move.StartWallCoyote(_wallDir);
|
||
Owner.TransitionTo(Owner.GetState<FallState>());
|
||
return;
|
||
}
|
||
|
||
// 按反方向键 → 启动墙壁土狼时间后脱离
|
||
float mx = Input.MoveInput.x;
|
||
if (Mathf.Abs(mx) > 0.1f && (mx > 0f ? 1 : -1) != _wallDir)
|
||
{
|
||
Move.StartWallCoyote(_wallDir);
|
||
Owner.TransitionTo(Owner.GetState<FallState>());
|
||
return;
|
||
}
|
||
|
||
UpdateCanJump();
|
||
}
|
||
|
||
public override void OnStateFixedUpdate()
|
||
{
|
||
if (_canJump)
|
||
// 正常模式(低于等于 wallGrabY,可蹬墙跳):缓慢下滑,给玩家操作窗口
|
||
Move?.ApplyWallSlide(Cfg.WallHangSpeed);
|
||
else
|
||
// 受限模式(高于 wallGrabY):较快下滑,不可蹬墙跳
|
||
Move?.ApplyWallSlide(Cfg.WallSlideSpeed);
|
||
}
|
||
|
||
// ── 内部 ──────────────────────────────────────────────────────────────
|
||
|
||
private void UpdateCanJump()
|
||
{
|
||
float tolerance = Cfg?.WallGrabHeightTolerance ?? 0.05f;
|
||
_canJump = Owner.transform.position.y <= _wallGrabY + tolerance;
|
||
}
|
||
|
||
private void OnJumpPressed()
|
||
{
|
||
// 受限模式禁止蹬墙跳
|
||
if (!_canJump) return;
|
||
|
||
var wjs = Owner.GetState<WallJumpState>();
|
||
if (wjs == null) return;
|
||
|
||
wjs.PrepareEnter(_wallDir, Input.MoveInput.x);
|
||
Owner.TransitionTo(wjs);
|
||
}
|
||
}
|
||
}
|
||
|