Files
zeling_v2/Assets/_Game/Scripts/World/MovingPlatform.cs

178 lines
6.5 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Collections;
using System.Collections.Generic;
using BaseGames.Core;
using BaseGames.Core.Events;
using UnityEngine;
namespace BaseGames.World
{
/// <summary>
/// 移动平台。Kinematic Rigidbody2D支持三种移动模式。
///
/// 乘客跟随Delta Position 方案,替代 SetParent
/// - PassengerSensorTrigger检测进入乘客<see cref="IPassengerReceiver"/>)。
/// - FixedUpdate(-300) 计算本帧期望位移 delta推送给所有乘客再执行 MovePosition。
/// - 乘客在自己的 FixedUpdate(-200) 最前端消费 delta_rb.position += delta。
/// - 乘客离台时通过 OnLeavePlatform 继承平台速度,避免卡顿。
///
/// 此方案不修改 Transform 层级,与动画/摄像机/HitBox 系统完全兼容。
/// </summary>
[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<IPassengerReceiver> _passengers = new();
private readonly List<IPassengerReceiver> _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<Rigidbody2D>();
_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<IPassengerReceiver>(out var receiver)) return;
if (!_passengers.Contains(receiver))
_passengers.Add(receiver);
}
private void OnTriggerExit2D(Collider2D other)
{
if (!other.TryGetComponent<IPassengerReceiver>(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
}
}