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

@@ -30,9 +30,7 @@ namespace BaseGames.UI.HUD
[Header("Form")]
[SerializeField] private Image[] _formIcons;
[Header("Interact Prompt")]
[Tooltip("独立 Widget 组件负责渲染图标+文本HUDController 仅保留引用供编辑器配置检查")]
[SerializeField] private InteractPromptWidget _interactPromptWidget;
// 交互提示已改为「每个交互物自带的世界空间提示」WorldInteractPrompt),不再由 HUD 承载。
[Header("Event Channels - Subscribe")]
[SerializeField] private IntEventChannelSO _onHPChanged;

View File

@@ -1,119 +0,0 @@
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using BaseGames.Core;
using BaseGames.Core.Events;
namespace BaseGames.UI.HUD
{
/// <summary>
/// 交互提示 Widget。
///
/// 职责:
/// • 订阅 InteractPromptEventChannelSO 显示/隐藏提示
/// • 显示按键图标Image+ 动作文本TMP_Text
/// • 监听 IInputIconService.OnIconSetChanged在设备切换或改键后自动刷新图标
///
/// 布置方式:放在 HUD Canvas 下,引用对应的事件频道 SO 资产。
/// 不依赖 HUDController可独立使用。
/// </summary>
public sealed class InteractPromptWidget : MonoBehaviour
{
[Header("UI 引用")]
[SerializeField] private Image _keyIcon;
[SerializeField] private TMP_Text _labelText;
[Tooltip("整个提示根节点,控制显示/隐藏")]
[SerializeField] private GameObject _root;
[Header("Event Channels")]
[SerializeField] private InteractPromptEventChannelSO _onShowPrompt;
[SerializeField] private VoidEventChannelSO _onHidePrompt;
// ── 运行时状态 ────────────────────────────────────────────────────────
private IInputIconService _iconService;
private string _currentActionName;
private readonly CompositeDisposable _subs = new();
// ── Lifecycle ─────────────────────────────────────────────────────────
private void OnEnable()
{
// ServiceLocator 可能在此组件 OnEnable 时尚未注册(执行顺序问题),
// 延迟到 ShowPrompt 首次调用时再获取,确保服务可用
_onShowPrompt?.Subscribe(ShowPrompt).AddTo(_subs);
_onHidePrompt?.Subscribe(HidePrompt).AddTo(_subs);
HidePrompt();
}
private void OnDisable()
{
_subs.Clear();
UnsubscribeFromIconService();
}
// ── Handlers ──────────────────────────────────────────────────────────
private void ShowPrompt(InteractPromptEvent evt)
{
_currentActionName = evt.ActionName;
// 延迟绑定:首次显示时获取服务(确保 ServiceLocator 已初始化)
if (_iconService == null)
{
_iconService = ServiceLocator.GetOrDefault<IInputIconService>();
if (_iconService != null)
_iconService.OnIconSetChanged += RefreshIcon;
}
if (_labelText != null)
_labelText.text = evt.LabelText;
RefreshIcon();
if (_root != null)
_root.SetActive(true);
else
gameObject.SetActive(true);
}
private void HidePrompt()
{
_currentActionName = null;
if (_root != null)
_root.SetActive(false);
else
gameObject.SetActive(false);
}
// ── Icon Refresh ──────────────────────────────────────────────────────
/// <summary>设备切换或改键后刷新图标。由 IInputIconService.OnIconSetChanged 调用。</summary>
private void RefreshIcon()
{
if (_keyIcon == null || string.IsNullOrEmpty(_currentActionName)) return;
var sprite = _iconService?.GetActionIcon(_currentActionName);
if (sprite != null)
{
_keyIcon.sprite = sprite;
_keyIcon.enabled = true;
}
else
{
// 找不到图标时隐藏图标格,避免显示错误占位图
_keyIcon.enabled = false;
}
}
private void UnsubscribeFromIconService()
{
if (_iconService != null)
{
_iconService.OnIconSetChanged -= RefreshIcon;
_iconService = null;
}
}
}
}

View File

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