优化移动平台的乘客接触逻辑,添加物理碰撞体以判断乘客接触方向,并实现零摩擦材质以防止角色被侧面托住
This commit is contained in:
@@ -38,6 +38,7 @@ namespace BaseGames.World
|
|||||||
[SerializeField] private LayerMask _passengerLayer; // 应包含 Player + Enemy 层
|
[SerializeField] private LayerMask _passengerLayer; // 应包含 Player + Enemy 层
|
||||||
|
|
||||||
private Rigidbody2D _rb;
|
private Rigidbody2D _rb;
|
||||||
|
private Collider2D _physicsCollider; // 物理碰撞体(非 Trigger),用于判断乘客接触方向
|
||||||
private readonly List<IPassengerReceiver> _passengers = new();
|
private readonly List<IPassengerReceiver> _passengers = new();
|
||||||
private readonly List<IPassengerReceiver> _passengerSnapshot = new(); // 迭代快照,防并发修改
|
private readonly List<IPassengerReceiver> _passengerSnapshot = new(); // 迭代快照,防并发修改
|
||||||
private int _waypointIndex;
|
private int _waypointIndex;
|
||||||
@@ -48,12 +49,29 @@ namespace BaseGames.World
|
|||||||
private WaitForSeconds _waitForEndpoint;
|
private WaitForSeconds _waitForEndpoint;
|
||||||
private readonly CompositeDisposable _subs = new();
|
private readonly CompositeDisposable _subs = new();
|
||||||
|
|
||||||
|
// 共享零摩擦材质:确保平台侧面不产生向上的摩擦力,
|
||||||
|
// 防止角色从侧面接触移动平台时被摩擦力托住而无法下落。
|
||||||
|
private static PhysicsMaterial2D s_zeroFriction;
|
||||||
|
|
||||||
private void Awake()
|
private void Awake()
|
||||||
{
|
{
|
||||||
_rb = GetComponent<Rigidbody2D>();
|
_rb = GetComponent<Rigidbody2D>();
|
||||||
_rb.bodyType = RigidbodyType2D.Kinematic;
|
_rb.bodyType = RigidbodyType2D.Kinematic;
|
||||||
_rb.interpolation = RigidbodyInterpolation2D.Interpolate;
|
_rb.interpolation = RigidbodyInterpolation2D.Interpolate;
|
||||||
_waitForEndpoint = new WaitForSeconds(_waitAtEndpoint);
|
_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()
|
private void OnEnable()
|
||||||
@@ -147,6 +165,10 @@ namespace BaseGames.World
|
|||||||
// attachedRigidbody:若玩家碰撞体在子对象上,也能正确找到根对象上的 IPassengerReceiver
|
// attachedRigidbody:若玩家碰撞体在子对象上,也能正确找到根对象上的 IPassengerReceiver
|
||||||
if (!other.attachedRigidbody) return;
|
if (!other.attachedRigidbody) return;
|
||||||
if (!other.attachedRigidbody.TryGetComponent<IPassengerReceiver>(out var receiver)) return;
|
if (!other.attachedRigidbody.TryGetComponent<IPassengerReceiver>(out var receiver)) return;
|
||||||
|
// 仅接受从上方进入的乘客:乘客碰撞体底部须位于平台顶面附近。
|
||||||
|
// 当平台从侧面移向角色时,角色底部远低于平台顶面,此检测将其排除,
|
||||||
|
// 防止角色被错误注册为乘客并获得平台水平速度(被"推着走")。
|
||||||
|
if (!IsPassengerFromAbove(other)) return;
|
||||||
if (!_passengers.Contains(receiver))
|
if (!_passengers.Contains(receiver))
|
||||||
_passengers.Add(receiver);
|
_passengers.Add(receiver);
|
||||||
}
|
}
|
||||||
@@ -164,6 +186,37 @@ namespace BaseGames.World
|
|||||||
private bool IsPassengerLayer(Collider2D col)
|
private bool IsPassengerLayer(Collider2D col)
|
||||||
=> (_passengerLayer.value & (1 << col.gameObject.layer)) != 0;
|
=> (_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
|
#if UNITY_EDITOR
|
||||||
private void OnDrawGizmos()
|
private void OnDrawGizmos()
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user