From acc5471c47405bafb62f3883a3170791a9d58150 Mon Sep 17 00:00:00 2001 From: Joywayer Date: Thu, 21 May 2026 20:31:24 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E7=A7=BB=E5=8A=A8=E5=B9=B3?= =?UTF-8?q?=E5=8F=B0=E7=9A=84=E4=B9=98=E5=AE=A2=E6=8E=A5=E8=A7=A6=E9=80=BB?= =?UTF-8?q?=E8=BE=91=EF=BC=8C=E6=B7=BB=E5=8A=A0=E7=89=A9=E7=90=86=E7=A2=B0?= =?UTF-8?q?=E6=92=9E=E4=BD=93=E4=BB=A5=E5=88=A4=E6=96=AD=E4=B9=98=E5=AE=A2?= =?UTF-8?q?=E6=8E=A5=E8=A7=A6=E6=96=B9=E5=90=91=EF=BC=8C=E5=B9=B6=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0=E9=9B=B6=E6=91=A9=E6=93=A6=E6=9D=90=E8=B4=A8=E4=BB=A5?= =?UTF-8?q?=E9=98=B2=E6=AD=A2=E8=A7=92=E8=89=B2=E8=A2=AB=E4=BE=A7=E9=9D=A2?= =?UTF-8?q?=E6=89=98=E4=BD=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Assets/_Game/Scripts/World/MovingPlatform.cs | 53 ++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/Assets/_Game/Scripts/World/MovingPlatform.cs b/Assets/_Game/Scripts/World/MovingPlatform.cs index 452b3d2..26faded 100644 --- a/Assets/_Game/Scripts/World/MovingPlatform.cs +++ b/Assets/_Game/Scripts/World/MovingPlatform.cs @@ -38,6 +38,7 @@ namespace BaseGames.World [SerializeField] private LayerMask _passengerLayer; // 应包含 Player + Enemy 层 private Rigidbody2D _rb; + private Collider2D _physicsCollider; // 物理碰撞体(非 Trigger),用于判断乘客接触方向 private readonly List _passengers = new(); private readonly List _passengerSnapshot = new(); // 迭代快照,防并发修改 private int _waypointIndex; @@ -48,12 +49,29 @@ namespace BaseGames.World private WaitForSeconds _waitForEndpoint; private readonly CompositeDisposable _subs = new(); + // 共享零摩擦材质:确保平台侧面不产生向上的摩擦力, + // 防止角色从侧面接触移动平台时被摩擦力托住而无法下落。 + private static PhysicsMaterial2D s_zeroFriction; + private void Awake() { _rb = GetComponent(); _rb.bodyType = RigidbodyType2D.Kinematic; _rb.interpolation = RigidbodyInterpolation2D.Interpolate; _waitForEndpoint = new WaitForSeconds(_waitAtEndpoint); + + // 缓存物理碰撞体(非 Trigger),并自动挂载零摩擦材质。 + // 零摩擦确保平台从侧面压向角色时,接触力只有法向分量(水平推开), + // 无切向摩擦分量,彻底消除"平台侧面摩擦托住角色/角色无法下落"的问题。 + EnsureZeroFrictionMaterial(); + foreach (var col in GetComponentsInChildren()) + { + if (col.isTrigger) continue; + _physicsCollider = col; + if (col.sharedMaterial == null || col.sharedMaterial.friction > 0f) + col.sharedMaterial = s_zeroFriction; + break; + } } private void OnEnable() @@ -147,6 +165,10 @@ namespace BaseGames.World // attachedRigidbody:若玩家碰撞体在子对象上,也能正确找到根对象上的 IPassengerReceiver if (!other.attachedRigidbody) return; if (!other.attachedRigidbody.TryGetComponent(out var receiver)) return; + // 仅接受从上方进入的乘客:乘客碰撞体底部须位于平台顶面附近。 + // 当平台从侧面移向角色时,角色底部远低于平台顶面,此检测将其排除, + // 防止角色被错误注册为乘客并获得平台水平速度(被"推着走")。 + if (!IsPassengerFromAbove(other)) return; if (!_passengers.Contains(receiver)) _passengers.Add(receiver); } @@ -164,6 +186,37 @@ namespace BaseGames.World private bool IsPassengerLayer(Collider2D col) => (_passengerLayer.value & (1 << col.gameObject.layer)) != 0; + /// + /// 判断乘客碰撞体是否从上方进入传感器。 + /// 以物理碰撞体(或传感器)顶面 Y 为基准,允许少量穿透容差 kTopTolerance。 + /// 当平台从侧面撞上角色时,角色底部远低于平台顶面,返回 false,不注册为乘客。 + /// + private bool IsPassengerFromAbove(Collider2D passengerCollider) + { + // 平台顶面 Y:优先用物理碰撞体 bounds,其次用传感器 bounds,最后退回 transform.position.y + float platformTop = _physicsCollider != null + ? _physicsCollider.bounds.max.y + : (_passengerSensor != null + ? _passengerSensor.bounds.max.y + : transform.position.y); + + // 容差 0.12f:允许落地时的正常物理穿透深度(通常 < 0.05f),同时比角色半高(约 0.9f)小得多, + // 足以可靠区分"从上方落入"与"从侧面进入"。 + const float kTopTolerance = 0.12f; + return passengerCollider.bounds.min.y >= platformTop - kTopTolerance; + } + + /// 初始化共享零摩擦材质(静态单例,整个项目只创建一次)。 + private static void EnsureZeroFrictionMaterial() + { + if (s_zeroFriction != null) return; + s_zeroFriction = new PhysicsMaterial2D("MovingPlatform_ZeroFriction") + { + friction = 0f, + bounciness = 0f, + }; + } + #if UNITY_EDITOR private void OnDrawGizmos() {