12 KiB
12 KiB
36 · 导航辅助系统(Navigation Hint System)
命名空间
BaseGames.Navigation
所属文档集 ← 返回索引 · 总览
依赖BaseGames.Core.Events·BaseGames.World(地图/场景系统)·BaseGames.Progression(能力/进程状态)
目录
- 系统总览
- NavHintSO — 路标数据
- WorldMarker — 世界路标组件
- HintNPC — NPC 指引系统
- BreadcrumbTracker — 面包屑导航
- 地图图钉联动
- SaveData 集成
- 事件频道
- 编辑器友好设计
1. 系统总览
大型银河恶魔城地图容易让玩家迷路。导航辅助系统通过三种手段减少无效探索:世界路标(场景中放置的方向指引)、NPC 指引(NPC 可以告诉玩家下一步去哪)、面包屑追踪(自动记录未探索的出口/门)。
导航辅助系统职责:
├─ NavHintSO → 路标数据 SO(触发条件 + 指引文本 + 目标房间)
├─ WorldMarker → 场景内路标物件(根据进程显示/隐藏方向指示)
├─ HintNPC → NPC 的"去哪里"指引对话(与 DialogueSystem 集成)
└─ BreadcrumbTracker → 追踪玩家发现过但未进入的门/通道,在地图上标记
设计原则:辅助,不强制。所有导航提示都是可选的,不阻断玩家自由探索。路标只有在"玩家卡住时"才有价值,不应该过于显眼影响探索乐趣。
2. NavHintSO — 路标数据
[CreateAssetMenu(menuName = "Navigation/NavHint")]
public class NavHintSO : ScriptableObject
{
[Header("触发条件")]
[Tooltip("满足哪些条件时,这条路标才应该显示")]
public NavHintCondition[] showConditions; // 全部满足才显示(AND)
[Header("指引内容")]
[TextArea(1, 3)]
public string hintText; // 如"东边的岩洞深处有可以突破障碍的力量"
[Header("目标位置(可选)")]
public string targetSceneName; // 目标房间(留空=不绑定地图图钉)
public Vector2 targetWorldPos; // 目标世界坐标(用于地图图钉)
[Header("优先级")]
[Range(1, 5)]
public int priority = 3; // 1=最低;5=最高(多条提示时显示最高优先级)
}
NavHintCondition(内置类型)
| 条件类 | 参数 | 满足时机 |
|---|---|---|
HasAbilityCondition |
abilityType |
玩家拥有指定能力 |
BossDefeatedCondition |
bossId |
指定 Boss 已被击败 |
AreaEnteredCondition |
regionId |
玩家已进入指定区域 |
TimeInAreaCondition |
regionId, minMinutes |
玩家在某区域停留超过N分钟(防止新手卡关) |
AlwaysShowCondition |
— | 始终显示(调试/强制提示) |
DifficultyCondition |
maxDifficulty |
仅在简单模式及以下显示 |
3. WorldMarker — 世界路标组件
场景中放置的物理路标(石碑、箭头标记、光柱),根据 NavHintSO 条件动态显示或隐藏:
public class WorldMarker : MonoBehaviour
{
[Header("路标数据")]
[SerializeField] NavHintSO _hint;
[Header("显示控制")]
[SerializeField] GameObject _markerVisual; // 路标视觉(Sprite/粒子)
[SerializeField] string _markerId; // 唯一 ID,用于追踪玩家是否已见过
[Header("交互(可选)")]
[SerializeField] bool _interactable; // 玩家靠近可读取路标文字
[SerializeField] MMF_Player _interactFeedback;
bool _isVisible;
void Start() => EvaluateVisibility();
// 当进程变化时重新评估
void OnEnable()
{
NavHintManager.Instance.RegisterMarker(this);
EvaluateVisibility();
}
void OnDisable() => NavHintManager.Instance.UnregisterMarker(this);
public void EvaluateVisibility()
{
bool shouldShow = _hint != null &&
Array.TrueForAll(_hint.showConditions, c => c.IsMet());
if (_isVisible == shouldShow) return;
_isVisible = shouldShow;
_markerVisual.SetActive(shouldShow);
}
// IInteractable 实现(若 _interactable = true)
public void Interact()
{
if (_hint == null) return;
_interactFeedback?.PlayFeedbacks();
// 将 hintText 发送至 DialogueManager 显示(单行气泡)
DialogueManager.Instance.ShowHintBubble(_hint.hintText);
// 标记地图图钉
if (!string.IsNullOrEmpty(_hint.targetSceneName))
MapManager.Instance.SetHintPin(_markerId, _hint.targetSceneName, _hint.targetWorldPos);
}
}
4. HintNPC — NPC 指引系统
HintNPC 是 InteractableNPC(15_DialogueSystem.md)的扩展,增加"下一步指引"对话集:
public class HintNPC : InteractableNPC
{
[Header("导航指引")]
[SerializeField] NavHintSO[] _hints; // 按优先级顺序,取第一个条件满足的
protected override DialogueSequenceSO GetCurrentDialogue()
{
// 先检查常规进程对话(父类逻辑)
var baseDialogue = base.GetCurrentDialogue();
// 再检查导航提示(若有更高优先级)
NavHintSO bestHint = null;
foreach (var hint in _hints)
{
if (Array.TrueForAll(hint.showConditions, c => c.IsMet()))
{
if (bestHint == null || hint.priority > bestHint.priority)
bestHint = hint;
}
}
if (bestHint != null)
{
// 将 hintText 包装为临时 DialogueSequenceSO(或直接返回 baseDialogue + Append)
return WrapHintAsDialogue(bestHint);
}
return baseDialogue;
}
DialogueSequenceSO WrapHintAsDialogue(NavHintSO hint)
{
// 动态创建单行对话,内容来自 hint.hintText
// 同时在地图上标注提示位置
if (!string.IsNullOrEmpty(hint.targetSceneName))
MapManager.Instance.SetHintPin(npcId + "_hint", hint.targetSceneName, hint.targetWorldPos);
return DialogueSequenceSO.CreateRuntime(npcId, hint.hintText);
}
}
5. BreadcrumbTracker — 面包屑导航
追踪玩家"见到但未进入"的门/通道,在地图上用特殊图标标记,提醒玩家"这里有一条路还没走过":
public class BreadcrumbTracker : MonoBehaviour
{
[Header("事件频道")]
[SerializeField] StringEventChannelSO _onRoomEntered;
// Key = doorId / portalId, Value = 目标场景名
readonly Dictionary<string, string> _seenButUnvisited = new();
void OnEnable() => _onRoomEntered.OnEventRaised += HandleRoomEntered;
void OnDisable() => _onRoomEntered.OnEventRaised -= HandleRoomEntered;
/// <summary>
/// 由 RoomTransition 在玩家*看见*门时调用(不需要进入)。
/// </summary>
public void RegisterSeen(string doorId, string targetScene)
{
if (!SaveManager.Instance.HasVisitedRoom(targetScene))
_seenButUnvisited[doorId] = targetScene;
}
/// <summary>
/// 玩家进入新场景后,从未访问列表中移除。
/// </summary>
void HandleRoomEntered(string sceneName)
{
var toRemove = _seenButUnvisited
.Where(kv => kv.Value == sceneName)
.Select(kv => kv.Key)
.ToList();
foreach (var key in toRemove)
{
_seenButUnvisited.Remove(key);
MapManager.Instance.ClearBreadcrumb(key);
}
// 在地图上显示未访问的门标记
foreach (var kv in _seenButUnvisited)
MapManager.Instance.SetBreadcrumb(kv.Key, kv.Value);
SaveToData();
}
void SaveToData()
{
SaveManager.Instance.SetBreadcrumbs(_seenButUnvisited
.Select(kv => new BreadcrumbEntry { doorId = kv.Key, targetScene = kv.Value })
.ToList());
}
}
[Serializable]
public struct BreadcrumbEntry
{
public string doorId;
public string targetScene;
}
地图中的面包屑图标
地图系统(16_MapSystem.md)新增面包屑图标层:
- 面包屑标记:橙色问号图标,覆盖在房间出口位置
- 提示图钉:蓝色图钉图标,由
WorldMarker或HintNPC触发时放置 - 玩家进入目标房间后,对应标记自动消失
6. 地图图钉联动
MapManager(16_MapSystem.md)需新增两个方法支持导航提示:
// 在 MapManager 中新增
public void SetHintPin(string pinId, string targetScene, Vector2 worldPos)
{
// 在全屏地图对应位置放置蓝色图钉 Sprite
_hintPins[pinId] = new HintPinData { targetScene = targetScene, worldPos = worldPos };
RefreshMapUI();
}
public void SetBreadcrumb(string doorId, string targetScene)
{
_breadcrumbs[doorId] = targetScene;
RefreshMapUI(); // 在地图边缘显示橙色问号
}
public void ClearBreadcrumb(string doorId)
{
_breadcrumbs.Remove(doorId);
RefreshMapUI();
}
7. SaveData 集成
"navigation": {
"breadcrumbs": [
{ "doorId": "Door_Cave_East_01", "targetScene": "Room_Cave_03" },
{ "doorId": "Door_Ruins_North", "targetScene": "Room_Ruins_01" }
],
"hintPins": [
{ "pinId": "NPC_Elder_hint", "targetScene": "Room_Ruins_01", "worldPos": { "x": 25, "y": -8 } }
]
}
8. 事件频道
| 频道资产 | 类型 | 发布方 | 主要订阅方 |
|---|---|---|---|
OnNavHintActivated.asset |
StringEventChannelSO |
WorldMarker(交互时) |
MapManager(放置图钉) |
OnBreadcrumbAdded.asset |
StringEventChannelSO |
BreadcrumbTracker |
MapManager |
OnBreadcrumbCleared.asset |
StringEventChannelSO |
BreadcrumbTracker |
MapManager |
9. 编辑器友好设计
WorldMarker Gizmos
Scene 视图中 WorldMarker 显示:
- 绿色箭头:当前条件满足(路标可见)
- 灰色箭头:条件未满足(路标隐藏)
- 箭头指向
_hint.targetWorldPos方向
NavHintManager Inspector(Play Mode)
┌─ NavHintManager ──────────────────────────────────────┐
│ 激活路标: 2 / 5 │
│ ┌────────────────────────────────────────┐ │
│ │ WorldMarker_Forest_East ✅ 已显示 │ │
│ │ WorldMarker_Cave_Entrance ⬜ 条件未满足 │ │
│ │ └ AreaEnteredCondition: Ruins ✗ │ │
│ └────────────────────────────────────────┘ │
│ 面包屑: 3 个未访问门 │
│ ┌─────────────────────────────────┐ │
│ │ Door_Cave_East_01 → Room_Cave_03│ │
│ │ Door_Ruins_North → Room_Ruins_01│ │
│ └─────────────────────────────────┘ │
└───────────────────────────────────────────────────────┘
路标放置 SOP
- 选择场景中合适的位置创建空 GameObject,命名
NavMarker_{描述} - 挂载
WorldMarker组件 - 创建
NavHintSO(右键Assets/Data/Navigation/→Create → Navigation/NavHint) - 配置条件(何时显示)和指引文本
- 若需地图联动,填写
targetSceneName和targetWorldPos - Play Mode 中可通过 NavHintManager Inspector 验证条件评估结果