using BaseGames.Core.Events; using BaseGames.Input; using UnityEngine; namespace BaseGames.World { /// /// 挂在 Player 上,检测附近可交互物,驱动 UI 提示显示/隐藏。 /// 通过 InputReaderSO.InteractEvent 绑定交互输入。 /// 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]; private void OnEnable() => _inputReader.InteractEvent += TryInteract; private void OnDisable() => _inputReader.InteractEvent -= TryInteract; 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 bestDist = float.MaxValue; for (int i = 0; i < count; i++) { var col = hits[i]; var interactable = col.GetComponentInParent(); if (interactable == null || !interactable.CanInteract) continue; float dist = Vector2.Distance(transform.position, col.transform.position); if (dist < bestDist) { bestDist = dist; best = interactable; } } return best; } private void OnDrawGizmosSelected() { Gizmos.color = Color.cyan; Gizmos.DrawWireSphere(transform.position, _detectRadius); } } }