using System.Collections.Generic; 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 InteractPromptEventChannelSO _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 _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(new InteractPromptEvent("Interact", _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(); _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); } } }