249 lines
7.0 KiB
Markdown
249 lines
7.0 KiB
Markdown
# 14 · 叙事系统规范
|
||
|
||
> **所属文档集** [← 返回索引](./README.md)
|
||
> **摘要**:对话系统、世界标记、NPC 状态管理与三结局触发逻辑。
|
||
|
||
---
|
||
|
||
## 目录
|
||
|
||
1. [叙事设计原则](#1-叙事设计原则)
|
||
2. [对话系统](#2-对话系统)
|
||
3. [世界标记系统](#3-世界标记系统)
|
||
4. [NPC 状态管理](#4-npc-状态管理)
|
||
5. [任务系统](#5-任务系统)
|
||
6. [结局触发系统](#6-结局触发系统)
|
||
7. [叙事事件目录](#7-叙事事件目录)
|
||
|
||
---
|
||
|
||
## 1. 叙事设计原则
|
||
|
||
| 原则 | 说明 |
|
||
|------|------|
|
||
| **环境叙事优先** | 通过场景、图鉴和道具描述讲述故事,减少强制对话 |
|
||
| **非线性探索** | 玩家可按任意顺序解锁叙事内容 |
|
||
| **状态感知** | NPC 的对话随玩家进程改变(不重复同一句话)|
|
||
| **数据驱动** | 所有对话树、条件和触发均为数据,不硬编码 |
|
||
|
||
---
|
||
|
||
## 2. 对话系统
|
||
|
||
### 2.1 对话数据模型
|
||
|
||
```
|
||
DataModel DialogueData {
|
||
dialogueId : ID
|
||
speakerId : ID // NPC ID 或 "player"
|
||
──────────────────────────────────
|
||
nodes : List<DialogueNode>
|
||
entryNodeId : ID // 起始节点
|
||
}
|
||
|
||
DataModel DialogueNode {
|
||
nodeId : ID
|
||
type : DialogueNodeType // Text / Choice / Condition / Action
|
||
──────────────────────────────────
|
||
// type = Text:
|
||
text : Optional<String> // 支持本地化 Key
|
||
portrait : Optional<PortraitID>
|
||
nextNodeId : Optional<ID> // null = 对话结束
|
||
|
||
// type = Choice:
|
||
choices : Optional<List<DialogueChoice>>
|
||
|
||
// type = Condition:
|
||
condition : Optional<DialogueCondition>
|
||
trueNodeId : Optional<ID>
|
||
falseNodeId : Optional<ID>
|
||
|
||
// type = Action:
|
||
action : Optional<DialogueAction>
|
||
nextNodeId : Optional<ID>
|
||
}
|
||
|
||
DataModel DialogueChoice {
|
||
choiceText : String
|
||
nextNodeId : ID
|
||
availableCondition: Optional<DialogueCondition> // null=始终显示
|
||
}
|
||
```
|
||
|
||
### 2.2 对话条件类型
|
||
|
||
```
|
||
DataModel DialogueCondition {
|
||
conditionType : DialogueConditionType
|
||
targetId : Optional<ID>
|
||
value : Optional<Number>
|
||
}
|
||
```
|
||
|
||
| 条件类型 | 说明 |
|
||
|---------|------|
|
||
| `HasAbility` | 玩家已解锁某能力 |
|
||
| `HasItem` | 玩家持有某物品 |
|
||
| `WorldFlagTrue` | 某世界标记为 true |
|
||
| `QuestStageIs` | 某任务处于指定阶段 |
|
||
| `BossDefeated` | 某 Boss 已被击败 |
|
||
| `FirstVisit` | 玩家第一次和此 NPC 对话 |
|
||
|
||
### 2.3 对话动作类型
|
||
|
||
| 动作类型 | 说明 |
|
||
|---------|------|
|
||
| `SetWorldFlag` | 设置世界标记 |
|
||
| `GiveItem` | 给予物品 |
|
||
| `GiveGeo` | 给予 Geo |
|
||
| `UnlockAbility` | 解锁能力 |
|
||
| `AdvanceQuest` | 推进任务阶段 |
|
||
| `PlayCutscene` | 播放过场动画 |
|
||
|
||
---
|
||
|
||
## 3. 世界标记系统
|
||
|
||
### 3.1 世界标记规范
|
||
|
||
```
|
||
WorldFlags: Map<String, Boolean>
|
||
```
|
||
|
||
- 存储于 `ProgressionSaveData.worldFlags`
|
||
- Key 为字符串,建议命名格式:`区域_事件_描述`(如 `region1_boss_firstEncounter`)
|
||
- 标记只能设为 true,不支持撤销(设计保证单向性)
|
||
- 叙事系统、门控系统、AI 系统均可读取世界标记
|
||
|
||
### 3.2 设置世界标记的来源
|
||
|
||
| 来源 | 说明 |
|
||
|------|------|
|
||
| 对话动作 `SetWorldFlag` | 对话选择/到达某节点时触发 |
|
||
| 门控开启 | 某些门控开启时自动设置 |
|
||
| Boss 死亡 | OnBossDefeated 事件触发时自动设置 |
|
||
| 收集品拾取 | 特定关键收集品触发 |
|
||
| 手动触发器 | 场景中的触发区域触碰时 |
|
||
|
||
---
|
||
|
||
## 4. NPC 状态管理
|
||
|
||
### 4.1 NPC 数据模型
|
||
|
||
```
|
||
DataModel NPCData {
|
||
npcId : ID
|
||
displayName : String
|
||
──────────────────────────────────
|
||
locations : List<NPCLocationEntry> // NPC 在不同阶段的位置
|
||
dialogueVersions: List<NPCDialogueVersion> // 不同进程阶段的对话
|
||
}
|
||
|
||
DataModel NPCLocationEntry {
|
||
condition : Optional<DialogueCondition> // 满足时位于此处(null=默认)
|
||
roomId : ID
|
||
position : Vector2
|
||
}
|
||
|
||
DataModel NPCDialogueVersion {
|
||
condition : Optional<DialogueCondition> // 满足时使用此对话(null=默认)
|
||
dialogueId : ID
|
||
priority : Integer // 多个条件同时满足时取最高优先级
|
||
}
|
||
```
|
||
|
||
### 4.2 对话版本选取规则
|
||
|
||
```
|
||
玩家与 NPC 交互时:
|
||
|
||
枚举 NPCData.dialogueVersions(按 priority 降序)
|
||
→ 找第一个 condition 满足(或 condition 为 null)的版本
|
||
→ 使用该版本的 dialogueId 开始对话
|
||
|
||
若无任何版本满足 → 使用默认兜底对话("......")
|
||
```
|
||
|
||
---
|
||
|
||
## 5. 任务系统
|
||
|
||
### 5.1 任务数据模型
|
||
|
||
```
|
||
DataModel QuestData {
|
||
questId : ID
|
||
displayName : String
|
||
description : String
|
||
──────────────────────────────────
|
||
stages : List<QuestStageData>
|
||
rewardOnComplete: Optional<QuestReward>
|
||
}
|
||
|
||
DataModel QuestStageData {
|
||
stageIndex : Integer
|
||
description : String
|
||
completionCondition: DialogueCondition // 推进到下一阶段的条件
|
||
}
|
||
|
||
DataModel QuestReward {
|
||
geoAmount : Optional<Integer>
|
||
itemIds : List<ID>
|
||
abilityId : Optional<ID>
|
||
worldFlagsToSet : List<String>
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 6. 结局触发系统
|
||
|
||
### 6.1 结局条件回顾
|
||
|
||
(详细条件见 [09_ProgressionSystem.md](09_ProgressionSystem.md) 第 5 节)
|
||
|
||
| 结局 | 类型 | 核心条件 |
|
||
|------|------|---------|
|
||
| 结局 A | 普通 | 击败最终 Boss |
|
||
| 结局 B | 完全 | Boss 全清 + 主线剧情完整 + 关键 NPC 任务 |
|
||
| 结局 C | 隐藏 | 结局 B + 隐藏世界标记序列激活 |
|
||
|
||
### 6.2 结局判定流程
|
||
|
||
```
|
||
最终 Boss 死亡时:
|
||
|
||
检查结局 C 条件(最严格)
|
||
├─ 满足 → 触发结局 C
|
||
└─ 不满足 →
|
||
|
||
检查结局 B 条件
|
||
├─ 满足 → 触发结局 B
|
||
└─ 不满足 → 触发结局 A
|
||
|
||
触发结局 X:
|
||
→ 发出 OnEndingTriggered 事件(endingType)
|
||
→ 播放对应过场动画序列
|
||
→ 记录到 SaveData(endingUnlocked)
|
||
→ 游戏结束流程
|
||
```
|
||
|
||
---
|
||
|
||
## 7. 叙事事件目录
|
||
|
||
| 事件 | 触发时机 | 载荷 |
|
||
|------|---------|------|
|
||
| `OnDialogueStarted` | 开始对话 | `dialogueId, npcId` |
|
||
| `OnDialogueNodeReached` | 到达对话节点 | `nodeId` |
|
||
| `OnDialogueChoiceMade` | 玩家做出选择 | `choiceIndex, nextNodeId` |
|
||
| `OnDialogueEnded` | 对话结束 | `dialogueId` |
|
||
| `OnWorldFlagSet` | 世界标记变更 | `flagKey` |
|
||
| `OnQuestStarted` | 任务开始 | `questId` |
|
||
| `OnQuestAdvanced` | 任务推进 | `questId, newStage` |
|
||
| `OnQuestCompleted` | 任务完成 | `questId` |
|
||
| `OnEndingTriggered` | 结局触发 | `endingType` |
|
||
| `OnCutsceneStarted` | 过场动画开始 | `cutsceneId` |
|
||
| `OnCutsceneEnded` | 过场动画结束 | `cutsceneId` |
|