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; private void OnEnable() => _inputReader.InteractEvent += TryInteract; private void OnDisable() => _inputReader.InteractEvent -= TryInteract; private void Update() { var hits = Physics2D.OverlapCircleAll(transform.position, _detectRadius, _interactableLayer); _nearest = FindNearest(hits); 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) { IInteractable best = null; float bestDist = float.MaxValue; foreach (var col in hits) { 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); } } }