跳跃靠墙高度修复
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -52,6 +52,11 @@ namespace BaseGames.Player
|
|||||||
private readonly Collider2D[] _groundBuffer = new Collider2D[4];
|
private readonly Collider2D[] _groundBuffer = new Collider2D[4];
|
||||||
private int _groundHitCount;
|
private int _groundHitCount;
|
||||||
private readonly ContactPoint2D[] _slopeContactBuffer = new ContactPoint2D[8];
|
private readonly ContactPoint2D[] _slopeContactBuffer = new ContactPoint2D[8];
|
||||||
|
private readonly ContactPoint2D[] _wallContactBuffer = new ContactPoint2D[8];
|
||||||
|
// 跳跃上升阶段贴墙时保护 vy:物理摩擦会在碰墙瞬间降低垂直速度,
|
||||||
|
// 通过 OnCollisionEnter/Stay2D 将 vy 恢复到碰撞前的值。
|
||||||
|
private float _savedVy;
|
||||||
|
private bool _preserveVyOnWallContact;
|
||||||
|
|
||||||
#if UNITY_EDITOR
|
#if UNITY_EDITOR
|
||||||
// ── 运行时调试(Inspector 中可见)───────────────────────────────
|
// ── 运行时调试(Inspector 中可见)───────────────────────────────
|
||||||
@@ -111,6 +116,10 @@ namespace BaseGames.Player
|
|||||||
_pendingHorizontalZero = false;
|
_pendingHorizontalZero = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 保存本帧物理步开始前的垂直速度,用于 OnCollisionEnter/Stay2D 中恢复被墙壁
|
||||||
|
// 摩擦力降低的 vy(状态机 -100 比本脚本 -200 晚执行,不会影响此处的读取时机)。
|
||||||
|
_savedVy = _rb.velocity.y;
|
||||||
|
|
||||||
CheckGrounded();
|
CheckGrounded();
|
||||||
CheckWalls();
|
CheckWalls();
|
||||||
|
|
||||||
@@ -283,6 +292,47 @@ namespace BaseGames.Player
|
|||||||
/// <summary>消耗墙壁土狼时间,防止同一帧被多次触发。</summary>
|
/// <summary>消耗墙壁土狼时间,防止同一帧被多次触发。</summary>
|
||||||
public void ConsumeWallCoyote() => _wallCoyoteTimer = 0f;
|
public void ConsumeWallCoyote() => _wallCoyoteTimer = 0f;
|
||||||
|
|
||||||
|
// ── 跳跃上升贴墙 vy 保护 ──────────────────────────────────────────────
|
||||||
|
/// <summary>
|
||||||
|
/// JumpState.OnStateEnter/Exit 调用,开启/关闭跳跃上升阶段的 vy 保护。
|
||||||
|
/// 开启后,OnCollisionEnter/Stay2D 检测到水平墙壁接触且角色有朝墙速度时,
|
||||||
|
/// 将 vy 恢复到本帧物理步前的值,消除物理摩擦对跳跃最高点的影响。
|
||||||
|
/// </summary>
|
||||||
|
public void SetPreserveVyOnWallContact(bool preserve)
|
||||||
|
=> _preserveVyOnWallContact = preserve;
|
||||||
|
|
||||||
|
private void OnCollisionEnter2D(Collision2D collision)
|
||||||
|
=> TryRestoreVyFromWallFriction(collision);
|
||||||
|
|
||||||
|
private void OnCollisionStay2D(Collision2D collision)
|
||||||
|
=> TryRestoreVyFromWallFriction(collision);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 检测水平墙壁碰撞时是否因摩擦力降低了 vy,若是则恢复到碰撞前的值。
|
||||||
|
/// 只在角色确实以朝向墙壁的水平速度(_inputVelocityX)发生碰撞时才恢复,
|
||||||
|
/// 防止 ZeroHVel 正常工作(vx=0,无摩擦)的帧中错误地抵消重力。
|
||||||
|
/// </summary>
|
||||||
|
private void TryRestoreVyFromWallFriction(Collision2D collision)
|
||||||
|
{
|
||||||
|
if (!_preserveVyOnWallContact || _savedVy <= 0f) return;
|
||||||
|
for (int i = 0; i < collision.contactCount; i++)
|
||||||
|
{
|
||||||
|
float nx = collision.GetContact(i).normal.x;
|
||||||
|
if (Mathf.Abs(nx) > 0.5f)
|
||||||
|
{
|
||||||
|
// 法线朝右(nx > 0.5)= 左侧墙,角色朝左运动时产生摩擦(vx < 0)
|
||||||
|
// 法线朝左(nx < -0.5)= 右侧墙,角色朝右运动时产生摩擦(vx > 0)
|
||||||
|
bool hadVelocityIntoWall = (nx > 0.5f && _inputVelocityX < -0.1f)
|
||||||
|
|| (nx < -0.5f && _inputVelocityX > 0.1f);
|
||||||
|
if (hadVelocityIntoWall)
|
||||||
|
{
|
||||||
|
_rb.velocity = new Vector2(_rb.velocity.x, _savedVy);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ── IPassengerReceiver ────────────────────────────────────────────────
|
// ── IPassengerReceiver ────────────────────────────────────────────────
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// MovingPlatform.FixedUpdate(-300) 推送本帧平台期望位移。
|
/// MovingPlatform.FixedUpdate(-300) 推送本帧平台期望位移。
|
||||||
@@ -445,6 +495,20 @@ namespace BaseGames.Player
|
|||||||
|
|
||||||
_isWallLeft = Physics2D.Raycast(pos, Vector2.left, len, _groundLayer);
|
_isWallLeft = Physics2D.Raycast(pos, Vector2.left, len, _groundLayer);
|
||||||
_isWallRight = Physics2D.Raycast(pos, Vector2.right, len, _groundLayer);
|
_isWallRight = Physics2D.Raycast(pos, Vector2.right, len, _groundLayer);
|
||||||
|
|
||||||
|
// 物理接触点兜底:补充射线未覆盖图层或长度不足时的漏检。
|
||||||
|
// GetContacts 返回上一物理步的接触点,由本脚本(-200)读取时先于状态机(-100),
|
||||||
|
// 可在状态机决定是否施加水平速度之前获得"当帧最新"的墙壁接触信息。
|
||||||
|
if (!_isWallLeft || !_isWallRight)
|
||||||
|
{
|
||||||
|
int cnt = _rb.GetContacts(_wallContactBuffer);
|
||||||
|
for (int i = 0; i < cnt; i++)
|
||||||
|
{
|
||||||
|
float nx = _wallContactBuffer[i].normal.x;
|
||||||
|
if (nx > 0.5f) _isWallLeft = true; // 法线朝右 = 左侧有墙
|
||||||
|
if (nx < -0.5f) _isWallRight = true; // 法线朝左 = 右侧有墙
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnDrawGizmos()
|
private void OnDrawGizmos()
|
||||||
|
|||||||
@@ -41,16 +41,33 @@ namespace BaseGames.Player.States
|
|||||||
|
|
||||||
_isDoubleJump = false; // 消耗标记
|
_isDoubleJump = false; // 消耗标记
|
||||||
Input.JumpCancelledEvent += OnJumpCancelled;
|
Input.JumpCancelledEvent += OnJumpCancelled;
|
||||||
|
// 开启上升阶段贴墙 vy 保护:防止物理摩擦降低跳跃最高点
|
||||||
|
Move.SetPreserveVyOnWallContact(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnStateUpdate()
|
public override void OnStateUpdate()
|
||||||
{
|
{
|
||||||
// 上升结束时转为下落
|
// 上升结束时转为下落。
|
||||||
|
// 例外:按住朝墙方向且射线已检测到墙时,物理摩擦可能将 vy 瞬间压到 ≤ 0,
|
||||||
|
// 此时不触发 FallState,让后续抓墙检测在 vy 稳定后接管状态转换,
|
||||||
|
// 防止因一帧摩擦导致跳跃高度降低并错误进入 FallState。
|
||||||
if (Move.Rb.velocity.y <= 0f)
|
if (Move.Rb.velocity.y <= 0f)
|
||||||
|
{
|
||||||
|
bool pressingTowardDetectedWall = false;
|
||||||
|
if (Mathf.Abs(Input.MoveInput.x) > 0.01f)
|
||||||
|
{
|
||||||
|
int inputDir = Input.MoveInput.x > 0 ? 1 : -1;
|
||||||
|
bool cwRay = inputDir > 0 ? Move.IsWallRight : Move.IsWallLeft;
|
||||||
|
var wd0 = Owner.WallDetector;
|
||||||
|
pressingTowardDetectedWall = cwRay
|
||||||
|
|| (wd0 != null && wd0.IsTouchingWall && wd0.WallDirection == inputDir);
|
||||||
|
}
|
||||||
|
if (!pressingTowardDetectedWall)
|
||||||
{
|
{
|
||||||
_owner.TransitionTo(_owner.GetState<FallState>());
|
_owner.TransitionTo(_owner.GetState<FallState>());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ── 下冲刺(下 + 冲刺 → 向下冲刺,优先于普通冲刺)──────────────────────
|
// ── 下冲刺(下 + 冲刺 → 向下冲刺,优先于普通冲刺)──────────────────────
|
||||||
// 按住下方向 + 冲刺键,且已解锁 DownDash 能力、空中冲刺次数未耗尽
|
// 按住下方向 + 冲刺键,且已解锁 DownDash 能力、空中冲刺次数未耗尽
|
||||||
@@ -98,8 +115,10 @@ namespace BaseGames.Player.States
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ── 抓墙:贴墙 + 朝向墙壁按键,或蹬墙跳后的自动抓墙──────────────
|
// ── 抓墙:贴墙 + 朝向墙壁按键,或蹬墙跳后的自动抓墙──────────────
|
||||||
|
// 仅在上升结束后(vy ≤ 0)才进入抓墙状态;上升阶段阻止转换以保留顶点重力缩减,
|
||||||
|
// 避免贴墙按方向键导致跳跃最大高度降低。
|
||||||
var wd = Owner.WallDetector;
|
var wd = Owner.WallDetector;
|
||||||
if (wd != null && wd.IsTouchingWall && !Move.IsGrounded)
|
if (wd != null && wd.IsTouchingWall && !Move.IsGrounded && !Move.IsRising)
|
||||||
{
|
{
|
||||||
int wallDir = wd.WallDirection;
|
int wallDir = wd.WallDirection;
|
||||||
bool pressingTowardWall = Mathf.Abs(Input.MoveInput.x) > 0.01f
|
bool pressingTowardWall = Mathf.Abs(Input.MoveInput.x) > 0.01f
|
||||||
@@ -130,11 +149,19 @@ namespace BaseGames.Player.States
|
|||||||
{
|
{
|
||||||
int inputDir = Input.MoveInput.x > 0 ? 1 : -1;
|
int inputDir = Input.MoveInput.x > 0 ? 1 : -1;
|
||||||
var wd = Owner.WallDetector;
|
var wd = Owner.WallDetector;
|
||||||
|
// PlayerWallDetector(order 0)在状态机(order -100)之后执行,
|
||||||
|
// 其 IsTouchingWall / HasPartialContact 对状态机而言是上一帧的结果。
|
||||||
|
// PlayerMovement.CheckWalls()(order -200)在状态机之前执行,
|
||||||
|
// IsWallLeft / IsWallRight 是当帧最新结果,可提前一帧停止施力,
|
||||||
|
// 防止角色以 RunSpeed 压入墙面后物理引擎的摩擦力降低上升速度。
|
||||||
|
bool currentFrameWall = inputDir > 0 ? Move.IsWallRight : Move.IsWallLeft;
|
||||||
if (wd != null && wd.IsTouchingWall && wd.WallDirection == inputDir)
|
if (wd != null && wd.IsTouchingWall && wd.WallDirection == inputDir)
|
||||||
{
|
{
|
||||||
// 按下朝墙方向键 + 在墙上(且未著地)→ 进入抓墙状态
|
// 按下朝墙方向键 + 在墙上(且未著地)→ 进入抓墙状态
|
||||||
|
// 仅在上升结束后(vy ≤ 0)才进入抓墙状态;上升阶段只停止水平施力,
|
||||||
|
// 保留顶点重力缩减逻辑,防止贴墙按键导致跳跃最大高度降低。
|
||||||
var wss = Owner.GetState<WallSlideState>();
|
var wss = Owner.GetState<WallSlideState>();
|
||||||
if (wss != null && !Move.IsGrounded)
|
if (wss != null && !Move.IsGrounded && !Move.IsRising)
|
||||||
{
|
{
|
||||||
wss.PrepareEnter(inputDir);
|
wss.PrepareEnter(inputDir);
|
||||||
Owner.TransitionTo(wss);
|
Owner.TransitionTo(wss);
|
||||||
@@ -142,10 +169,10 @@ namespace BaseGames.Player.States
|
|||||||
}
|
}
|
||||||
Move.ZeroHorizontalVelocity();
|
Move.ZeroHorizontalVelocity();
|
||||||
}
|
}
|
||||||
else if (wd != null && wd.HasPartialContact(inputDir))
|
else if (currentFrameWall || (wd != null && wd.HasPartialContact(inputDir)))
|
||||||
{
|
{
|
||||||
// 仅部分射线命中(如检测点高于矮墙顶部),停止施加朝墙方向的水平速度,
|
// 当帧单射线已检测到墙(PlayerMovement -200 先于状态机 -100 执行),
|
||||||
// 防止角色边角被卡在墙顶而无法继续下落。
|
// 或上一帧部分射线命中——停止施力,防止以 RunSpeed 压入墙面产生摩擦力。
|
||||||
Move.ZeroHorizontalVelocity();
|
Move.ZeroHorizontalVelocity();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -160,6 +187,7 @@ namespace BaseGames.Player.States
|
|||||||
// 顶点悬停可能已降低重力,离开本状态时必须恢复;
|
// 顶点悬停可能已降低重力,离开本状态时必须恢复;
|
||||||
// 否则进入 FallState/DashState 等后续状态时重力仍低于默认值。
|
// 否则进入 FallState/DashState 等后续状态时重力仍低于默认值。
|
||||||
Move.SetGravityScale(Cfg.DefaultGravityScale);
|
Move.SetGravityScale(Cfg.DefaultGravityScale);
|
||||||
|
Move.SetPreserveVyOnWallContact(false);
|
||||||
Input.JumpCancelledEvent -= OnJumpCancelled;
|
Input.JumpCancelledEvent -= OnJumpCancelled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user