优化移动平台的乘客接触逻辑,添加物理碰撞体以判断乘客接触方向,并实现零摩擦材质以防止角色被侧面托住

This commit is contained in:
2026-05-21 20:31:24 +08:00
parent 8ccfa22a0e
commit acc5471c47

View File

@@ -38,6 +38,7 @@ namespace BaseGames.World
[SerializeField] private LayerMask _passengerLayer; // 应包含 Player + Enemy 层
private Rigidbody2D _rb;
private Collider2D _physicsCollider; // 物理碰撞体(非 Trigger用于判断乘客接触方向
private readonly List<IPassengerReceiver> _passengers = new();
private readonly List<IPassengerReceiver> _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<Rigidbody2D>();
_rb.bodyType = RigidbodyType2D.Kinematic;
_rb.interpolation = RigidbodyInterpolation2D.Interpolate;
_waitForEndpoint = new WaitForSeconds(_waitAtEndpoint);
// 缓存物理碰撞体(非 Trigger并自动挂载零摩擦材质。
// 零摩擦确保平台从侧面压向角色时,接触力只有法向分量(水平推开),
// 无切向摩擦分量,彻底消除"平台侧面摩擦托住角色/角色无法下落"的问题。
EnsureZeroFrictionMaterial();
foreach (var col in GetComponentsInChildren<Collider2D>())
{
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<IPassengerReceiver>(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;
/// <summary>
/// 判断乘客碰撞体是否从上方进入传感器。
/// 以物理碰撞体(或传感器)顶面 Y 为基准,允许少量穿透容差 <c>kTopTolerance</c>。
/// 当平台从侧面撞上角色时,角色底部远低于平台顶面,返回 false不注册为乘客。
/// </summary>
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;
}
/// <summary>初始化共享零摩擦材质(静态单例,整个项目只创建一次)。</summary>
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()
{