using System.Collections; using System.Collections.Generic; using BaseGames.Core; using BaseGames.Core.Events; using UnityEngine; namespace BaseGames.World { /// /// 移动平台。Kinematic Rigidbody2D,支持三种移动模式。 /// /// 乘客跟随(Delta Position 方案,替代 SetParent): /// - PassengerSensor(Trigger)检测进入乘客()。 /// - FixedUpdate(-300) 计算本帧期望位移 delta,推送给所有乘客,再执行 MovePosition。 /// - 乘客在自己的 FixedUpdate(-200) 最前端消费 delta:_rb.position += delta。 /// - 乘客离台时通过 OnLeavePlatform 继承平台速度,避免卡顿。 /// /// 此方案不修改 Transform 层级,与动画/摄像机/HitBox 系统完全兼容。 /// [DefaultExecutionOrder(-300)] [RequireComponent(typeof(Rigidbody2D))] public class MovingPlatform : MonoBehaviour { public enum MoveType { LinearAB, WayPoints, TriggeredLinear } [Header("移动配置")] [SerializeField] private MoveType _moveType = MoveType.LinearAB; [SerializeField] private Transform[] _wayPoints; [SerializeField] private float _speed = 3f; [SerializeField] private float _waitAtEndpoint = 0.5f; [Header("TriggeredLinear 模式")] [SerializeField] private VoidEventChannelSO _activationChannel; [Header("乘客检测")] [SerializeField] private BoxCollider2D _passengerSensor; // isTrigger,检测乘客 [SerializeField] private LayerMask _passengerLayer; // 应包含 Player + Enemy 层 private Rigidbody2D _rb; private readonly List _passengers = new(); private readonly List _passengerSnapshot = new(); // 迭代快照,防并发修改 private int _waypointIndex; private bool _movingForward = true; private bool _triggered; private bool _waiting; private Vector2 _frameVelocity; // 本帧实际速度,供离台时继承 private WaitForSeconds _waitForEndpoint; private readonly CompositeDisposable _subs = new(); private void Awake() { _rb = GetComponent(); _rb.bodyType = RigidbodyType2D.Kinematic; _rb.interpolation = RigidbodyInterpolation2D.Interpolate; _waitForEndpoint = new WaitForSeconds(_waitAtEndpoint); } private void OnEnable() { _activationChannel?.Subscribe(OnTriggered).AddTo(_subs); } private void OnDisable() { _subs.Clear(); } private void FixedUpdate() { if (_wayPoints == null || _wayPoints.Length == 0) { _frameVelocity = Vector2.zero; return; } if (_moveType == MoveType.TriggeredLinear && !_triggered) { _frameVelocity = Vector2.zero; return; } if (_waiting) { _frameVelocity = Vector2.zero; return; } MoveAndBroadcast(); } private void MoveAndBroadcast() { var target = (Vector2)_wayPoints[_waypointIndex].position; var next = Vector2.MoveTowards(_rb.position, target, _speed * Time.fixedDeltaTime); // 计算期望 delta(在 MovePosition 执行前,不依赖物理步骤是否完成) Vector2 delta = next - _rb.position; _frameVelocity = delta / Time.fixedDeltaTime; // 先广播 delta,乘客在下一个 FixedUpdate(-200) 消费 _passengerSnapshot.Clear(); _passengerSnapshot.AddRange(_passengers); foreach (var r in _passengerSnapshot) r.SetPlatformDelta(delta); _rb.MovePosition(next); if (Vector2.Distance(_rb.position, target) < 0.02f) StartCoroutine(WaitAndAdvance()); } private IEnumerator WaitAndAdvance() { _waiting = true; yield return _waitForEndpoint; AdvanceWaypoint(); _waiting = false; } private void AdvanceWaypoint() { if (_moveType == MoveType.TriggeredLinear) { _waypointIndex = Mathf.Min(_waypointIndex + 1, _wayPoints.Length - 1); if (_waypointIndex == _wayPoints.Length - 1) _triggered = false; return; } if (_moveType == MoveType.LinearAB) { _movingForward = !_movingForward; _waypointIndex = _movingForward ? 1 : 0; } else // WayPoints { _waypointIndex = (_waypointIndex + 1) % _wayPoints.Length; } } private void OnTriggered() => _triggered = true; // ── Passenger Detection ─────────────────────────────────────────────── private void OnTriggerEnter2D(Collider2D other) { if (!IsPassengerLayer(other)) return; if (!other.TryGetComponent(out var receiver)) return; if (!_passengers.Contains(receiver)) _passengers.Add(receiver); } private void OnTriggerExit2D(Collider2D other) { if (!other.TryGetComponent(out var receiver)) return; if (!_passengers.Remove(receiver)) return; // 继承平台速度,防止离台时速度骤变卡顿 receiver.OnLeavePlatform(_frameVelocity); } private bool IsPassengerLayer(Collider2D col) => (_passengerLayer.value & (1 << col.gameObject.layer)) != 0; #if UNITY_EDITOR private void OnDrawGizmos() { if (_wayPoints == null || _wayPoints.Length < 2) return; Gizmos.color = new Color(1f, 0.8f, 0f, 0.8f); for (int i = 0; i < _wayPoints.Length - 1; i++) { if (_wayPoints[i] != null && _wayPoints[i + 1] != null) Gizmos.DrawLine(_wayPoints[i].position, _wayPoints[i + 1].position); } } #endif } }