# 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 entryNodeId : ID // 起始节点 } DataModel DialogueNode { nodeId : ID type : DialogueNodeType // Text / Choice / Condition / Action ────────────────────────────────── // type = Text: text : Optional // 支持本地化 Key portrait : Optional nextNodeId : Optional // null = 对话结束 // type = Choice: choices : Optional> // type = Condition: condition : Optional trueNodeId : Optional falseNodeId : Optional // type = Action: action : Optional nextNodeId : Optional } DataModel DialogueChoice { choiceText : String nextNodeId : ID availableCondition: Optional // null=始终显示 } ``` ### 2.2 对话条件类型 ``` DataModel DialogueCondition { conditionType : DialogueConditionType targetId : Optional value : Optional } ``` | 条件类型 | 说明 | |---------|------| | `HasAbility` | 玩家已解锁某能力 | | `HasItem` | 玩家持有某物品 | | `WorldFlagTrue` | 某世界标记为 true | | `QuestStageIs` | 某任务处于指定阶段 | | `BossDefeated` | 某 Boss 已被击败 | | `FirstVisit` | 玩家第一次和此 NPC 对话 | ### 2.3 对话动作类型 | 动作类型 | 说明 | |---------|------| | `SetWorldFlag` | 设置世界标记 | | `GiveItem` | 给予物品 | | `GiveGeo` | 给予 Geo | | `UnlockAbility` | 解锁能力 | | `AdvanceQuest` | 推进任务阶段 | | `PlayCutscene` | 播放过场动画 | --- ## 3. 世界标记系统 ### 3.1 世界标记规范 ``` WorldFlags: Map ``` - 存储于 `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 // NPC 在不同阶段的位置 dialogueVersions: List // 不同进程阶段的对话 } DataModel NPCLocationEntry { condition : Optional // 满足时位于此处(null=默认) roomId : ID position : Vector2 } DataModel NPCDialogueVersion { condition : Optional // 满足时使用此对话(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 rewardOnComplete: Optional } DataModel QuestStageData { stageIndex : Integer description : String completionCondition: DialogueCondition // 推进到下一阶段的条件 } DataModel QuestReward { geoAmount : Optional itemIds : List abilityId : Optional worldFlagsToSet : List } ``` --- ## 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` |