Refactor interaction prompt system to use world space prompts

- Removed the InteractPromptWidget from HUD and its references in HUDController.
- Introduced IInteractPromptView interface for world space interaction prompts.
- Implemented WorldInteractPrompt class to manage display of interaction prompts in world space.
- Updated InteractableDetector to handle showing/hiding of world space prompts based on player proximity to interactable objects.
- Created a new prefab for UI_WorldInteractPrompt to facilitate the new interaction prompt system.
This commit is contained in:
2026-06-10 14:14:08 +08:00
parent 32566020c7
commit a1f54b68e6
23 changed files with 6085 additions and 554 deletions

View File

@@ -1,21 +1,23 @@
using System.Collections.Generic;
using BaseGames.Core.Events;
using BaseGames.Core.Interaction;
using BaseGames.Input;
using UnityEngine;
namespace BaseGames.World
{
/// <summary>
/// 挂在 Player 上,检测附近可交互物,驱动 UI 提示显示/隐藏
/// 挂在 Player 上,检测附近可交互物,驱动其世界空间提示(<see cref="IInteractPromptView"/>)显隐
/// 通过 InputReaderSO.InteractEvent 绑定交互输入。
/// <para>
/// 提示表现是「每个交互物自带」的世界空间子节点(跟随物体、可单独配样式),
/// 本组件只负责「检测最近可交互物」并通知它显示/隐藏,不持有任何 UI。
/// </para>
/// </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;
[SerializeField] private float _detectRadius = 1.5f;
[SerializeField] private LayerMask _interactableLayer;
[SerializeField] private InputReaderSO _inputReader;
private IInteractable _nearest;
private IInteractable _previousNearest;
@@ -26,6 +28,9 @@ namespace BaseGames.World
// Collider → IInteractable 缓存,避免 FindNearest 每帧重复 GetComponentInParent
private readonly Dictionary<Collider2D, IInteractable> _componentCache = new();
// IInteractable → 其世界空间提示视图缓存(可能为 null表示该物体未配置提示
private readonly Dictionary<IInteractable, IInteractPromptView> _promptCache = new();
private void OnEnable()
{
_inputReader.InteractEvent += TryInteract;
@@ -34,7 +39,12 @@ namespace BaseGames.World
private void OnDisable()
{
_inputReader.InteractEvent -= TryInteract;
// 离场前隐藏当前提示,防止跨场景残留
ResolvePrompt(_previousNearest)?.Hide();
_componentCache.Clear(); // 清理缓存,防止跨场景持有旧引用
_promptCache.Clear();
_previousNearest = null;
_nearest = null;
}
private void Update()
@@ -48,13 +58,13 @@ namespace BaseGames.World
if (_previousNearest != null)
{
_previousNearest.OnPlayerExitRange();
_onHideInteractPrompt?.Raise();
ResolvePrompt(_previousNearest)?.Hide();
}
if (_nearest != null)
{
_nearest.OnPlayerEnterRange(transform);
_onShowInteractPrompt?.Raise(new InteractPromptEvent("Interact", _nearest.InteractPrompt));
ResolvePrompt(_nearest)?.Show(_nearest.InteractPrompt);
}
_previousNearest = _nearest;
@@ -98,6 +108,17 @@ namespace BaseGames.World
return best;
}
/// <summary>解析交互物的世界空间提示视图(在其子节点上查找一次并缓存;无则记 null。</summary>
private IInteractPromptView ResolvePrompt(IInteractable interactable)
{
if (interactable == null) return null;
if (_promptCache.TryGetValue(interactable, out var view)) return view;
view = (interactable as Component)?.GetComponentInChildren<IInteractPromptView>(true);
_promptCache[interactable] = view;
return view;
}
private void OnDrawGizmosSelected()
{
Gizmos.color = Color.cyan;