21 KiB
ichni Official 剧情系统整合、配置与测试手册
适用对象:程序员、Technical Designer、负责 Unity 配置和剧情 QA 的成员
适用项目:ichni Official
当前实现:Yarn Spinner3.2.1、Unity Localization、ES3、DOTween
最后更新:2026-07-21
本文说明如何把剧本编写者交付的 Yarn Scripts 整合进游戏,并配置 StoryData、角色、Timeline、Helper、存档和测试流程。CreatorStudio 不参与本套任务;只有未来任务明确触及节奏游戏元素、共享序列化结构或 Creator 导入流程时,才需要重新评估跨项目同步。
1. 系统边界与数据流
StoryData(静态剧情图、条件、Marker、默认变量)
-> StoryTreeController(生成 Block、推导状态、处理回滚)
-> TextBlockView -> StoryDialogueController -> DialogueRunner
-> VNDialoguePresenter / VNPortraitStage / DialogUIPage
-> StoryProgress -> StorySaveModule -> ES3
-> SongBlockView -> SongSaves.isTried
-> TutorialBlockView -> TutorialFlowController
-> StoryTimelineController(Marker 投影、进度、回滚入口)
-> 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 使用:
Chapter0
StoryTreeController.BuildChapter 会在运行时报告三者不一致,但不会擅自改写资产。
3.2 创建章节目录和 Yarn Project
建议结构:
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
使用:
Create > Ichni > Story > New > StoryData
配置:
Chapter Index:与章节资产完全一致。Yarn Project:拖入本章节 Yarn Project。Initial Variables:登记章节变量的默认值和类型。Blocks:配置完整静态剧情图。Timeline Markers:配置重要时间点。Exclusive Route Groups:只用于 Validation,登记互斥终点组。
最后在场景 StoryManager.storyDatas 中添加:
Key: Chapter1
Value: StoryData_Chapter1
4. Initial Variables
每个变量定义包含:
KeyType: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 = 720rowStep = 525helperReservedLeftSpace = 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)NotBlock CompletedVariable Compare
状态优先级:
Forbidden > Completed > Current > Locked
互斥路线应同时配置 Unlock 和 Forbidden。例如:
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 运行流程:
StoryDialogueController检查 Runner、章节上下文和 Yarn Node 是否存在。- 复制
ChapterStorySave并开始事务。 StoryProgress.BeginBlockMutation捕获同列 Marker 快照。- Yarn 运行,选项和变量只修改内存。
- 正常结束:完成 Block、提交 ES3、执行排队的解锁/Message。
- 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 NameTutorial 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 后检查:
Source Yarn Scripts覆盖正确目录。Base Language与 Yarn 源文本语言一致。- 开启
Use Unity Localisation System。 String Table Collection指向目标 Unity String Table Collection。- 点击
Add Line Tags to Yarn Scripts,为缺少 ID 的台词和选项生成#line:。 - 保存并重新导入,确认 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
创建:
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 应包含:
DialogueRunnerUnityLocalisedLineProviderVNDialoguePresenterStoryDialogueController
StoryDialogueController:
dialogueRunnerstoryUIPage- 可选的统一
yarnLinesTable
DialogUIPage:
portraitStagedialogueTextspeakerContainer/speakerTextadvanceButtonfastForwardButtonbackButtonshowHistoryButtondialogueHistoryPagechoiceFramechoiceButtons
DialogueHistoryPage 必须有全屏可接收 Raycast 的覆盖层;打开时要阻断底层 Dialog 输入。
当前 MenuScene 仍启用了调试用 ConsoleLinePresenter。它会额外把每行本地化结果输出到 Console;发布构建前应从 DialogueRunner Presenter 列表移除或禁用,只保留 VNDialoguePresenter。
14. Helper 配置
Helper 使用全局 StoryHelperData,不属于单个 StoryData。
字段:
Helper IDPortrait SpriteLocalization TableDisplay Name KeyFallback Dialogue KeyDialogue Pool
每条候选包含:
IDText KeyAvailability:复用 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 静态检查
- Yarn Project 无编译错误。
- StoryData Validation 没有 Error。
- 所有 Warning 已确认是临时内容还是需要修复。
- StoryData Preview 中 Block 尺寸、中心列和连线符合预期。
17.2 新档主流程
- 清除当前章节 StorySave。
- 打开章节,确认只有入口 Block 为 Current。
- 完成 TextBlock,确认后继解锁并在重启后保持。
- 在重要选择中选择 Route A,确认 Route B 变为 Forbidden。
- 再次进入已完成 TextBlock,确认选项自动采用。
17.3 对话按钮
Advance第一次补全文字、第二次下一句。FastForward只影响当前一次 Dialog。- FastForward 可跨过已记忆选项,但不会越过首次选择页面。
History只显示当前一次对话,能滚动并阻断底层输入。Back中途退出后,变量、选项、Block 完成状态全部恢复。
17.4 Timeline
- Marker 只在 Anchor Current / Completed 时显示。
- 横向拖动 StoryTree,Marker 和 Repeat Tick 同步移动。
- 完成 Marker 所在列后按钮可点击。
- 回滚后目标列及右侧重新开放,目标列之前保持不变。
- Song 成绩、
isTried、Unlock Key、Settings 和其它章节不变。
17.5 Song 与 Tutorial
- SongBlock 自动选中目标歌曲。
- 只浏览选曲再返回,不写
isTried。 - 确认进入目标歌曲后,即使中途退出,SongBlock 仍完成。
- Tutorial 选择游玩或跳过都完成 Block。
- Completed Tutorial 仍可再次点击回顾。
17.6 本地化
逐一切换 en、zh-CN、ja、ko、vi-VN:
- Yarn 台词与选项正确。
- Character 名称正确。
- TextBlock 标题正确。
- Timeline 文本和回滚确认正确。
- Helper 名称与气泡正确。
- MessageBox / SelectionBox 正确。
- 长文本不溢出,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 验收:
- Chapter 0 所有正式 Yarn 台本不再使用废弃临时变量语法。
- Yarn Base Language 与源文本一致。
- 五种首发语言的 Yarn、Story UI、Helper、Message 文本齐全。
- TextBlock 标题、Speaker、Tutorial、Timeline 确认框全部完成 Unity Localization。
- ConsoleLinePresenter 从发布配置移除。
- StoryData Validation 无 Error,所有 Warning 有明确处理结论。
- 两个互斥结局、支线 Forbidden、选项记忆和 Timeline 回滚完成回归测试。
- SongBlock
isTried、Tutorial 回顾、Back 回滚、FastForward 会话边界均通过测试。 - Android、iOS、PC 分别完成长文本、输入和场景往返测试。
- 正式发布前冻结 Story Save v1;发布后结构变化必须编写迁移,不能继续直接删档。