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

@@ -0,0 +1,141 @@
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using BaseGames.Core;
using BaseGames.Core.Interaction;
namespace BaseGames.UI
{
/// <summary>
/// 世界空间交互提示(按键图标 + 文字),挂在可交互物的子节点上,跟随物体显示。
///
/// 职责:
/// • 实现 <see cref="IInteractPromptView"/>,由玩家身上的交互探测器在进入/离开最近可交互物时驱动显隐。
/// • 显示按键图标Image+ 动作文本TMP_Text图标走 <see cref="IInputIconService"/>
/// 随设备切换 / 改键自动刷新(与全局一致)。
/// • 通过 <see cref="CanvasGroup"/> 做淡入 / 淡出。
///
/// 布置方式:作为可交互物(门 / 存档点 / 商店 / 传送点…)的子节点,
/// 用世界空间 Canvas 承载。气泡相对物体的位置 = 本节点的 Transform
/// 直接在场景里拖动本子节点即可单独微调每个物体的提示锚点。
/// </summary>
public sealed class WorldInteractPrompt : MonoBehaviour, IInteractPromptView
{
[Header("UI 引用")]
[Tooltip("淡入淡出用 CanvasGroup一般挂在本节点的 Canvas 上)。")]
[SerializeField] private CanvasGroup _canvasGroup;
[Tooltip("按键图标 Image。由 IInputIconService 按当前设备填充。")]
[SerializeField] private Image _keyIcon;
[Tooltip("提示文字 TMP_Text。显示 IInteractable.InteractPrompt 的当前值。")]
[SerializeField] private TMP_Text _labelText;
[Header("按键动作")]
[Tooltip("用于查询按键图标的输入动作名(与 InputReader 的 Action 对应)。")]
[SerializeField] private string _actionName = "Interact";
[Header("淡入淡出")]
[Tooltip("淡入持续时间。0 = 立即显示。")]
[SerializeField] [Min(0f)] private float _fadeInDuration = 0.12f;
[Tooltip("淡出持续时间。0 = 立即隐藏。")]
[SerializeField] [Min(0f)] private float _fadeOutDuration = 0.08f;
// ── 运行时状态 ────────────────────────────────────────────────────────
private IInputIconService _iconService;
private bool _visible;
private float _alpha;
// ── Lifecycle ─────────────────────────────────────────────────────────
private void Awake()
{
_alpha = 0f;
_visible = false;
ApplyAlpha(0f);
}
private void OnDisable()
{
UnsubscribeFromIconService();
_visible = false;
_alpha = 0f;
ApplyAlpha(0f);
}
private void Update()
{
// 完全隐藏且无需继续淡出时,跳过
if (!_visible && _alpha <= 0f) return;
float target = _visible ? 1f : 0f;
if (!Mathf.Approximately(_alpha, target))
{
float dur = _visible ? _fadeInDuration : _fadeOutDuration;
float speed = dur > 0f ? Time.unscaledDeltaTime / dur : 1f;
_alpha = Mathf.MoveTowards(_alpha, target, speed);
ApplyAlpha(_alpha);
}
}
// ── IInteractPromptView ───────────────────────────────────────────────
public void Show(string label)
{
if (_labelText != null)
_labelText.text = label;
// 延迟绑定:首次显示时获取服务(确保 ServiceLocator 已初始化)
if (_iconService == null)
{
_iconService = ServiceLocator.GetOrDefault<IInputIconService>();
if (_iconService != null)
_iconService.OnIconSetChanged += RefreshIcon;
}
RefreshIcon();
_visible = true;
}
public void Hide() => _visible = false;
// ── Icon ──────────────────────────────────────────────────────────────
/// <summary>设备切换或改键后刷新图标。由 IInputIconService.OnIconSetChanged 调用。</summary>
private void RefreshIcon()
{
if (_keyIcon == null || string.IsNullOrEmpty(_actionName)) return;
var sprite = _iconService?.GetActionIcon(_actionName);
if (sprite != null)
{
_keyIcon.sprite = sprite;
_keyIcon.enabled = true;
}
else
{
_keyIcon.enabled = false; // 找不到图标时隐藏图标格,避免错误占位图
}
}
private void UnsubscribeFromIconService()
{
if (_iconService != null)
{
_iconService.OnIconSetChanged -= RefreshIcon;
_iconService = null;
}
}
// ── 辅助 ──────────────────────────────────────────────────────────────
private void ApplyAlpha(float a)
{
if (_canvasGroup != null)
{
_canvasGroup.alpha = a;
// 提示纯展示,不接收交互;同时彻底关闭射线避免遮挡
_canvasGroup.interactable = false;
_canvasGroup.blocksRaycasts = false;
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 845b4e4cb1d22f54e8b0910d937650a8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: