Files
ichni_Official/docs/story-programmer-integration-guide.md
SoulliesOfficial 810d019619 剧情+对话完善
2026-07-21 15:24:42 -04:00

534 lines
21 KiB
Markdown
Raw 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.
# ichni Official 剧情系统整合、配置与测试手册
> 适用对象程序员、Technical Designer、负责 Unity 配置和剧情 QA 的成员
> 适用项目:`ichni Official`
> 当前实现Yarn Spinner `3.2.1`、Unity Localization、ES3、DOTween
> 最后更新2026-07-21
本文说明如何把剧本编写者交付的 Yarn Scripts 整合进游戏,并配置 StoryData、角色、Timeline、Helper、存档和测试流程。CreatorStudio 不参与本套任务;只有未来任务明确触及节奏游戏元素、共享序列化结构或 Creator 导入流程时,才需要重新评估跨项目同步。
## 1. 系统边界与数据流
```text
StoryData静态剧情图、条件、Marker、默认变量
-> StoryTreeController生成 Block、推导状态、处理回滚
-> TextBlockView -> StoryDialogueController -> DialogueRunner
-> VNDialoguePresenter / VNPortraitStage / DialogUIPage
-> StoryProgress -> StorySaveModule -> ES3
-> SongBlockView -> SongSaves.isTried
-> TutorialBlockView -> TutorialFlowController
-> StoryTimelineControllerMarker 投影、进度、回滚入口)
-> StoryHelperController全局 HelperData、条件对话池
```
三套持久化数据必须保持隔离:
| 数据域 | 主要内容 | 回滚本章时是否变化 |
|---|---|---|
| `SongSaveModule` | `isTried``isCompleted`、Accuracy、MaxCombo、FC/AP、Chart Revision | 否 |
| `StorySaveModule` | Completed Blocks、章节变量、选项、Marker 快照 | 是,仅当前章节 |
| `UnlockSaveModule` | Offline 内容 Key | 否 |
## 2. 主要代码和资产
| 路径 | 职责 |
|---|---|
| `Assets/Scripts/NewStorySystem/Data/StoryData.cs` | 运行时章节数据与查询 |
| `Assets/Scripts/NewStorySystem/Data/StoryData.Editor.cs` | StoryData Validation 与 Inspector Preview |
| `Assets/Scripts/NewStorySystem/Data/StoryBlockDefinition.cs` | Block 类型、布局、条件和类型专属字段 |
| `Assets/Scripts/NewStorySystem/Data/StoryConditionNode.cs` | AND / OR / NOT / Variable / BlockCompleted 条件树 |
| `Assets/Scripts/NewStorySystem/Tree/StoryTreeController.cs` | 生成、状态推导、连接、完成、回滚 |
| `Assets/Scripts/NewStorySystem/Dialogue/StoryDialogueController.cs` | TextBlock 对话事务与 Yarn Project 切换 |
| `Assets/Scripts/NewStorySystem/Dialogue/VNDialoguePresenter.cs` | 台词、选项、快进、记录 |
| `Assets/Scripts/NewStorySystem/Dialogue/VNPortraitStage.cs` | 动态立绘实例和动画入口 |
| `Assets/Scripts/NewStorySystem/Save/StorySaveModule.cs` | 按章节 ES3 存档与事务 |
| `Assets/Scripts/NewStorySystem/Timeline/StoryTimelineController.cs` | Marker、刻度同步、进度和确认回滚 |
| `Assets/Scripts/NewStorySystem/Helper/StoryHelperController.cs` | 全局 Helper、加权随机台词和气泡 |
| `Assets/Scripts/NewStorySystem/YarnFunctions/` | 项目专有 Yarn Commands / Functions |
| `Assets/Story/Chapter0/` | Chapter 0 StoryData、Yarn Project、Yarn Scripts、CharacterRegistry |
| `Assets/Localization/Story/` | 当前 Yarn 台词 Unity String Table Collection |
## 3. 新章节整合流程
### 3.1 确定稳定 Chapter Index
`ChapterSelectionUnit.chapterIndex``StoryData.chapterIndex``StoryManager.storyDatas` 的 Dictionary Key 必须完全一致,包括大小写。
当前 Chapter 0 使用:
```text
Chapter0
```
`StoryTreeController.BuildChapter` 会在运行时报告三者不一致,但不会擅自改写资产。
### 3.2 创建章节目录和 Yarn Project
建议结构:
```text
Assets/Story/Chapter1/
Chapter1.yarnproject
StoryData_Chapter1.asset
CharacterRegistry.asset也可改为全局 Registry需统一引用
C1_Intro.yarn
C1_Checkpoint.yarn
...
```
Yarn Project 的 `Source Yarn Scripts` 建议使用 `**/*.yarn`并确保不会把其它章节目录意外包含进来。Node 名必须在该 Project 内唯一。
### 3.3 创建 StoryData
使用:
```text
Create > Ichni > Story > New > StoryData
```
配置:
1. `Chapter Index`:与章节资产完全一致。
2. `Yarn Project`:拖入本章节 Yarn Project。
3. `Initial Variables`:登记章节变量的默认值和类型。
4. `Blocks`:配置完整静态剧情图。
5. `Timeline Markers`:配置重要时间点。
6. `Exclusive Route Groups`:只用于 Validation登记互斥终点组。
最后在场景 `StoryManager.storyDatas` 中添加:
```text
Key: Chapter1
Value: StoryData_Chapter1
```
## 4. Initial Variables
每个变量定义包含:
- `Key`
- `Type`Bool / Int / Float / String
- 对应类型的 `Default`
默认值不写入玩家存档。只有 Yarn 或其它剧情入口第一次写入同名变量时,才产生存档覆盖值。`remove_variable` 会删除覆盖值,使读取重新回到 StoryData 默认值。
建议所有 Yarn 会读写的变量都在 `Initial Variables` 登记,即使运行时允许首次写入未声明变量。这样能让策划、程序员和 Validation 审阅时看到章节完整变量表。
## 5. Block 通用配置
### 5.1 Identity 与 Layout
- `ID`:稳定唯一,连接、存档、选项和 Marker 都依赖它。
- `Type`Text / Song / Tutorial。
- `Column`:叙事时间向右推进;列坐标代表 Block 中心点。
- `Row`:路线位置,正数向下、负数向上。
运行时默认步距:
- `columnStep = 720`
- `rowStep = 525`
- `helperReservedLeftSpace = 400`
Prefab 自己决定尺寸,控制器不覆盖 `sizeDelta`
| Block | 当前设计尺寸 |
|---|---|
| Important Text | `600 x 375` |
| Secondary Text | `400 x 200` |
| Song | `400 x 100` |
| Tutorial | `400 x 100` |
如果未来修改 Prefab 尺寸,还要同步 `StoryData.Editor.cs` 中的 Preview 尺寸常量。
### 5.2 Next Blocks 与 Connected Rule
`Next Blocks` 同时承担:
- 运行时连接线。
- 目标 Block 的自动前置条件来源。
目标 Block 的 `Connected Rule`
| 规则 | 语义 |
|---|---|
| `Require All Direct Predecessors` | 所有直接前置完成后可用;默认值 |
| `Require Any Direct Predecessor` | 任一直接前置完成即可;用于互斥分支汇合 |
| `Ignore Connections` | 连线只作视觉用途,不参与解锁 |
自动连接条件与手写 `Unlock Condition` 按 AND 合并。没有前置连接的入口 Block 不受自动条件限制。
### 5.3 Unlock 与 Forbidden
两者共用 `StoryCondition`
- `All Of (AND)`
- `Any Of (OR)`
- `Not`
- `Block Completed`
- `Variable Compare`
状态优先级:
```text
Forbidden > Completed > Current > Locked
```
互斥路线应同时配置 Unlock 和 Forbidden。例如
```text
Route A Unlock: c0_signal_route == 1
Route A Forbidden: c0_signal_route == 2
Route B Unlock: c0_signal_route == 2
Route B Forbidden: c0_signal_route == 1
```
不要为相同语义再增加一套 Route Binding 字段;运行时路线始终以通用条件树为准。
## 6. TextBlock 配置
字段:
- `Yarn Node`:点击时启动的入口 Node。
- `Title Key`Block 标题的 Unity Localization Entry Key。
- `Importance`Important / Secondary。
当前限制:`TextBlockView` 仍直接把 `titleKey` 字符串显示到 TMP没有解析 Unity Localization。这是发布前本地化待办配置时仍应从现在开始填写正式 Key避免后续迁移时重新清点内容。
TextBlock 运行流程:
1. `StoryDialogueController` 检查 Runner、章节上下文和 Yarn Node 是否存在。
2. 复制 `ChapterStorySave` 并开始事务。
3. `StoryProgress.BeginBlockMutation` 捕获同列 Marker 快照。
4. Yarn 运行,选项和变量只修改内存。
5. 正常结束:完成 Block、提交 ES3、执行排队的解锁/Message。
6. Back 或启动失败:恢复进入前快照并刷新现有 StoryTree。
## 7. SongBlock 配置
字段:
- `Song ID`:必须等于目标 `SongItemData.songName`,不能使用 `displaySongName`
- `Illustration Y`:范围 `-74.25``74.25`
插画和曲名资料由 `ChapterSelectionUnit` 中的 `SongItemData` 提供,不在 StoryData 重复维护。
完成规则:
- 玩家确认进入目标歌曲时,`InformationTransistor.PrepareGameLaunch` 调用 `SongSaveModule.MarkSongTried`
- `SongStatusSave.isTried = true` 是 SongBlock 的唯一持久化完成事实。
- 即使玩家进入后中途退出,也算尝试并完成对应 SongBlock。
- Timeline 回滚不会把 `isTried` 改回 false重新构建剧情树会再次把该 SongBlock 同步为完成。
同时仍要通过 `UnlockSaveModule.CanEnterSong` 检查章节和歌曲授权SongBlock 不能绕过普通选曲的锁定规则。
## 8. TutorialBlock 配置
字段:
- `Display Name`
- `Tutorial Key``TutorialCollection` 的稳定 Key。
- `Progress Variable`:玩家选择游玩或跳过后写入 `true` 的章节变量。
- `Difficulty Save ID`:目标 `DifficultyData.saveDifficultyId`,不是列表下标。
玩家每次点击 Completed TutorialBlock 都可以重新选择游玩或跳过。第一次选择任一项时会立刻完成 TutorialBlock 并解锁后续;游玩教程不等待结算。
当前 `TutorialFlowController` 的“游玩教程/跳过教程”文本仍是硬编码中文,发布前需要迁移到 Unity Localization。
## 9. Timeline Marker
每个 Marker 配置:
- `Marker ID`:章节内唯一。
- `Anchor`:必须是 TextBlock ID。
- `Label Key`:时间或特殊叙事文本的 Unity Localization Key。
- `Main Progress`:是否参与主线百分比。
- `Value`:完成 Anchor 后贡献的 `0..1` 进度;互斥结局都可以是 `1`
显示规则:
- Anchor 为 `Current``Completed`:显示。
- Anchor 为 `Locked``Forbidden`:隐藏。
位置直接读取 Anchor TextBlock 的实际视觉中心,并监听 Story ScrollRect装饰刻度使用一张 `Image Type = Tiled` 的 Repeat Image 跟随同一横向位移。
点击 Marker 只有在对应“列之前快照”已经创建后才可交互。确认回滚会重置目标列及右侧的 Block、选项和章节变量并删除失效的后续 Marker 快照。
当前 Timeline 回滚确认框仍有硬编码中文;发布前需要迁移到 Unity Localization。
## 10. Exclusive Route Groups
该列表只用于编辑器 Validation不参与 Runtime。
一个 Group 应包含同一章节中互斥的两个或多个终点 Block。Validation 会检查:
- Group ID 是否有效和重复。
- 终点 Block 是否存在和重复。
- 终点是否仍有 Next Block。
- 互斥终点是否配置了 Forbidden Condition。
通用条件树可以表达任意复杂条件Validation 无法数学证明两条路线真正互斥,最终仍需人工测试。
## 11. Yarn 与 Unity Localization
所有新剧情文本只使用 Unity Localization。
### 11.1 Yarn Project Inspector
选中 `.yarnproject` 后检查:
1. `Source Yarn Scripts` 覆盖正确目录。
2. `Base Language` 与 Yarn 源文本语言一致。
3. 开启 `Use Unity Localisation System`
4. `String Table Collection` 指向目标 Unity String Table Collection。
5. 点击 `Add Line Tags to Yarn Scripts`,为缺少 ID 的台词和选项生成 `#line:`
6. 保存并重新导入,确认 Yarn Project 没有编译错误。
当前 Chapter 0 使用 `Chapter0_Lines`,而 `StoryDialogueController` 已预留统一 `yarnLinesTable`。在 Chapter 1 大量接入前,推荐把各章节 Yarn 台词统一到一个 `YarnLines` String Table Collection这样场景中的单个 `UnityLocalisedLineProvider` 在切换 Yarn Project 时无需同时切换表。
若暂时保留 `Chapter0_Lines`
- `UnityLocalisedLineProvider.stringsTable` 必须指向 `Chapter0_Lines`
- `StoryDialogueController.yarnLinesTable` 留空时不会覆盖现有 Provider 配置。
### 11.2 Locale
首发至少验证:
- English`en`
- Simplified Chinese`zh-CN`
- Japanese`ja`
- Korean`ko`
- Vietnamese`vi-VN`
新增 Yarn 行后,确认五张 String Table 都有同一 Line ID。不要通过删除并重新生成 Line Tag 的方式“整理”ID否则会同时影响本地化和已保存选项。
### 11.3 非 Yarn 文本
建议按用途拆表:
| 内容 | 建议 String Table |
|---|---|
| Yarn 台词和选项 | `YarnLines` |
| TextBlock 标题、Timeline、章节标题 | `StoryUI` |
| Helper 名称和气泡 | `StoryHelper` |
| MessageBox / SelectionBox | `Message` |
当前场景中 `StoryTimelineController.localizationTable``StoryHelperData.localizationTable` 仍为空;完成正式文本接入时必须补齐。
## 12. 角色与立绘配置
### 12.1 CharacterData
创建:
```text
Create > Ichni > Story > New > CharacterData
```
字段:
- `Character ID`:与 Yarn 的 `CharacterName` 完全一致,例如 `Asahi`
- `Display Name Key`:角色显示名的 Unity Localization Key。
- `Portrait Style`Sprite / Live2D / Spine。
- `Position Offset`:归一化舞台偏移。
- `Emotion Sprites``normal``happy``angry` 等表情到 Sprite 的映射。
- `Dynamic Portrait Prefab`:未来 Live2D / Spine 使用,必须实现 `IPortraitRenderer`
把 CharacterData 登记到 `CharacterRegistry.characters`,再把 Registry 赋给场景 `StoryManager.characterRegistry`
当前限制:`VNDialoguePresenter` 的 Speaker TMP 仍直接显示 Yarn 的 `CharacterName`,尚未通过 `CharacterData.displayNameKey` 本地化。该字段先按规范配置,发布前应补上显示名解析。
### 12.2 VNPortraitStage
场景需配置:
- `_spritePortraitPrefab`
- `_portraitContainer`
Sprite Prefab 必须包含 `SpritePortraitRenderer`Live2D / Spine Prefab 必须有实现 `IPortraitRenderer` 的组件。
当前舞台按需 Instantiate`hide_portrait` 时 Destroy。对话数量增大后若发现频繁分配应再评估池化现在不要为了未知规模提前重构。
## 13. Dialog 场景装配
`StoryDialogueRoot` 应包含:
- `DialogueRunner`
- `UnityLocalisedLineProvider`
- `VNDialoguePresenter`
- `StoryDialogueController`
`StoryDialogueController`
- `dialogueRunner`
- `storyUIPage`
- 可选的统一 `yarnLinesTable`
`DialogUIPage`
- `portraitStage`
- `dialogueText`
- `speakerContainer` / `speakerText`
- `advanceButton`
- `fastForwardButton`
- `backButton`
- `showHistoryButton`
- `dialogueHistoryPage`
- `choiceFrame`
- `choiceButtons`
`DialogueHistoryPage` 必须有全屏可接收 Raycast 的覆盖层;打开时要阻断底层 Dialog 输入。
当前 `MenuScene` 仍启用了调试用 `ConsoleLinePresenter`。它会额外把每行本地化结果输出到 Console发布构建前应从 DialogueRunner Presenter 列表移除或禁用,只保留 `VNDialoguePresenter`
## 14. Helper 配置
Helper 使用全局 `StoryHelperData`,不属于单个 StoryData。
字段:
- `Helper ID`
- `Portrait Sprite`
- `Localization Table`
- `Display Name Key`
- `Fallback Dialogue Key`
- `Dialogue Pool`
每条候选包含:
- `ID`
- `Text Key`
- `Availability`:复用 StoryCondition留空时始终可用。
- `Weight`
运行时会从满足条件的候选中加权随机抽取,并尽量避免连续重复。没有候选时使用 Fallback。`StoryHelperController` 会动态实例化独立 `HelperTalk` Prefab不使用 MessageBox。
当前 `Assets/Story/StoryHelperData.asset` 的 Localization Table 为空且 Dialogue Pool 为空,只有 Fallback Key正式 Helper 文本测试前需要配置对应 String Table 和至少一条候选。
## 15. StoryData Validation
在 StoryData Inspector 点击 `Validate Story Data`。Console 现在会输出:
- 一条摘要。
- 每个问题一条独立 Warning 或 Error。
不要再把整份报告拼到同一条多行日志Unity Console 折叠行不会按内嵌换行扩展高度,会造成文字重叠。
当前 Validation 检查:
- Chapter Index 和 Yarn Project。
- Initial Variable Key 的格式和重复。
- Block ID 重复、Next Block 引用、类型必要字段。
- Yarn Node 是否存在及是否被多个 TextBlock 共用。
- 入口、不可达 Block、环路、非向前连接和终点。
- Unlock / Forbidden 条件树的空节点和无效 Block 引用。
- Timeline Marker ID、Anchor、Label 和重复 Anchor。
- Exclusive Route Groups。
- Yarn 文件中的 `<<declare>>``<<set $...>>` 旧语法。
Validation 不检查:
- Scene / Prefab 引用是否齐全。
- Song ID 是否真的存在于 ChapterSelectionUnit。
- TutorialCollection 内容。
- Unity Localization Entry 是否齐全。
- 互斥条件是否在逻辑上百分之百正确。
- 所有语言的排版和溢出。
因此 Validation 通过不等于剧情已经达到发布质量。
## 16. 当前 Chapter 0 的已知配置状态
审阅时确认:
- `StoryData_Chapter0.chapterIndex` 已修正为 `Chapter0`,与 ChapterSelectionUnit 和 StoryManager Key 对齐。
- `Chapter0.yarnproject` 的 Base Language 是 `en`,但旧 `C0_A0.yarn` 含中文原文;正式生产前应确定基础语言并统一。
- `C0_A0.yarn` 仍包含废弃的 `declare / set $` 示例Validation 会逐条报告;此前已经明确该旧台本不作为新系统规范。
- 五份 `C0_RollbackDemo_*.yarn` 是当前永久变量、选项记忆和 Timeline 回滚的测试样例。
- `StoryTimelineController.localizationTable``StoryHelperData.localizationTable` 尚未配置。
- TextBlock 标题、Speaker 显示名、Tutorial 选择和 Timeline 回滚确认仍有待完成 Unity Localization 接入。
## 17. 推荐测试顺序
### 17.1 静态检查
1. Yarn Project 无编译错误。
2. StoryData Validation 没有 Error。
3. 所有 Warning 已确认是临时内容还是需要修复。
4. StoryData Preview 中 Block 尺寸、中心列和连线符合预期。
### 17.2 新档主流程
1. 清除当前章节 StorySave。
2. 打开章节,确认只有入口 Block 为 Current。
3. 完成 TextBlock确认后继解锁并在重启后保持。
4. 在重要选择中选择 Route A确认 Route B 变为 Forbidden。
5. 再次进入已完成 TextBlock确认选项自动采用。
### 17.3 对话按钮
1. `Advance` 第一次补全文字、第二次下一句。
2. `FastForward` 只影响当前一次 Dialog。
3. FastForward 可跨过已记忆选项,但不会越过首次选择页面。
4. `History` 只显示当前一次对话,能滚动并阻断底层输入。
5. `Back` 中途退出后变量、选项、Block 完成状态全部恢复。
### 17.4 Timeline
1. Marker 只在 Anchor Current / Completed 时显示。
2. 横向拖动 StoryTreeMarker 和 Repeat Tick 同步移动。
3. 完成 Marker 所在列后按钮可点击。
4. 回滚后目标列及右侧重新开放,目标列之前保持不变。
5. Song 成绩、`isTried`、Unlock Key、Settings 和其它章节不变。
### 17.5 Song 与 Tutorial
1. SongBlock 自动选中目标歌曲。
2. 只浏览选曲再返回,不写 `isTried`
3. 确认进入目标歌曲后即使中途退出SongBlock 仍完成。
4. Tutorial 选择游玩或跳过都完成 Block。
5. Completed Tutorial 仍可再次点击回顾。
### 17.6 本地化
逐一切换 `en``zh-CN``ja``ko``vi-VN`
1. Yarn 台词与选项正确。
2. Character 名称正确。
3. TextBlock 标题正确。
4. Timeline 文本和回滚确认正确。
5. Helper 名称与气泡正确。
6. MessageBox / SelectionBox 正确。
7. 长文本不溢出CJK 与越南语字体包含所需字形。
## 18. 常见故障定位
| 现象 | 检查顺序 |
|---|---|
| 点击 TextBlock 无反应 | Block State -> Button -> StoryDialogueController -> Yarn Node -> Yarn Project |
| Yarn Node 不存在 | StoryData 的 Node 名、Yarn Project source glob、编译诊断 |
| 对话退出后变量仍存在 | 是否使用项目 `set_*`;全局副作用是否经过 Commit Queue |
| 选项被意外跳过 | Option Line ID 是否沿用;是否已有 Choice Record是否错误修改选项组 |
| Marker 不移动 | Story ScrollRect 引用、Marker Container 坐标转换、Anchor View 是否存在 |
| SongBlock 不完成 | `PrepareGameLaunch` 是否调用、Song ID 是否匹配、`SongStatusSave.isTried` |
| SongBlock 错误完成 | 是否有多个章节/歌曲复用了同一个 `songName`;该字段必须全局稳定 |
| Helper 只显示 Key | StoryHelperData Localization Table 或 Entry 缺失 |
| Speaker 显示内部 ID | 当前显示名本地化尚未接入,不能只配置 displayNameKey 就视为完成 |
| Console 出现多份台词日志 | 调试用 ConsoleLinePresenter 仍启用 |
## 19. 发布前剧情系统 Gate
满足以下条件后,剧情和对话系统才可进入 Release 验收:
1. Chapter 0 所有正式 Yarn 台本不再使用废弃临时变量语法。
2. Yarn Base Language 与源文本一致。
3. 五种首发语言的 Yarn、Story UI、Helper、Message 文本齐全。
4. TextBlock 标题、Speaker、Tutorial、Timeline 确认框全部完成 Unity Localization。
5. ConsoleLinePresenter 从发布配置移除。
6. StoryData Validation 无 Error所有 Warning 有明确处理结论。
7. 两个互斥结局、支线 Forbidden、选项记忆和 Timeline 回滚完成回归测试。
8. SongBlock `isTried`、Tutorial 回顾、Back 回滚、FastForward 会话边界均通过测试。
9. Android、iOS、PC 分别完成长文本、输入和场景往返测试。
10. 正式发布前冻结 Story Save v1发布后结构变化必须编写迁移不能继续直接删档。