using UnityEngine; namespace BaseGames.Player.States { /// /// 抓墙状态(自定义设计,架构 05_PlayerModule §2)。 /// /// 触发条件:空中贴墙时玩家按下朝向墙壁的方向键(由 FallState/JumpState 检测并调用 PrepareEnter)。 /// 维持条件:进入后无需持续按键;主动按下反方向键或落地时解除。 /// /// 高度记忆机制(防止单面墙反复爬升): /// - 首次抓墙(或切换到另一侧墙壁)时记录 _wallGrabY。 /// - 若当前 Y > _wallGrabY + 容差 → 受限模式:持续下滑,不可蹬墙跳。 /// - 若当前 Y ≤ _wallGrabY + 容差 → 正常模式:静止悬挂,可触发蹬墙跳。 /// - 重置时机:① 落地时(由 IdleState/RunState 调用 ResetWallGrab); /// ② 切换到另一侧墙壁时(OnStateEnter 内自动判断)。 /// public class WallSlideState : PlayerStateBase { // ── 运行时状态 ──────────────────────────────────────────────────────── /// 首次抓住该侧墙壁时记录的 Y 坐标。 private float _wallGrabY = float.MinValue; /// 上次记录 wallGrabY 时的墙壁方向(+1 右墙 / -1 左墙)。 private int _lastGrabDir = 0; /// 本次进入时确认的墙壁方向(由 PrepareEnter 设置)。 private int _wallDir = 0; /// 当前是否处于正常模式(可蹬墙跳)。受限模式(isRestricted)时不可跳。 private bool _canJump = false; public WallSlideState(PlayerController owner) : base(owner) { } /// /// 由 FallState / JumpState 在调用 TransitionTo 之前调用,传入已确认的墙壁方向。 /// public void PrepareEnter(int wallDir) => _wallDir = wallDir; /// /// 落地时由 IdleState / RunState 调用,重置高度记忆,允许下次抓同侧墙时重新计算。 /// public void ResetWallGrab() { _wallGrabY = float.MinValue; _lastGrabDir = 0; } public override void OnStateEnter() { // 若切换到另一侧墙壁,重置高度记录(视为全新的墙壁) if (_wallDir != _lastGrabDir) { _wallGrabY = Owner.transform.position.y; _lastGrabDir = _wallDir; } // 计算当前是否处于正常模式 UpdateCanJump(); if (AnimCfg?.WallSlide != null) Anim?.Play(AnimCfg.WallSlide); // 消耗蹬墙跳后的自动抓墙标记 Owner.SetPostWallJump(false); Input.JumpStartedEvent += OnJumpPressed; } public override void OnStateExit() { Input.JumpStartedEvent -= OnJumpPressed; } public override void OnStateUpdate() { var wd = Owner.WallDetector; // 离开墙壁 → 下落 if (wd == null || !wd.IsTouchingWall) { Owner.TransitionTo(Owner.GetState()); return; } // 着地 → 闲置 if (Move.IsGrounded) { Owner.TransitionTo(Owner.GetState()); return; } // 主动按反方向键 → 脱离(松墙下落) float mx = Input.MoveInput.x; if (Mathf.Abs(mx) > 0.1f) { int inputDir = mx > 0f ? 1 : -1; if (inputDir != _wallDir) { Owner.TransitionTo(Owner.GetState()); return; } } // 每帧刷新正常/受限状态 UpdateCanJump(); } public override void OnStateFixedUpdate() { if (_canJump) // 正常模式:静止悬挂,阻止向下速度 Move?.ZeroVerticalVelocity(); else // 受限模式:持续下滑 Move?.ApplyWallSlide(); } // ── 内部 ────────────────────────────────────────────────────────────── 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(); if (wjs == null) return; wjs.PrepareEnter(_wallDir, Input.MoveInput.x); Owner.TransitionTo(wjs); } } }