PlayerMovementConfigSO.cs
新增 WallHangSpeed = 1f:正常抓墙(低于等于 wallGrabY,可蹬墙跳区间)的缓慢下滑速度 WallSlideSpeed 语义调整为:受限模式(高于 wallGrabY)的较快下滑速度 PlayerMovement.cs ApplyWallSlide() 改为 ApplyWallSlide(float speed),由调用方传入对应速度 WallSlideState.cs OnStateFixedUpdate:正常模式用 WallHangSpeed,受限模式用 WallSlideSpeed(两档清晰分离) 恢复反方向键脱离:脱离时同样调用 StartWallCoyote,wall coyote 窗口内仍能触发蹬墙跳 更新类头注释完整描述脱离方式和下滑速度档位Two wall slide improvements:
This commit is contained in:
@@ -17,8 +17,14 @@ namespace BaseGames.Editor
|
||||
/// · PlayerHitBox ↔ EnemyHurtBox → 应碰撞(玩家攻击伤害敌人)
|
||||
/// · EnemyHitBox ↔ PlayerHurtBox → 应碰撞(敌人攻击伤害玩家)
|
||||
/// · EnemyHitBox ↔ EnemyHurtBox → 应碰撞(敌人可互相伤害,HitBox 运行时排除自身根节点)
|
||||
/// · Player ↔ Platform → 应碰撞(玩家站在平台上)
|
||||
/// · Enemy ↔ Platform → 应碰撞(敌人站在平台上)
|
||||
/// · Player ↔ Platform → 应碰撞(玩家站在实体平台上)
|
||||
/// · Player ↔ OneWayPlatform → 应碰撞(玩家站在单向平台上)
|
||||
/// · Player ↔ MovingOneWayPlatform → 应碰撞(玩家站在移动单向平台上)
|
||||
/// · Player ↔ MidHeightOneWayPlatform → 应碰撞(玩家站在半高单向平台上)
|
||||
/// · Enemy ↔ Platform → 应碰撞(敌人站在实体平台上)
|
||||
/// · Enemy ↔ OneWayPlatform → 应碰撞(敌人站在单向平台上)
|
||||
/// · Enemy ↔ MovingOneWayPlatform → 应碰撞(敌人站在移动单向平台上)
|
||||
/// · Enemy ↔ MidHeightOneWayPlatform → 应碰撞(敌人站在半高单向平台上)
|
||||
/// · PlayerProjectile ↔ EnemyHurtBox → 应碰撞(玩家投射物伤害敌人)
|
||||
/// · PlayerProjectile ↔ PlayerHurtBox → 应忽略(玩家投射物不自伤)
|
||||
/// · PlayerProjectile ↔ Platform → 应碰撞(玩家投射物命中地形)
|
||||
@@ -41,8 +47,14 @@ namespace BaseGames.Editor
|
||||
new("PlayerHitBox", "EnemyHurtBox", true, "玩家攻击伤害敌人"),
|
||||
new("EnemyHitBox", "PlayerHurtBox", true, "敌人攻击伤害玩家"),
|
||||
new("EnemyHitBox", "EnemyHurtBox", true, "敌人可互相伤害(HitBox 运行时排除自身根节点)"),
|
||||
new("Player", "Platform", true, "玩家站在平台上"),
|
||||
new("Enemy", "Platform", true, "敌人站在平台上"),
|
||||
new("Player", "Platform", true, "玩家站在实体平台上"),
|
||||
new("Player", "OneWayPlatform", true, "玩家站在单向平台上(PlatformEffector2D 控制单向穿透)"),
|
||||
new("Player", "MovingOneWayPlatform", true, "玩家站在移动单向平台上"),
|
||||
new("Player", "MidHeightOneWayPlatform", true, "玩家站在半高单向平台上"),
|
||||
new("Enemy", "Platform", true, "敌人站在实体平台上"),
|
||||
new("Enemy", "OneWayPlatform", true, "敌人站在单向平台上"),
|
||||
new("Enemy", "MovingOneWayPlatform", true, "敌人站在移动单向平台上"),
|
||||
new("Enemy", "MidHeightOneWayPlatform", true, "敌人站在半高单向平台上"),
|
||||
new("PlayerProjectile", "EnemyHurtBox", true, "玩家投射物伤害敌人"),
|
||||
new("PlayerProjectile", "PlayerHurtBox", false, "玩家投射物不自伤"),
|
||||
new("PlayerProjectile", "Platform", true, "玩家投射物命中地形"),
|
||||
|
||||
@@ -24,6 +24,8 @@ namespace BaseGames.Player
|
||||
// ── 运行时状态 ────────────────────────────────────────────────────────
|
||||
private Rigidbody2D _rb;
|
||||
private float _coyoteTimer;
|
||||
private float _wallCoyoteTimer;
|
||||
private int _wallCoyoteDir; // 离墙时记录的墙壁方向(+1 右 / -1 左)
|
||||
private bool _isGrounded;
|
||||
// Update 中调用 ZeroHorizontalVelocity 后设置此标记;
|
||||
// 下一个 FixedUpdate(-200,先于状态机 -100)读取并清零,
|
||||
@@ -47,6 +49,7 @@ namespace BaseGames.Player
|
||||
[SerializeField] private bool _dbg_IsGrounded;
|
||||
[SerializeField] private bool _dbg_OnOneWayPlatform;
|
||||
[SerializeField] private bool _dbg_HasCoyoteTime;
|
||||
[SerializeField] private bool _dbg_HasWallCoyoteTime;
|
||||
[SerializeField] private bool _dbg_IsWallLeft;
|
||||
[SerializeField] private bool _dbg_IsWallRight;
|
||||
[SerializeField] private bool _dbg_CancelWindowOpen;
|
||||
@@ -55,6 +58,8 @@ namespace BaseGames.Player
|
||||
|
||||
public bool IsGrounded => _isGrounded;
|
||||
public bool HasCoyoteTime => _coyoteTimer > 0f;
|
||||
public bool HasWallCoyoteTime => _wallCoyoteTimer > 0f;
|
||||
public int WallCoyoteDir => _wallCoyoteDir;
|
||||
public bool IsWallLeft => _isWallLeft;
|
||||
public bool IsWallRight => _isWallRight;
|
||||
public bool OnOneWayPlatform => _onOneWayPlatform;
|
||||
@@ -93,12 +98,15 @@ namespace BaseGames.Player
|
||||
else
|
||||
_coyoteTimer = Mathf.Max(0f, _coyoteTimer - Time.fixedDeltaTime);
|
||||
|
||||
_wallCoyoteTimer = Mathf.Max(0f, _wallCoyoteTimer - Time.fixedDeltaTime);
|
||||
|
||||
#if UNITY_EDITOR
|
||||
_dbg_VelocityX = _rb.velocity.x;
|
||||
_dbg_VelocityY = _rb.velocity.y;
|
||||
_dbg_IsGrounded = _isGrounded;
|
||||
_dbg_OnOneWayPlatform = _onOneWayPlatform;
|
||||
_dbg_HasCoyoteTime = _coyoteTimer > 0f;
|
||||
_dbg_HasWallCoyoteTime = _wallCoyoteTimer > 0f;
|
||||
_dbg_IsWallLeft = _isWallLeft;
|
||||
_dbg_IsWallRight = _isWallRight;
|
||||
_dbg_CancelWindowOpen = _cancelWindowOpen;
|
||||
@@ -198,6 +206,21 @@ namespace BaseGames.Player
|
||||
// ── 取消窗口 ──────────────────────────────────────────────────────────
|
||||
public void SetCancelWindowOpen(bool open) => _cancelWindowOpen = open;
|
||||
|
||||
// ── 墙壁土狼时间 ──────────────────────────────────────────────────────
|
||||
/// <summary>
|
||||
/// 由 WallSlideState 在切换到 FallState 时调用:启动墙壁土狼计时器。
|
||||
/// 窗口内(HasWallCoyoteTime == true)按跳跃键仍可触发蹬墙跳。
|
||||
/// wallDir:+1 = 右墙 / -1 = 左墙。
|
||||
/// </summary>
|
||||
public void StartWallCoyote(int wallDir)
|
||||
{
|
||||
_wallCoyoteTimer = _config.WallCoyoteTime;
|
||||
_wallCoyoteDir = wallDir;
|
||||
}
|
||||
|
||||
/// <summary>消耗墙壁土狼时间,防止同一帧被多次触发。</summary>
|
||||
public void ConsumeWallCoyote() => _wallCoyoteTimer = 0f;
|
||||
|
||||
// ── 冲刺 ──────────────────────────────────────────────────────────────
|
||||
/// <summary>
|
||||
/// 施加冲刺速度(DashState/DashState 调用)。
|
||||
@@ -210,12 +233,12 @@ namespace BaseGames.Player
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 壁滑:将垂直速度限制为 -WallSlideSpeed(受限抓墙时向下缓慢滑动)。
|
||||
/// WallSlideState.OnStateFixedUpdate 在受限模式下每帧调用。
|
||||
/// 壁滑:将垂直速度限制为 -speed(每帧调用以约束最大下滑速度)。
|
||||
/// WallSlideState.OnStateFixedUpdate 根据正常/受限模式传入不同速度。
|
||||
/// </summary>
|
||||
public void ApplyWallSlide()
|
||||
public void ApplyWallSlide(float speed)
|
||||
{
|
||||
float targetY = -_config.WallSlideSpeed;
|
||||
float targetY = -speed;
|
||||
if (_rb.velocity.y < targetY)
|
||||
_rb.velocity = new Vector2(_rb.velocity.x, targetY);
|
||||
}
|
||||
|
||||
@@ -57,10 +57,16 @@ namespace BaseGames.Player
|
||||
[Header("抓墙 / 壁滑")]
|
||||
[Tooltip("受限抓墙时(高于 wallGrabY)的下滑速度(单位/秒)。推荐 2。")]
|
||||
public float WallSlideSpeed = 2f;
|
||||
[Tooltip("正常抓墙时(低于等于 wallGrabY,可蹬墙跳区间)的缓慢下滑速度(单位/秒)。\n" +
|
||||
"设为 0 = 完全静止悬挂;推荐 1,轻微下滑更有手感。")]
|
||||
public float WallHangSpeed = 1f;
|
||||
public float WallRayLength = 0.55f;
|
||||
public float WallRayOffsetY = 0.2f;
|
||||
[Tooltip("抓墙高度容差:当前 Y 不超过 wallGrabY + 此值时视为未抬升,防止浮点抖动误判。")]
|
||||
public float WallGrabHeightTolerance = 0.05f;
|
||||
[Tooltip("离开墙面后仍可触发蹬墙跳的缓冲时长(秒)。" +
|
||||
"类比地面土狼时间,主动按↓脱离或墙面消失后,此窗口内按跳跃仍视为有效的蹬墙跳。推荐 0.12。")]
|
||||
public float WallCoyoteTime = 0.12f;
|
||||
|
||||
[Header("蹬墙跳 — 背墙跳(Jump Away,远离墙壁斜上方)")]
|
||||
[Tooltip("背墙跳水平速度(远离墙壁方向)。推荐 14。")]
|
||||
|
||||
@@ -22,6 +22,20 @@ namespace BaseGames.Player.States
|
||||
|
||||
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())
|
||||
|
||||
@@ -6,7 +6,17 @@ namespace BaseGames.Player.States
|
||||
/// 抓墙状态(自定义设计,架构 05_PlayerModule §2)。
|
||||
///
|
||||
/// 触发条件:空中贴墙时玩家按下朝向墙壁的方向键(由 FallState/JumpState 检测并调用 PrepareEnter)。
|
||||
/// 维持条件:进入后无需持续按键;主动按下反方向键或落地时解除。
|
||||
/// 维持条件:进入后无需持续按键;按下↓键或落地时解除;跳跃键触发蹬墙跳后由 WallJumpState 接管。
|
||||
///
|
||||
/// 脱离方式:
|
||||
/// - 跳跃键 → 蹬墙跳(WallJumpState,由 OnJumpPressed 响应)
|
||||
/// - ↓ 键 / 反方向键 → 主动脱离,进入 FallState;wall coyote 窗口内仍可触发蹬墙跳
|
||||
/// - 离开墙面 → 自然下落(FallState)
|
||||
/// - 着地 → 闲置(IdleState)
|
||||
///
|
||||
/// 下滑速度分为两档(均在 PlayerMovementConfigSO 配置):
|
||||
/// - 正常模式(低于等于 wallGrabY,可蹬墙跳)→ WallHangSpeed(缓慢下滑)
|
||||
/// - 受限模式(高于 wallGrabY,不可蹬墙跳) → WallSlideSpeed(较快下滑)
|
||||
///
|
||||
/// 高度记忆机制(防止单面墙反复爬升):
|
||||
/// - 首次抓墙(或切换到另一侧墙壁)时记录 _wallGrabY。
|
||||
@@ -73,9 +83,10 @@ namespace BaseGames.Player.States
|
||||
{
|
||||
var wd = Owner.WallDetector;
|
||||
|
||||
// 离开墙壁 → 下落
|
||||
// 离开墙壁 → 启动墙壁土狼时间后下落
|
||||
if (wd == null || !wd.IsTouchingWall)
|
||||
{
|
||||
Move.StartWallCoyote(_wallDir);
|
||||
Owner.TransitionTo(Owner.GetState<FallState>());
|
||||
return;
|
||||
}
|
||||
@@ -87,30 +98,35 @@ namespace BaseGames.Player.States
|
||||
return;
|
||||
}
|
||||
|
||||
// 主动按反方向键 → 脱离(松墙下落)
|
||||
float mx = Input.MoveInput.x;
|
||||
if (Mathf.Abs(mx) > 0.1f)
|
||||
// 按下方向键 → 启动墙壁土狼时间后主动脱离,自然下落
|
||||
if (Input.MoveInput.y < -0.5f)
|
||||
{
|
||||
int inputDir = mx > 0f ? 1 : -1;
|
||||
if (inputDir != _wallDir)
|
||||
{
|
||||
Owner.TransitionTo(Owner.GetState<FallState>());
|
||||
return;
|
||||
}
|
||||
Move.StartWallCoyote(_wallDir);
|
||||
Owner.TransitionTo(Owner.GetState<FallState>());
|
||||
return;
|
||||
}
|
||||
|
||||
// 按反方向键 → 启动墙壁土狼时间后脱离
|
||||
// wall coyote 存在时,离墙后短窗口内仍可触发蹬墙跳,不会误双跳
|
||||
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)
|
||||
// 正常模式:静止悬挂,阻止向下速度
|
||||
Move?.ZeroVerticalVelocity();
|
||||
// 正常模式(低于等于 wallGrabY,可蹬墙跳):缓慢下滑,给玩家操作窗口
|
||||
Move?.ApplyWallSlide(Cfg.WallHangSpeed);
|
||||
else
|
||||
// 受限模式:持续下滑
|
||||
Move?.ApplyWallSlide();
|
||||
// 受限模式(高于 wallGrabY):较快下滑,不可蹬墙跳
|
||||
Move?.ApplyWallSlide(Cfg.WallSlideSpeed);
|
||||
}
|
||||
|
||||
// ── 内部 ──────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -50,6 +50,9 @@
|
||||
| Layer 名称 | 挂载对象 | 用途说明 |
|
||||
|---|---|---|
|
||||
| `Platform` | 静态地面、移动平台、Tilemap 地形、障碍物 | 玩家与敌人站立的实体平台;`PlayerMovement` 的 `_groundLayer`、`PlayerWallDetector` 的 `_wallLayer`(默认)均包含此 Layer;投射物命中后销毁 |
|
||||
| `OneWayPlatform` | 单向平台(静态) | 玩家可从下方穿过、从上方站立的平台;挂载 `PhantomPlate`(`PlatformEffector2D` + `IDropThrough`);`_groundLayer` 必须包含此 Layer 才能检测 `OnOneWayPlatform` 状态 |
|
||||
| `MovingOneWayPlatform` | 单向平台(移动) | 会移动的单向平台;行为同 `OneWayPlatform`,独立层便于移动脚本、AI 路径查询按层筛选 |
|
||||
| `MidHeightOneWayPlatform` | 单向平台(半高) | 半腰高度的单向平台;角色可从侧方穿越;行为同 `OneWayPlatform` |
|
||||
| `Wall` | 墙壁碰撞体(垂直面) | 玩家攀墙检测(`PlayerWallDetector` 默认掩码包含 `Wall` 和 `Platform`);若地形使用统一的 Tilemap 则墙壁可合并到 `Platform` Layer |
|
||||
|
||||
### 2.4 触发区域
|
||||
@@ -81,8 +84,14 @@
|
||||
| `PlayerHitBox` | `EnemyHurtBox` | ✅ | 玩家攻击伤害敌人 |
|
||||
| `EnemyHitBox` | `PlayerHurtBox` | ✅ | 敌人攻击伤害玩家 |
|
||||
| `EnemyHitBox` | `EnemyHurtBox` | ✅ | 敌人可互相伤害(HitBox 运行时排除自身根节点) |
|
||||
| `Player` | `Platform` | ✅ | 玩家站在平台上 |
|
||||
| `Enemy` | `Platform` | ✅ | 敌人站在平台上 |
|
||||
| `Player` | `Platform` | ✅ | 玩家站在实体平台上 |
|
||||
| `Player` | `OneWayPlatform` | ✅ | 玩家站在单向平台上(PlatformEffector2D 控制单向穿透) |
|
||||
| `Player` | `MovingOneWayPlatform` | ✅ | 玩家站在移动单向平台上 |
|
||||
| `Player` | `MidHeightOneWayPlatform` | ✅ | 玩家站在半高单向平台上 |
|
||||
| `Enemy` | `Platform` | ✅ | 敌人站在实体平台上 |
|
||||
| `Enemy` | `OneWayPlatform` | ✅ | 敌人站在单向平台上 |
|
||||
| `Enemy` | `MovingOneWayPlatform` | ✅ | 敌人站在移动单向平台上 |
|
||||
| `Enemy` | `MidHeightOneWayPlatform` | ✅ | 敌人站在半高单向平台上 |
|
||||
| `PlayerProjectile` | `EnemyHurtBox` | ✅ | 玩家投射物伤害敌人 |
|
||||
| `PlayerProjectile` | `PlayerHurtBox` | ❌ | 玩家投射物不自伤 |
|
||||
| `PlayerProjectile` | `Platform` | ✅ | 玩家投射物命中地形 |
|
||||
|
||||
Reference in New Issue
Block a user