# 36 · 导航辅助系统(Navigation Hint System) > **命名空间** `BaseGames.Navigation` > **所属文档集** [← 返回索引](./README.md) · [总览](./00_Overview.md) > **依赖** `BaseGames.Core.Events` · `BaseGames.World`(地图/场景系统)· `BaseGames.Progression`(能力/进程状态) --- ## 目录 1. [系统总览](#1-系统总览) 2. [NavHintSO — 路标数据](#2-navhintso--路标数据) 3. [WorldMarker — 世界路标组件](#3-worldmarker--世界路标组件) 4. [HintNPC — NPC 指引系统](#4-hintnpc--npc-指引系统) 5. [BreadcrumbTracker — 面包屑导航](#5-breadcrumbtracker--面包屑导航) 6. [地图图钉联动](#6-地图图钉联动) 7. [SaveData 集成](#7-savedata-集成) 8. [事件频道](#8-事件频道) 9. [编辑器友好设计](#9-编辑器友好设计) --- ## 1. 系统总览 大型银河恶魔城地图容易让玩家迷路。导航辅助系统通过三种手段减少无效探索:**世界路标**(场景中放置的方向指引)、**NPC 指引**(NPC 可以告诉玩家下一步去哪)、**面包屑追踪**(自动记录未探索的出口/门)。 ``` 导航辅助系统职责: ├─ NavHintSO → 路标数据 SO(触发条件 + 指引文本 + 目标房间) ├─ WorldMarker → 场景内路标物件(根据进程显示/隐藏方向指示) ├─ HintNPC → NPC 的"去哪里"指引对话(与 DialogueSystem 集成) └─ BreadcrumbTracker → 追踪玩家发现过但未进入的门/通道,在地图上标记 ``` **设计原则**:辅助,不强制。所有导航提示都是可选的,不阻断玩家自由探索。路标只有在"玩家卡住时"才有价值,不应该过于显眼影响探索乐趣。 --- ## 2. NavHintSO — 路标数据 ```csharp [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` 条件动态显示或隐藏: ```csharp 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`)的扩展,增加"下一步指引"对话集: ```csharp 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 — 面包屑导航 追踪玩家"见到但未进入"的门/通道,在地图上用特殊图标标记,提醒玩家"这里有一条路还没走过": ```csharp public class BreadcrumbTracker : MonoBehaviour { [Header("事件频道")] [SerializeField] StringEventChannelSO _onRoomEntered; // Key = doorId / portalId, Value = 目标场景名 readonly Dictionary _seenButUnvisited = new(); void OnEnable() => _onRoomEntered.OnEventRaised += HandleRoomEntered; void OnDisable() => _onRoomEntered.OnEventRaised -= HandleRoomEntered; /// /// 由 RoomTransition 在玩家*看见*门时调用(不需要进入)。 /// public void RegisterSeen(string doorId, string targetScene) { if (!SaveManager.Instance.HasVisitedRoom(targetScene)) _seenButUnvisited[doorId] = targetScene; } /// /// 玩家进入新场景后,从未访问列表中移除。 /// 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`)需新增两个方法支持导航提示: ```csharp // 在 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 集成 ```json "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 1. 选择场景中合适的位置创建空 GameObject,命名 `NavMarker_{描述}` 2. 挂载 `WorldMarker` 组件 3. 创建 `NavHintSO`(右键 `Assets/Data/Navigation/` → `Create → Navigation/NavHint`) 4. 配置条件(何时显示)和指引文本 5. 若需地图联动,填写 `targetSceneName` 和 `targetWorldPos` 6. Play Mode 中可通过 NavHintManager Inspector 验证条件评估结果