PlayerMovement:新增 _facingLocked 字段 + LockFacing(bool) 方法;UpdateFacing() 锁定时直接返回 WallSlideState:OnStateEnter 调用 LockFacing(true) + FlipFacing(_wallDir);OnStateExit 调用 LockFacing(false) 解锁 WallJumpState:OnStateEnter 保险性再调一次 LockFacing(false);WallJumpAway/Toward 同步写入 _inputVelocityX,确保解锁后 UpdateFacing 朝向正确(背墙跳 = 离墙方向,对墙跳 = 朝墙方向)
108 lines
3.7 KiB
C#
108 lines
3.7 KiB
C#
using System.Collections.Generic;
|
||
using BaseGames.Core.Events;
|
||
using BaseGames.Input;
|
||
using UnityEngine;
|
||
|
||
namespace BaseGames.World
|
||
{
|
||
/// <summary>
|
||
/// 挂在 Player 上,检测附近可交互物,驱动 UI 提示显示/隐藏。
|
||
/// 通过 InputReaderSO.InteractEvent 绑定交互输入。
|
||
/// </summary>
|
||
public class InteractableDetector : MonoBehaviour
|
||
{
|
||
[SerializeField] private float _detectRadius = 1.5f;
|
||
[SerializeField] private LayerMask _interactableLayer;
|
||
[SerializeField] private InputReaderSO _inputReader;
|
||
[SerializeField] private StringEventChannelSO _onShowInteractPrompt;
|
||
[SerializeField] private VoidEventChannelSO _onHideInteractPrompt;
|
||
|
||
private IInteractable _nearest;
|
||
private IInteractable _previousNearest;
|
||
|
||
// 预分配检测缓冲区,避免 OverlapCircleAll 每帧 GC 分配
|
||
private readonly Collider2D[] _overlapBuffer = new Collider2D[16];
|
||
|
||
// Collider → IInteractable 缓存,避免 FindNearest 每帧重复 GetComponentInParent
|
||
private readonly Dictionary<Collider2D, IInteractable> _componentCache = new();
|
||
|
||
private void OnEnable()
|
||
{
|
||
_inputReader.InteractEvent += TryInteract;
|
||
}
|
||
|
||
private void OnDisable()
|
||
{
|
||
_inputReader.InteractEvent -= TryInteract;
|
||
_componentCache.Clear(); // 清理缓存,防止跨场景持有旧引用
|
||
}
|
||
|
||
private void Update()
|
||
{
|
||
int count = Physics2D.OverlapCircleNonAlloc(
|
||
transform.position, _detectRadius, _overlapBuffer, _interactableLayer);
|
||
_nearest = FindNearest(_overlapBuffer, count);
|
||
|
||
if (_nearest != _previousNearest)
|
||
{
|
||
if (_previousNearest != null)
|
||
{
|
||
_previousNearest.OnPlayerExitRange();
|
||
_onHideInteractPrompt?.Raise();
|
||
}
|
||
|
||
if (_nearest != null)
|
||
{
|
||
_nearest.OnPlayerEnterRange(transform);
|
||
_onShowInteractPrompt?.Raise(_nearest.InteractPrompt);
|
||
}
|
||
|
||
_previousNearest = _nearest;
|
||
}
|
||
}
|
||
|
||
private void TryInteract()
|
||
{
|
||
if (_nearest != null && _nearest.CanInteract)
|
||
_nearest.Interact(transform);
|
||
}
|
||
|
||
private IInteractable FindNearest(Collider2D[] hits, int count)
|
||
{
|
||
IInteractable best = null;
|
||
float bestSqrDist = float.MaxValue;
|
||
|
||
for (int i = 0; i < count; i++)
|
||
{
|
||
var col = hits[i];
|
||
if (col == null) continue;
|
||
|
||
// 查缓存,未命中时才调用 GetComponentInParent(避免每帧反射开销)
|
||
if (!_componentCache.TryGetValue(col, out var interactable))
|
||
{
|
||
interactable = col.GetComponentInParent<IInteractable>();
|
||
_componentCache[col] = interactable;
|
||
}
|
||
|
||
if (interactable == null || !interactable.CanInteract) continue;
|
||
|
||
// 用 sqrMagnitude 比较距离,省去 Distance 的 sqrt 开销
|
||
float sqrDist = ((Vector2)transform.position - (Vector2)col.transform.position).sqrMagnitude;
|
||
if (sqrDist < bestSqrDist)
|
||
{
|
||
bestSqrDist = sqrDist;
|
||
best = interactable;
|
||
}
|
||
}
|
||
|
||
return best;
|
||
}
|
||
|
||
private void OnDrawGizmosSelected()
|
||
{
|
||
Gizmos.color = Color.cyan;
|
||
Gizmos.DrawWireSphere(transform.position, _detectRadius);
|
||
}
|
||
}
|
||
}
|