优化移动平台的乘客接触逻辑,添加物理碰撞体以判断乘客接触方向,并实现零摩擦材质以防止角色被侧面托住
This commit is contained in:
@@ -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()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user