chore: initial commit

This commit is contained in:
2026-05-08 11:04:00 +08:00
commit f55d2a57c3
6278 changed files with 866081 additions and 0 deletions

View File

@@ -0,0 +1,344 @@
# 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<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`)需新增两个方法支持导航提示:
```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 InspectorPlay 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 验证条件评估结果