Files
zeling_v2/Assets/_Game/Scripts/World/InteractableDetector.cs
Joywayer e879efaa89 Add InputDeviceIconSetSO configuration guide and related documentation
- Created a new markdown file detailing the configuration of InputDeviceIconSetSO.
- Included sections on system architecture, field explanations, image specifications, and complete workflow from setup to runtime.
- Documented the automatic device recognition logic and provided troubleshooting for common issues.
- Added references to relevant files and scripts for easier navigation.
2026-05-23 00:10:23 +08:00

108 lines
3.8 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.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 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<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(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<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);
}
}
}