Files
zeling_v2/Docs/DesignSpec/14_NarrativeSystem.md
2026-05-08 11:04:00 +08:00

249 lines
7.0 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
→ 播放对应过场动画序列
→ 记录到 SaveDataendingUnlocked
→ 游戏结束流程
```
---
## 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` |