Compare commits

...
Author SHA1 Message Date
dingyuntian 75ece85309 Merge branch 'bugfix/帧动画调整' into 'develop'
chore: 版本号升级

See merge request aibis-dream/aibis-dream!841
2026-08-05 13:56:42 +00:00
dingyuntian 278543cb8b chore: 版本号升级 2026-08-05 21:56:13 +08:00
dingyuntian 9fdef73926 Merge branch 'bugfix/帧动画调整' into 'develop'
Bugfix/帧动画调整

See merge request aibis-dream/aibis-dream!840
2026-08-05 13:53:03 +00:00
dingyuntian 121e7f398c feat: FlowAsync调整 2026-08-05 21:14:06 +08:00
dingyuntian 9c3c467046 fix: 静音功能 2026-08-05 21:13:43 +08:00
dingyuntian 69152748b5 Merge branch 'build' into 'develop'
feat: 打包后

See merge request aibis-dream/aibis-dream!839
2026-08-03 09:39:52 +00:00
dingyuntian 8ac4a6705a feat: 打包后 2026-08-03 17:13:24 +08:00
dingyuntian a23b7376a0 Merge branch 'bugfix佩佩2流程' into 'develop'
scene(huoshan): 调整火山维修场景维修面板线缆与布局

See merge request aibis-dream/aibis-dream!838
2026-08-03 08:29:13 +00:00
bottlefishandCursor 1ccf81fe8a chore(shapes): 启用 Shapes 即时模式实例化
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-03 16:27:45 +08:00
bottlefishandCursor 68d7be823d scene(huoshan): 调整火山维修场景维修面板线缆与布局
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-03 16:27:31 +08:00
dingyuntian 1c37b395da Merge branch 'build' into 'develop'
fix: 小问题

See merge request aibis-dream/aibis-dream!837
2026-08-03 08:26:20 +00:00
dingyuntian 79999f7dfa fix: 小问题 2026-08-03 16:17:11 +08:00
dingyuntian 090c9e1d86 Merge branch 'bugfix佩佩2流程' into 'develop'
fix(peipei): 修正佩佩BE判定变量为 global_isPeipeiBE

See merge request aibis-dream/aibis-dream!836
2026-08-03 08:11:07 +00:00
bottlefishandCursor 452fcdf3fb fix(peipei): 修正佩佩BE判定变量为 global_isPeipeiBE
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-03 16:06:06 +08:00
dingyuntian 3f4fa30b1d Merge branch 'build' into 'develop'
Build

See merge request aibis-dream/aibis-dream!835
2026-08-03 07:10:49 +00:00
dingyuntian dd2e09f12e chore: 本地化 2026-08-03 15:10:14 +08:00
dingyuntian 9fdb91c8b1 feat: yarn调整 2026-08-03 15:10:02 +08:00
dingyuntian 5794245a98 Merge branch 'feature/本地化&勘误' into 'develop'
Feature/本地化&勘误

See merge request aibis-dream/aibis-dream!833
2026-08-03 06:55:41 +00:00
dingyuntian 5e16404d98 Merge branch 'bugfix/本地化最后一波' into 'develop'
Bugfix/本地化最后一波

See merge request aibis-dream/aibis-dream!834
2026-08-03 06:54:56 +00:00
dingyuntian 6f1bead4ab chore: 版本号升级 2026-08-03 14:54:32 +08:00
dingyuntian 89def8621c feat: 本地化文本更新 2026-08-03 14:48:16 +08:00
dingyuntian ce860ca734 art: 诊所外佩佩尺寸问题 2026-08-03 14:47:53 +08:00
dingyuntian 899f803796 feat: 缩略图更新 2026-08-03 14:47:34 +08:00
Ovid 5cbbbdf03e +UF内部说话人 2026-08-03 10:28:41 +08:00
Ovid ef47709301 commit 2026-08-03 10:28:26 +08:00
dingyuntian 7d5e1301a0 Merge branch 'bugfix/本地化最后一波' into 'develop'
Bugfix/本地化最后一波

See merge request aibis-dream/aibis-dream!832
2026-08-02 20:06:45 +00:00
dingyuntian 44ac9ace0a fix: 垃圾测试 2026-08-03 04:05:31 +08:00
dingyuntian 2cb37a09a7 feat: 火山病历本地化 2026-08-03 03:49:00 +08:00
dingyuntian 7867e501b8 fix: 冒号和中括号加转义 2026-08-03 02:07:17 +08:00
dingyuntian 1ed7b7c881 fix: 版本号规则更新 2026-08-03 01:33:51 +08:00
dingyuntian 708786e788 feat: 佩佩病历本地化 2026-08-03 01:21:14 +08:00
dingyuntian d0cd0b00b5 fix: 社群链接调整 2026-08-02 23:50:41 +08:00
dingyuntian 0e2e61a83b Merge branch 'bugfix/问题修复' into 'develop'
Bugfix/问题修复

See merge request aibis-dream/aibis-dream!831
2026-08-02 13:46:09 +00:00
dingyuntian 5fa324de6c fix: 切割锯子接入EventTrigger 2026-08-02 21:45:36 +08:00
dingyuntian 93501dddaf feat: 增加本地化图片播放 2026-08-02 21:20:05 +08:00
dingyuntian e6c3148764 fix: 梦境行加长 2026-08-02 19:49:44 +08:00
dingyuntian 4739e14d8d fix: 佩佩外出动画替换 2026-08-02 19:49:22 +08:00
dingyuntian a088531224 Merge branch 'bugfix/火山本地化问题' into 'develop'
fix: 火山玩法本地化问题

See merge request aibis-dream/aibis-dream!830
2026-08-02 11:27:23 +00:00
dingyuntian 2fa3eb8024 fix: 火山玩法本地化问题 2026-08-02 19:26:56 +08:00
dingyuntian 6c46874e44 Merge branch 'feature/Steam愿望单' into 'develop'
Feature/steam愿望单

See merge request aibis-dream/aibis-dream!829
2026-08-02 09:56:07 +00:00
dingyuntian 398bdf18de art: 检索Timeline 2026-08-02 17:55:42 +08:00
dingyuntian be4e820aa5 feat: 愿望单按钮 2026-08-02 17:55:26 +08:00
dingyuntian e8aea1e83d feat: 本地化表调整 2026-08-02 17:54:33 +08:00
dingyuntian fad170c673 Merge branch 'feature/d2nMus' into 'develop'
d2nMus

See merge request aibis-dream/aibis-dream!827
2026-08-02 09:49:26 +00:00
Ovid ba5960b4e7 d2nMusDebug&错字 2026-08-02 16:54:22 +08:00
dingyuntian 48b1ed4ec9 Merge branch 'feature/Steam愿望单' into 'develop'
Feature/steam愿望单

See merge request aibis-dream/aibis-dream!828
2026-08-02 06:29:44 +00:00
dingyuntian 3b1a551a04 fix: 本地化问题微调 2026-08-02 14:07:16 +08:00
dingyuntian 658aa1889b Merge branch 'bugfix/火山玩法回归' into feature/Steam愿望单 2026-08-02 13:44:53 +08:00
dingyuntian 56337c5173 fix: 火山玩法回退 2026-08-02 13:43:06 +08:00
Ovid 3d61fa5944 d2nMus 2026-08-02 02:33:04 +08:00
dingyuntian 64c7245d9e feat: 帧动画Timeline问题处理 2026-08-02 02:22:46 +08:00
dingyuntian 20a458ec78 feat: 字体兼容相关调整 2026-08-02 01:22:59 +08:00
dingyuntian 3adb04ebb3 feat: 本地化字体兼容 2026-08-02 01:22:40 +08:00
dingyuntian 3bff294130 chore: 去除无用资源 2026-08-02 01:20:27 +08:00
dingyuntian b0ae7233e0 fix: 本地化小调整 2026-08-02 01:18:50 +08:00
dingyuntian 4e2231ef01 art: 字体重新引入 2026-08-02 01:18:08 +08:00
dingyuntian c77306af0c feat: 本地化导入完成 2026-08-01 17:04:50 +08:00
dingyuntian ee08eab9b0 chore: 自动测试重构及规范 2026-08-01 13:57:45 +08:00
dingyuntian 24c5169a10 fix: 调整Character语言回退逻辑 2026-07-31 23:13:56 +08:00
dingyuntian 48dcc9ec3a Merge branch 'develop' into feature/Steam愿望单 2026-07-31 22:44:07 +08:00
dingyuntian d675cd1338 Merge branch 'feature/梦境问题' into 'develop'
Feature/梦境问题

See merge request aibis-dream/aibis-dream!826
2026-07-31 14:43:17 +00:00
dingyuntian bd39c27c1d feat: 添加打开Steam商店页功能 2026-07-31 18:22:55 +08:00
dingyuntian 01cc5036d3 chore: UnityTest清理 2026-07-31 18:22:08 +08:00
dingyuntian de8d4a5037 fix: 问题修复 2026-07-31 16:41:05 +08:00
dingyuntian 8bff84640a Merge branch 'develop' into feature/梦境问题 2026-07-31 15:17:23 +08:00
dingyuntian 62e05968b7 feat: 本地化扩展 2026-07-30 21:25:36 +08:00
dingyuntian 0749df3eb2 Merge branch 'feature/梦境问题' into 'develop'
fix: 打包问题

See merge request aibis-dream/aibis-dream!825
2026-07-30 12:19:54 +00:00
dingyuntian f113c979a2 fix: 打包问题 2026-07-30 20:11:44 +08:00
dingyuntian d9d61be09a Merge branch 'feature/梦境问题' into 'develop'
Revert "feat: 梦境问题处理"

See merge request aibis-dream/aibis-dream!824
2026-07-29 17:09:42 +00:00
dingyuntian daa30de7f3 Revert "feat: 梦境问题处理"
This reverts commit 6b46fe3faa.
2026-07-30 01:09:04 +08:00
dingyuntian 6cc19f3d7b feat: 升版本 2026-07-30 01:01:31 +08:00
dingyuntian 1df4188994 Merge branch 'develop' into feature/梦境gengx 2026-07-30 00:48:50 +08:00
dingyuntian 6b46fe3faa feat: 梦境问题处理 2026-07-30 00:48:41 +08:00
dingyuntian b211f674d7 Merge branch 'feature/D2SdEBUG' into 'develop'
COMMIT;

See merge request aibis-dream/aibis-dream!823
2026-07-29 16:22:03 +00:00
Ovid 497ddd45e3 COMMIT; 2026-07-30 00:16:54 +08:00
dingyuntian 7502638281 Merge branch 'feature/本地化正式' into 'develop'
feat: 火山玩法本地化

See merge request aibis-dream/aibis-dream!821
2026-07-29 15:37:19 +00:00
dingyuntian 99338a04cc Merge branch 'feature/musfix-PP2删除记忆音乐改无版权' into 'develop'
替换PP2音乐

See merge request aibis-dream/aibis-dream!818
2026-07-28 15:43:35 +00:00
Ovid 38fce43283 替换PP2音乐 2026-07-28 23:33:52 +08:00
dingyuntian c735f3084e Merge branch 'feature/章节名' into 'develop'
章节名

See merge request aibis-dream/aibis-dream!817
2026-07-28 10:16:00 +00:00
Ovid e881c9e710 章节名 2026-07-28 17:52:03 +08:00
dingyuntian fe11a89b2d Merge branch 'feature/本地化' into 'develop'
yarn: 本地化

See merge request aibis-dream/aibis-dream!816
2026-07-28 09:14:26 +00:00
dingyuntian 9386af3155 Merge branch 'feature/本地化' into 'develop'
yarn: 添加行号

See merge request aibis-dream/aibis-dream!815
2026-07-28 09:03:35 +00:00
970 changed files with 176503 additions and 10927 deletions
+11
View File
@@ -67,6 +67,17 @@ Modular framework with Kit-pattern utilities:
4. Favor composition over inheritance
5. All runtime asset loading via `ResourceSystem` — never `Resources.Load`
### Unity Tests
- Unity Tests are encouraged when they provide useful feedback, but do not add tests mechanically at every layer merely because production code changed.
- Choose the lowest layer that fully protects each behavior. Add a second layer only when it covers a distinct integration risk.
- Stable tests protect accepted contracts and high-risk regressions. Prefer extending an existing domain suite; create a new permanent file or asmdef only for a genuinely new long-lived boundary.
- Tests for unfinished, exploratory, or intermediate behavior must live under `Assets/Tests/_Temporary/<Feature>/`; never mix them into permanent suites.
- Every temporary test folder must state its purpose and deletion or promotion condition. Delete it when the feature is accepted, replaced, abandoned, or adequately covered by permanent contract tests.
- Prefer EditMode when it is sufficient; use PlayMode for behavior that truly requires Unity lifecycle, frames, coroutines, physics, or Timeline runtime integration.
- Remove tests when their feature is removed, their contract is replaced, or they only mirror implementation details.
- Follow [Unity Test Guidelines](Docs/UnityTestingGuidelines.md) for placement, design, cleanup, and review criteria.
### Scene Structure
- Scenes in `Assets/Scenes/`; `Persistence` scene hosts global managers and Persistent Loader
- Fix scenes: `{Character}FixScene` (e.g. `HuoShanFixScene`, `PeipeiFixScene`, `ExpressFixScene`)
@@ -15,7 +15,7 @@ MonoBehaviour:
m_DefaultGroup: 09546aca93a801441859af2a811fe53b
m_currentHash:
serializedVersion: 2
Hash: ae2a22805ec7ff55174c38995aa3e54d
Hash: 2b0c2e120ca52edab004ec49752fe5ec
m_OptimizeCatalogSize: 0
m_BuildRemoteCatalog: 0
m_BundleLocalCatalog: 0
@@ -71,6 +71,16 @@ MonoBehaviour:
- {fileID: 11400000, guid: 089fc86f714d6a94bafea3b117fbd639, type: 2}
- {fileID: 11400000, guid: 1c592abe924a856439461f3785ec53da, type: 2}
- {fileID: 11400000, guid: 26fd03652560e7f4abec092229fc38f2, type: 2}
- {fileID: 11400000, guid: 1051221ba75fcea47b67648acedc5383, type: 2}
- {fileID: 11400000, guid: 2b05cac5050c285498c6d11b749f865a, type: 2}
- {fileID: 11400000, guid: 92e4c8e9ee367dd499b7e2f0c1e4fb3a, type: 2}
- {fileID: 11400000, guid: 2bf1c0c3a87916a41aa95e5c29efe3d9, type: 2}
- {fileID: 11400000, guid: f91efa4c655c0014ca776fb2e8b4f1fc, type: 2}
- {fileID: 11400000, guid: b05cfdc6bbe312248b8be84a1ff01e32, type: 2}
- {fileID: 11400000, guid: 6e0c8dfd5e5e1c74b80cecc21df2d9cd, type: 2}
- {fileID: 11400000, guid: d029169cdb68ba848a0f0260aeddae55, type: 2}
- {fileID: 11400000, guid: 0418ce3bee30b98409aa4d268d7d89d7, type: 2}
- {fileID: 11400000, guid: f0205c268777acb459dddbb4690d7c27, type: 2}
m_BuildSettings:
m_CompileScriptsInVirtualMode: 0
m_CleanupStreamingAssetsAfterBuilds: 1
@@ -121,6 +131,7 @@ MonoBehaviour:
- Locale-pt-BR
- Locale-ru
- Locale-es
- Typography
m_SchemaTemplates: []
m_GroupTemplateObjects:
- {fileID: 11400000, guid: 0c158052680a3e94184c450d00291514, type: 2}
@@ -0,0 +1,31 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: bbb281ee3bf0b054c82ac2347e9e782c, type: 3}
m_Name: Localization-Asset-Tables-Portuguese (Brazil) (pt-BR)
m_EditorClassIdentifier:
m_GroupName: Localization-Asset-Tables-Portuguese (Brazil) (pt-BR)
m_Data:
m_SerializedData: []
m_GUID: 96edbabcf99a9a74ab2ac2cffeea0541
m_SerializeEntries:
- m_GUID: 90b94df062e41984db3329bb28f34faf
m_Address: sprite_pt-BR
m_ReadOnly: 1
m_SerializedLabels:
- Locale-pt-BR
FlaggedDuringContentUpdateRestriction: 0
m_ReadOnly: 1
m_Settings: {fileID: 11400000, guid: 77169ce22e430f64fb36c771815a4a7b, type: 2}
m_SchemaSet:
m_Schemas:
- {fileID: 11400000, guid: 29e8470e79377b4409a648ee18ce7682, type: 2}
- {fileID: 11400000, guid: 04b28f4d862df2440ac0be17bd1cd694, type: 2}
@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: d8dda148dfd9e5745807a2cbecd1f95f
guid: 92e4c8e9ee367dd499b7e2f0c1e4fb3a
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
@@ -0,0 +1,31 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: bbb281ee3bf0b054c82ac2347e9e782c, type: 3}
m_Name: Localization-Asset-Tables-Russian (ru)
m_EditorClassIdentifier:
m_GroupName: Localization-Asset-Tables-Russian (ru)
m_Data:
m_SerializedData: []
m_GUID: dc30b2d9e3b8f714aa238ad780039c50
m_SerializeEntries:
- m_GUID: 9905fa50dcc341244b3e840602e2be5c
m_Address: sprite_ru
m_ReadOnly: 1
m_SerializedLabels:
- Locale-ru
FlaggedDuringContentUpdateRestriction: 0
m_ReadOnly: 1
m_Settings: {fileID: 11400000, guid: 77169ce22e430f64fb36c771815a4a7b, type: 2}
m_SchemaSet:
m_Schemas:
- {fileID: 11400000, guid: ffaea4b400f30fd43bb702a14172406c, type: 2}
- {fileID: 11400000, guid: 42d59bddf89a08140adb832b309cf5a7, type: 2}
@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 2f628bf07a7245b4cb27a590db9f51ef
guid: 2bf1c0c3a87916a41aa95e5c29efe3d9
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
@@ -0,0 +1,31 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: bbb281ee3bf0b054c82ac2347e9e782c, type: 3}
m_Name: Localization-Asset-Tables-Spanish (es)
m_EditorClassIdentifier:
m_GroupName: Localization-Asset-Tables-Spanish (es)
m_Data:
m_SerializedData: []
m_GUID: 763871518508ac74989cadb20512c476
m_SerializeEntries:
- m_GUID: d4d54e71333c6a84796e814143a6ecb2
m_Address: sprite_es
m_ReadOnly: 1
m_SerializedLabels:
- Locale-es
FlaggedDuringContentUpdateRestriction: 0
m_ReadOnly: 1
m_Settings: {fileID: 11400000, guid: 77169ce22e430f64fb36c771815a4a7b, type: 2}
m_SchemaSet:
m_Schemas:
- {fileID: 11400000, guid: 779a71a2538b963459fd1ef9cc055b7c, type: 2}
- {fileID: 11400000, guid: 90fabd6fb037ad0489aaccead4298af4, type: 2}
@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 1b9419cf5ebdeb449a112d42f4205c33
guid: f91efa4c655c0014ca776fb2e8b4f1fc
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
@@ -16,13 +16,7 @@ MonoBehaviour:
m_Data:
m_SerializedData: []
m_GUID: b848f030a719ba04c9f1e343f17ddc0c
m_SerializeEntries:
- m_GUID: 6d8ee72f781acf64abfe82ab659f5771
m_Address: 03_qq_symbol
m_ReadOnly: 1
m_SerializedLabels:
- Locale-zh-Hans
FlaggedDuringContentUpdateRestriction: 0
m_SerializeEntries: []
m_ReadOnly: 1
m_Settings: {fileID: 11400000, guid: 77169ce22e430f64fb36c771815a4a7b, type: 2}
m_SchemaSet:
@@ -0,0 +1,25 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: bbb281ee3bf0b054c82ac2347e9e782c, type: 3}
m_Name: Localization-Assets-Japanese (Japan) (ja-JP)
m_EditorClassIdentifier:
m_GroupName: Localization-Assets-Japanese (Japan) (ja-JP)
m_Data:
m_SerializedData: []
m_GUID: 9bb3a2bc8a719b04b918354595af2429
m_SerializeEntries: []
m_ReadOnly: 1
m_Settings: {fileID: 11400000, guid: 77169ce22e430f64fb36c771815a4a7b, type: 2}
m_SchemaSet:
m_Schemas:
- {fileID: 11400000, guid: d3a6468425a79364fa21d96e369e8399, type: 2}
- {fileID: 11400000, guid: 2a43b55ded538f14f92548425cfb42e5, type: 2}
@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: c0f4601f141d314479c22f4bb3efd5a7
guid: 2b05cac5050c285498c6d11b749f865a
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
@@ -40,18 +40,33 @@ MonoBehaviour:
m_ReadOnly: 1
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 032b1b5e09139ae45b6de51d35baae79
m_Address: discord-square-color-icon
- m_GUID: 539db7533ab287b45a774b3c5e2ccd9d
m_Address: "\u7ED3\u5C3E\u56FE\u6807"
m_ReadOnly: 1
m_SerializedLabels:
- Locale-en
- Locale-zh-Hans
- Locale-ja-JP
- Locale-pt-BR
- Locale-ru
- Locale-es
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 7ea8c68fdc624de4c86434963c06ec55
m_Address: Assets/Language/ChapterInfo/ChapterInfo Shared Data.asset
m_ReadOnly: 1
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: e93fefacb613c7949bd980f442308be8
m_Address: "\u613F\u671B\u5355\u95EE\u5377\u793E\u7FA4"
m_ReadOnly: 1
m_SerializedLabels:
- Locale-zh-Hans
- Locale-en
- Locale-ja-JP
- Locale-pt-BR
- Locale-ru
- Locale-es
FlaggedDuringContentUpdateRestriction: 0
m_ReadOnly: 1
m_Settings: {fileID: 11400000, guid: 77169ce22e430f64fb36c771815a4a7b, type: 2}
m_SchemaSet:
@@ -0,0 +1,45 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: e5d17a21594effb4e9591490b009e7aa, type: 3}
m_Name: Localization-Asset-Tables-Portuguese (Brazil) (pt-BR)_BundledAssetGroupSchema
m_EditorClassIdentifier:
m_Group: {fileID: 11400000, guid: 92e4c8e9ee367dd499b7e2f0c1e4fb3a, type: 2}
m_InternalBundleIdMode: 1
m_Compression: 1
m_IncludeAddressInCatalog: 1
m_IncludeGUIDInCatalog: 1
m_IncludeLabelsInCatalog: 1
m_InternalIdNamingMode: 0
m_CacheClearBehavior: 0
m_IncludeInBuild: 1
m_BundledAssetProviderType:
m_AssemblyName: Unity.ResourceManager, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
m_ClassName: UnityEngine.ResourceManagement.ResourceProviders.BundledAssetProvider
m_ForceUniqueProvider: 0
m_UseAssetBundleCache: 1
m_UseAssetBundleCrc: 1
m_UseAssetBundleCrcForCachedBundles: 1
m_UseUWRForLocalBundles: 0
m_Timeout: 0
m_ChunkedTransfer: 0
m_RedirectLimit: -1
m_RetryCount: 0
m_BuildPath:
m_Id: 2ae6a7549097a9b4caefe44b61b1fcc5
m_LoadPath:
m_Id: 631ec5c677ab8084fb9be9b9ee39c6ef
m_BundleMode: 0
m_AssetBundleProviderType:
m_AssemblyName: Unity.ResourceManager, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
m_ClassName: UnityEngine.ResourceManagement.ResourceProviders.AssetBundleProvider
m_BundleNaming: 1
m_AssetLoadMode: 0
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 29e8470e79377b4409a648ee18ce7682
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,16 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 5834b5087d578d24c926ce20cd31e6d6, type: 3}
m_Name: Localization-Asset-Tables-Portuguese (Brazil) (pt-BR)_ContentUpdateGroupSchema
m_EditorClassIdentifier:
m_Group: {fileID: 11400000, guid: 92e4c8e9ee367dd499b7e2f0c1e4fb3a, type: 2}
m_StaticContent: 0
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 04b28f4d862df2440ac0be17bd1cd694
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,45 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: e5d17a21594effb4e9591490b009e7aa, type: 3}
m_Name: Localization-Asset-Tables-Russian (ru)_BundledAssetGroupSchema
m_EditorClassIdentifier:
m_Group: {fileID: 11400000, guid: 2bf1c0c3a87916a41aa95e5c29efe3d9, type: 2}
m_InternalBundleIdMode: 1
m_Compression: 1
m_IncludeAddressInCatalog: 1
m_IncludeGUIDInCatalog: 1
m_IncludeLabelsInCatalog: 1
m_InternalIdNamingMode: 0
m_CacheClearBehavior: 0
m_IncludeInBuild: 1
m_BundledAssetProviderType:
m_AssemblyName: Unity.ResourceManager, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
m_ClassName: UnityEngine.ResourceManagement.ResourceProviders.BundledAssetProvider
m_ForceUniqueProvider: 0
m_UseAssetBundleCache: 1
m_UseAssetBundleCrc: 1
m_UseAssetBundleCrcForCachedBundles: 1
m_UseUWRForLocalBundles: 0
m_Timeout: 0
m_ChunkedTransfer: 0
m_RedirectLimit: -1
m_RetryCount: 0
m_BuildPath:
m_Id: 2ae6a7549097a9b4caefe44b61b1fcc5
m_LoadPath:
m_Id: 631ec5c677ab8084fb9be9b9ee39c6ef
m_BundleMode: 0
m_AssetBundleProviderType:
m_AssemblyName: Unity.ResourceManager, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
m_ClassName: UnityEngine.ResourceManagement.ResourceProviders.AssetBundleProvider
m_BundleNaming: 1
m_AssetLoadMode: 0
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: ffaea4b400f30fd43bb702a14172406c
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,16 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 5834b5087d578d24c926ce20cd31e6d6, type: 3}
m_Name: Localization-Asset-Tables-Russian (ru)_ContentUpdateGroupSchema
m_EditorClassIdentifier:
m_Group: {fileID: 11400000, guid: 2bf1c0c3a87916a41aa95e5c29efe3d9, type: 2}
m_StaticContent: 0
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 42d59bddf89a08140adb832b309cf5a7
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,45 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: e5d17a21594effb4e9591490b009e7aa, type: 3}
m_Name: Localization-Asset-Tables-Spanish (es)_BundledAssetGroupSchema
m_EditorClassIdentifier:
m_Group: {fileID: 11400000, guid: f91efa4c655c0014ca776fb2e8b4f1fc, type: 2}
m_InternalBundleIdMode: 1
m_Compression: 1
m_IncludeAddressInCatalog: 1
m_IncludeGUIDInCatalog: 1
m_IncludeLabelsInCatalog: 1
m_InternalIdNamingMode: 0
m_CacheClearBehavior: 0
m_IncludeInBuild: 1
m_BundledAssetProviderType:
m_AssemblyName: Unity.ResourceManager, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
m_ClassName: UnityEngine.ResourceManagement.ResourceProviders.BundledAssetProvider
m_ForceUniqueProvider: 0
m_UseAssetBundleCache: 1
m_UseAssetBundleCrc: 1
m_UseAssetBundleCrcForCachedBundles: 1
m_UseUWRForLocalBundles: 0
m_Timeout: 0
m_ChunkedTransfer: 0
m_RedirectLimit: -1
m_RetryCount: 0
m_BuildPath:
m_Id: 2ae6a7549097a9b4caefe44b61b1fcc5
m_LoadPath:
m_Id: 631ec5c677ab8084fb9be9b9ee39c6ef
m_BundleMode: 0
m_AssetBundleProviderType:
m_AssemblyName: Unity.ResourceManager, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
m_ClassName: UnityEngine.ResourceManagement.ResourceProviders.AssetBundleProvider
m_BundleNaming: 1
m_AssetLoadMode: 0
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 779a71a2538b963459fd1ef9cc055b7c
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,16 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 5834b5087d578d24c926ce20cd31e6d6, type: 3}
m_Name: Localization-Asset-Tables-Spanish (es)_ContentUpdateGroupSchema
m_EditorClassIdentifier:
m_Group: {fileID: 11400000, guid: f91efa4c655c0014ca776fb2e8b4f1fc, type: 2}
m_StaticContent: 0
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 90fabd6fb037ad0489aaccead4298af4
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,45 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: e5d17a21594effb4e9591490b009e7aa, type: 3}
m_Name: Localization-Assets-Japanese (Japan) (ja-JP)_BundledAssetGroupSchema
m_EditorClassIdentifier:
m_Group: {fileID: 11400000, guid: 2b05cac5050c285498c6d11b749f865a, type: 2}
m_InternalBundleIdMode: 1
m_Compression: 1
m_IncludeAddressInCatalog: 1
m_IncludeGUIDInCatalog: 1
m_IncludeLabelsInCatalog: 1
m_InternalIdNamingMode: 0
m_CacheClearBehavior: 0
m_IncludeInBuild: 1
m_BundledAssetProviderType:
m_AssemblyName: Unity.ResourceManager, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
m_ClassName: UnityEngine.ResourceManagement.ResourceProviders.BundledAssetProvider
m_ForceUniqueProvider: 0
m_UseAssetBundleCache: 1
m_UseAssetBundleCrc: 1
m_UseAssetBundleCrcForCachedBundles: 1
m_UseUWRForLocalBundles: 0
m_Timeout: 0
m_ChunkedTransfer: 0
m_RedirectLimit: -1
m_RetryCount: 0
m_BuildPath:
m_Id: 2ae6a7549097a9b4caefe44b61b1fcc5
m_LoadPath:
m_Id: 631ec5c677ab8084fb9be9b9ee39c6ef
m_BundleMode: 0
m_AssetBundleProviderType:
m_AssemblyName: Unity.ResourceManager, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
m_ClassName: UnityEngine.ResourceManagement.ResourceProviders.AssetBundleProvider
m_BundleNaming: 1
m_AssetLoadMode: 0
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: d3a6468425a79364fa21d96e369e8399
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,16 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 5834b5087d578d24c926ce20cd31e6d6, type: 3}
m_Name: Localization-Assets-Japanese (Japan) (ja-JP)_ContentUpdateGroupSchema
m_EditorClassIdentifier:
m_Group: {fileID: 11400000, guid: 2b05cac5050c285498c6d11b749f865a, type: 2}
m_StaticContent: 0
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 2a43b55ded538f14f92548425cfb42e5
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,45 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: e5d17a21594effb4e9591490b009e7aa, type: 3}
m_Name: Shared_En_BundledAssetGroupSchema
m_EditorClassIdentifier:
m_Group: {fileID: 11400000, guid: b05cfdc6bbe312248b8be84a1ff01e32, type: 2}
m_InternalBundleIdMode: 1
m_Compression: 1
m_IncludeAddressInCatalog: 1
m_IncludeGUIDInCatalog: 1
m_IncludeLabelsInCatalog: 1
m_InternalIdNamingMode: 0
m_CacheClearBehavior: 0
m_IncludeInBuild: 1
m_BundledAssetProviderType:
m_AssemblyName: Unity.ResourceManager, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
m_ClassName: UnityEngine.ResourceManagement.ResourceProviders.BundledAssetProvider
m_ForceUniqueProvider: 0
m_UseAssetBundleCache: 1
m_UseAssetBundleCrc: 1
m_UseAssetBundleCrcForCachedBundles: 1
m_UseUWRForLocalBundles: 0
m_Timeout: 0
m_ChunkedTransfer: 0
m_RedirectLimit: -1
m_RetryCount: 0
m_BuildPath:
m_Id: 2ae6a7549097a9b4caefe44b61b1fcc5
m_LoadPath:
m_Id: 631ec5c677ab8084fb9be9b9ee39c6ef
m_BundleMode: 0
m_AssetBundleProviderType:
m_AssemblyName: Unity.ResourceManager, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
m_ClassName: UnityEngine.ResourceManagement.ResourceProviders.AssetBundleProvider
m_BundleNaming: 0
m_AssetLoadMode: 0
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: d477ca02df8260c4ba977ebfe3a551fb
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,16 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 5834b5087d578d24c926ce20cd31e6d6, type: 3}
m_Name: Shared_En_ContentUpdateGroupSchema
m_EditorClassIdentifier:
m_Group: {fileID: 11400000, guid: b05cfdc6bbe312248b8be84a1ff01e32, type: 2}
m_StaticContent: 0
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 7f3b2267eaa2569429428b05d408314e
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,45 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: e5d17a21594effb4e9591490b009e7aa, type: 3}
m_Name: Shared_Es_BundledAssetGroupSchema
m_EditorClassIdentifier:
m_Group: {fileID: 11400000, guid: 0418ce3bee30b98409aa4d268d7d89d7, type: 2}
m_InternalBundleIdMode: 1
m_Compression: 1
m_IncludeAddressInCatalog: 1
m_IncludeGUIDInCatalog: 1
m_IncludeLabelsInCatalog: 1
m_InternalIdNamingMode: 0
m_CacheClearBehavior: 0
m_IncludeInBuild: 1
m_BundledAssetProviderType:
m_AssemblyName: Unity.ResourceManager, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
m_ClassName: UnityEngine.ResourceManagement.ResourceProviders.BundledAssetProvider
m_ForceUniqueProvider: 0
m_UseAssetBundleCache: 1
m_UseAssetBundleCrc: 1
m_UseAssetBundleCrcForCachedBundles: 1
m_UseUWRForLocalBundles: 0
m_Timeout: 0
m_ChunkedTransfer: 0
m_RedirectLimit: -1
m_RetryCount: 0
m_BuildPath:
m_Id: 2ae6a7549097a9b4caefe44b61b1fcc5
m_LoadPath:
m_Id: 631ec5c677ab8084fb9be9b9ee39c6ef
m_BundleMode: 0
m_AssetBundleProviderType:
m_AssemblyName: Unity.ResourceManager, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
m_ClassName: UnityEngine.ResourceManagement.ResourceProviders.AssetBundleProvider
m_BundleNaming: 0
m_AssetLoadMode: 0
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 196a7a55916e25f4896599ddb2660f7a
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,16 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 5834b5087d578d24c926ce20cd31e6d6, type: 3}
m_Name: Shared_Es_ContentUpdateGroupSchema
m_EditorClassIdentifier:
m_Group: {fileID: 11400000, guid: 0418ce3bee30b98409aa4d268d7d89d7, type: 2}
m_StaticContent: 0
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: a40977b297f51304cb63f1a2787ea4a4
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,45 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: e5d17a21594effb4e9591490b009e7aa, type: 3}
m_Name: Shared_Ja_BundledAssetGroupSchema
m_EditorClassIdentifier:
m_Group: {fileID: 11400000, guid: 6e0c8dfd5e5e1c74b80cecc21df2d9cd, type: 2}
m_InternalBundleIdMode: 1
m_Compression: 1
m_IncludeAddressInCatalog: 1
m_IncludeGUIDInCatalog: 1
m_IncludeLabelsInCatalog: 1
m_InternalIdNamingMode: 0
m_CacheClearBehavior: 0
m_IncludeInBuild: 1
m_BundledAssetProviderType:
m_AssemblyName: Unity.ResourceManager, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
m_ClassName: UnityEngine.ResourceManagement.ResourceProviders.BundledAssetProvider
m_ForceUniqueProvider: 0
m_UseAssetBundleCache: 1
m_UseAssetBundleCrc: 1
m_UseAssetBundleCrcForCachedBundles: 1
m_UseUWRForLocalBundles: 0
m_Timeout: 0
m_ChunkedTransfer: 0
m_RedirectLimit: -1
m_RetryCount: 0
m_BuildPath:
m_Id: 2ae6a7549097a9b4caefe44b61b1fcc5
m_LoadPath:
m_Id: 631ec5c677ab8084fb9be9b9ee39c6ef
m_BundleMode: 0
m_AssetBundleProviderType:
m_AssemblyName: Unity.ResourceManager, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
m_ClassName: UnityEngine.ResourceManagement.ResourceProviders.AssetBundleProvider
m_BundleNaming: 0
m_AssetLoadMode: 0
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: a2d74ab6ace13ab4ba87121fa11aea2c
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,16 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 5834b5087d578d24c926ce20cd31e6d6, type: 3}
m_Name: Shared_Ja_ContentUpdateGroupSchema
m_EditorClassIdentifier:
m_Group: {fileID: 11400000, guid: 6e0c8dfd5e5e1c74b80cecc21df2d9cd, type: 2}
m_StaticContent: 0
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: b4ec695ab7e7a7848a6e4df4156f5091
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,45 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: e5d17a21594effb4e9591490b009e7aa, type: 3}
m_Name: Shared_Pt_BundledAssetGroupSchema
m_EditorClassIdentifier:
m_Group: {fileID: 11400000, guid: f0205c268777acb459dddbb4690d7c27, type: 2}
m_InternalBundleIdMode: 1
m_Compression: 1
m_IncludeAddressInCatalog: 1
m_IncludeGUIDInCatalog: 1
m_IncludeLabelsInCatalog: 1
m_InternalIdNamingMode: 0
m_CacheClearBehavior: 0
m_IncludeInBuild: 1
m_BundledAssetProviderType:
m_AssemblyName: Unity.ResourceManager, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
m_ClassName: UnityEngine.ResourceManagement.ResourceProviders.BundledAssetProvider
m_ForceUniqueProvider: 0
m_UseAssetBundleCache: 1
m_UseAssetBundleCrc: 1
m_UseAssetBundleCrcForCachedBundles: 1
m_UseUWRForLocalBundles: 0
m_Timeout: 0
m_ChunkedTransfer: 0
m_RedirectLimit: -1
m_RetryCount: 0
m_BuildPath:
m_Id: 2ae6a7549097a9b4caefe44b61b1fcc5
m_LoadPath:
m_Id: 631ec5c677ab8084fb9be9b9ee39c6ef
m_BundleMode: 0
m_AssetBundleProviderType:
m_AssemblyName: Unity.ResourceManager, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
m_ClassName: UnityEngine.ResourceManagement.ResourceProviders.AssetBundleProvider
m_BundleNaming: 0
m_AssetLoadMode: 0
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 7219f667c0a45a642a5b943eb602dffc
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,16 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 5834b5087d578d24c926ce20cd31e6d6, type: 3}
m_Name: Shared_Pt_ContentUpdateGroupSchema
m_EditorClassIdentifier:
m_Group: {fileID: 11400000, guid: f0205c268777acb459dddbb4690d7c27, type: 2}
m_StaticContent: 0
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 6f490fe007fca8342aceea06f8b0e054
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,45 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: e5d17a21594effb4e9591490b009e7aa, type: 3}
m_Name: Shared_Ru_BundledAssetGroupSchema
m_EditorClassIdentifier:
m_Group: {fileID: 11400000, guid: d029169cdb68ba848a0f0260aeddae55, type: 2}
m_InternalBundleIdMode: 1
m_Compression: 1
m_IncludeAddressInCatalog: 1
m_IncludeGUIDInCatalog: 1
m_IncludeLabelsInCatalog: 1
m_InternalIdNamingMode: 0
m_CacheClearBehavior: 0
m_IncludeInBuild: 1
m_BundledAssetProviderType:
m_AssemblyName: Unity.ResourceManager, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
m_ClassName: UnityEngine.ResourceManagement.ResourceProviders.BundledAssetProvider
m_ForceUniqueProvider: 0
m_UseAssetBundleCache: 1
m_UseAssetBundleCrc: 1
m_UseAssetBundleCrcForCachedBundles: 1
m_UseUWRForLocalBundles: 0
m_Timeout: 0
m_ChunkedTransfer: 0
m_RedirectLimit: -1
m_RetryCount: 0
m_BuildPath:
m_Id: 2ae6a7549097a9b4caefe44b61b1fcc5
m_LoadPath:
m_Id: 631ec5c677ab8084fb9be9b9ee39c6ef
m_BundleMode: 0
m_AssetBundleProviderType:
m_AssemblyName: Unity.ResourceManager, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
m_ClassName: UnityEngine.ResourceManagement.ResourceProviders.AssetBundleProvider
m_BundleNaming: 0
m_AssetLoadMode: 0
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 955397ef95649794a87b3c52b818f5c1
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,16 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 5834b5087d578d24c926ce20cd31e6d6, type: 3}
m_Name: Shared_Ru_ContentUpdateGroupSchema
m_EditorClassIdentifier:
m_Group: {fileID: 11400000, guid: d029169cdb68ba848a0f0260aeddae55, type: 2}
m_StaticContent: 0
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 5012207ff0e21364d9462964eb6374ef
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,45 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: e5d17a21594effb4e9591490b009e7aa, type: 3}
m_Name: Shared_Typography_BundledAssetGroupSchema
m_EditorClassIdentifier:
m_Group: {fileID: 11400000, guid: 1051221ba75fcea47b67648acedc5383, type: 2}
m_InternalBundleIdMode: 1
m_Compression: 1
m_IncludeAddressInCatalog: 1
m_IncludeGUIDInCatalog: 1
m_IncludeLabelsInCatalog: 1
m_InternalIdNamingMode: 0
m_CacheClearBehavior: 0
m_IncludeInBuild: 1
m_BundledAssetProviderType:
m_AssemblyName: Unity.ResourceManager, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
m_ClassName: UnityEngine.ResourceManagement.ResourceProviders.BundledAssetProvider
m_ForceUniqueProvider: 0
m_UseAssetBundleCache: 1
m_UseAssetBundleCrc: 1
m_UseAssetBundleCrcForCachedBundles: 1
m_UseUWRForLocalBundles: 0
m_Timeout: 0
m_ChunkedTransfer: 0
m_RedirectLimit: -1
m_RetryCount: 0
m_BuildPath:
m_Id: 2ae6a7549097a9b4caefe44b61b1fcc5
m_LoadPath:
m_Id: 631ec5c677ab8084fb9be9b9ee39c6ef
m_BundleMode: 0
m_AssetBundleProviderType:
m_AssemblyName: Unity.ResourceManager, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
m_ClassName: UnityEngine.ResourceManagement.ResourceProviders.AssetBundleProvider
m_BundleNaming: 0
m_AssetLoadMode: 0
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: b39cbcc4de75ad6468842c36c7e5138d
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,16 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 5834b5087d578d24c926ce20cd31e6d6, type: 3}
m_Name: Shared_Typography_ContentUpdateGroupSchema
m_EditorClassIdentifier:
m_Group: {fileID: 11400000, guid: 1051221ba75fcea47b67648acedc5383, type: 2}
m_StaticContent: 0
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 6bd7e9bcac1e24042b79a54f47f52106
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,30 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: bbb281ee3bf0b054c82ac2347e9e782c, type: 3}
m_Name: Shared_En
m_EditorClassIdentifier:
m_GroupName: Shared_En
m_Data:
m_SerializedData: []
m_GUID: 29507884f3280da4dafc16e8e5f4b2bb
m_SerializeEntries:
- m_GUID: 35bfbd48717731341844dee0f2cf0e25
m_Address: Sprite/Obj_en
m_ReadOnly: 0
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
m_ReadOnly: 0
m_Settings: {fileID: 11400000, guid: 77169ce22e430f64fb36c771815a4a7b, type: 2}
m_SchemaSet:
m_Schemas:
- {fileID: 11400000, guid: d477ca02df8260c4ba977ebfe3a551fb, type: 2}
- {fileID: 11400000, guid: 7f3b2267eaa2569429428b05d408314e, type: 2}
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: b05cfdc6bbe312248b8be84a1ff01e32
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,30 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: bbb281ee3bf0b054c82ac2347e9e782c, type: 3}
m_Name: Shared_Es
m_EditorClassIdentifier:
m_GroupName: Shared_Es
m_Data:
m_SerializedData: []
m_GUID: 829895de7eaa2a34183c7ef64d0dce86
m_SerializeEntries:
- m_GUID: e3eb91f33227ac24a91c0f7bedb3cade
m_Address: Sprite/Obj_es
m_ReadOnly: 0
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
m_ReadOnly: 0
m_Settings: {fileID: 11400000, guid: 77169ce22e430f64fb36c771815a4a7b, type: 2}
m_SchemaSet:
m_Schemas:
- {fileID: 11400000, guid: 196a7a55916e25f4896599ddb2660f7a, type: 2}
- {fileID: 11400000, guid: a40977b297f51304cb63f1a2787ea4a4, type: 2}
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 0418ce3bee30b98409aa4d268d7d89d7
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,30 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: bbb281ee3bf0b054c82ac2347e9e782c, type: 3}
m_Name: Shared_Ja
m_EditorClassIdentifier:
m_GroupName: Shared_Ja
m_Data:
m_SerializedData: []
m_GUID: 3c94b26b5824a4d4994401b840a0eb17
m_SerializeEntries:
- m_GUID: 44a964dd5827e044387ce89002c789ed
m_Address: Sprite/Obj_ja-JP
m_ReadOnly: 0
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
m_ReadOnly: 0
m_Settings: {fileID: 11400000, guid: 77169ce22e430f64fb36c771815a4a7b, type: 2}
m_SchemaSet:
m_Schemas:
- {fileID: 11400000, guid: a2d74ab6ace13ab4ba87121fa11aea2c, type: 2}
- {fileID: 11400000, guid: b4ec695ab7e7a7848a6e4df4156f5091, type: 2}
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 6e0c8dfd5e5e1c74b80cecc21df2d9cd
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,30 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: bbb281ee3bf0b054c82ac2347e9e782c, type: 3}
m_Name: Shared_Pt
m_EditorClassIdentifier:
m_GroupName: Shared_Pt
m_Data:
m_SerializedData: []
m_GUID: 93a7fef7294edab48960d5cfe05bc589
m_SerializeEntries:
- m_GUID: 7b906b9ce46cacd4d9937d1dc7af0048
m_Address: Sprite/Obj_pt-BR
m_ReadOnly: 0
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
m_ReadOnly: 0
m_Settings: {fileID: 11400000, guid: 77169ce22e430f64fb36c771815a4a7b, type: 2}
m_SchemaSet:
m_Schemas:
- {fileID: 11400000, guid: 7219f667c0a45a642a5b943eb602dffc, type: 2}
- {fileID: 11400000, guid: 6f490fe007fca8342aceea06f8b0e054, type: 2}
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: f0205c268777acb459dddbb4690d7c27
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,30 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: bbb281ee3bf0b054c82ac2347e9e782c, type: 3}
m_Name: Shared_Ru
m_EditorClassIdentifier:
m_GroupName: Shared_Ru
m_Data:
m_SerializedData: []
m_GUID: bc556d622a58aee4ea7684147eb2e432
m_SerializeEntries:
- m_GUID: b636657f158aa9d4a85197ee4e1e072c
m_Address: Sprite/Obj_ru
m_ReadOnly: 0
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
m_ReadOnly: 0
m_Settings: {fileID: 11400000, guid: 77169ce22e430f64fb36c771815a4a7b, type: 2}
m_SchemaSet:
m_Schemas:
- {fileID: 11400000, guid: 955397ef95649794a87b3c52b818f5c1, type: 2}
- {fileID: 11400000, guid: 5012207ff0e21364d9462964eb6374ef, type: 2}
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: d029169cdb68ba848a0f0260aeddae55
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,49 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: bbb281ee3bf0b054c82ac2347e9e782c, type: 3}
m_Name: Shared_Typography
m_EditorClassIdentifier:
m_GroupName: Shared_Typography
m_Data:
m_SerializedData: []
m_GUID: 7663edc0b4aef67468c672b641025c85
m_SerializeEntries:
- m_GUID: e41dce1e1d6e0564b95054b5c8b8bb4c
m_Address: Typography/Profile/ZhHans
m_ReadOnly: 0
m_SerializedLabels:
- Typography
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 67cd590df91846b4d81698d68426e616
m_Address: Typography/Profile/Japanese
m_ReadOnly: 0
m_SerializedLabels:
- Typography
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: d53382c947b5edc4a88216c5fcf37b38
m_Address: Typography/Profile/Latin
m_ReadOnly: 0
m_SerializedLabels:
- Typography
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: f82150d4b378dc441884eb47d4f67b7c
m_Address: Typography/Profile/Russian
m_ReadOnly: 0
m_SerializedLabels:
- Typography
FlaggedDuringContentUpdateRestriction: 0
m_ReadOnly: 0
m_Settings: {fileID: 11400000, guid: 77169ce22e430f64fb36c771815a4a7b, type: 2}
m_SchemaSet:
m_Schemas:
- {fileID: 11400000, guid: b39cbcc4de75ad6468842c36c7e5138d, type: 2}
- {fileID: 11400000, guid: 6bd7e9bcac1e24042b79a54f47f52106, type: 2}
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 1051221ba75fcea47b67648acedc5383
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -1,260 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using AibisDream.Kit;
using AibisDream.SaveSystem;
using FMOD;
using FMOD.Studio;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NUnit.Framework;
namespace AibisDream.SystemEditor.Tests
{
public sealed class AudioGlobalParameterSnapshotTests
{
[Test]
public void SetFromYarn_TracksOnlySuccessfulUniqueNames()
{
var api = new FakeFmodGlobalParameterApi();
api.SetByNameResults["missing"] = RESULT.ERR_EVENT_NOTFOUND;
var registry = new YarnGlobalParameterRegistry(api);
Assert.That(registry.SetFromYarn("reverb", 0.4f), Is.EqualTo(RESULT.OK));
Assert.That(registry.SetFromYarn("REVERB", 0.7f), Is.EqualTo(RESULT.OK));
Assert.That(
registry.SetFromYarn("missing", 1f),
Is.EqualTo(RESULT.ERR_EVENT_NOTFOUND));
Assert.That(registry.TrackedNames, Has.Count.EqualTo(1));
Assert.That(registry.TrackedNames.Single(), Is.EqualTo("reverb"));
Assert.That(api.SetByNameCalls[0].IgnoreSeekSpeed, Is.False);
Assert.That(api.SetByNameCalls[1].IgnoreSeekSpeed, Is.False);
}
[Test]
public void CaptureFinalValues_ReadsFmodFinalValueAndRetainsFailuresForRetry()
{
var api = new FakeFmodGlobalParameterApi();
api.Reads["stage"] = new ParameterRead(9f, 2.37f, RESULT.OK);
api.Reads["missing"] = new ParameterRead(0f, 0f, RESULT.ERR_EVENT_NOTFOUND);
var registry = new YarnGlobalParameterRegistry(api);
registry.SetFromYarn("stage", 9f);
registry.SetFromYarn("missing", 1f);
var warnings = new List<string>();
var snapshot = registry.CaptureFinalValues(warnings.Add);
Assert.That(snapshot, Has.Count.EqualTo(1));
Assert.That(snapshot["stage"], Is.EqualTo(2.37f));
Assert.That(snapshot["stage"], Is.Not.EqualTo(9f));
Assert.That(registry.TrackedNames, Is.EquivalentTo(new[] { "stage", "missing" }));
Assert.That(warnings, Has.Count.EqualTo(1));
StringAssert.Contains("missing", warnings[0]);
}
[Test]
public void ResetToDefaults_RemovesOnlyParametersThatFmodResetSuccessfully()
{
var goodId = new PARAMETER_ID { data1 = 1, data2 = 2 };
var failedId = new PARAMETER_ID { data1 = 3, data2 = 4 };
var api = new FakeFmodGlobalParameterApi();
api.Descriptions["good"] = new ParameterDescriptionRead(
new PARAMETER_DESCRIPTION { id = goodId, defaultvalue = 0.25f },
RESULT.OK);
api.Descriptions["failed"] = new ParameterDescriptionRead(
new PARAMETER_DESCRIPTION { id = failedId, defaultvalue = 0.75f },
RESULT.OK);
api.SetByIdResults[failedId] = RESULT.ERR_INVALID_PARAM;
var registry = new YarnGlobalParameterRegistry(api);
registry.SetFromYarn("good", 1f);
registry.SetFromYarn("failed", 1f);
var warnings = new List<string>();
registry.ResetToDefaults(warnings.Add);
Assert.That(registry.TrackedNames, Is.EquivalentTo(new[] { "failed" }));
Assert.That(api.SetByIdCalls, Has.Count.EqualTo(2));
var goodReset = api.SetByIdCalls.Single(call => call.Id.Equals(goodId));
Assert.That(goodReset.Value, Is.EqualTo(0.25f));
Assert.That(goodReset.IgnoreSeekSpeed, Is.True);
Assert.That(warnings, Has.Count.EqualTo(1));
StringAssert.Contains("failed", warnings[0]);
}
[Test]
public void RestoreFinalValues_JumpsImmediatelyThenNextYarnTargetUsesSeekSpeed()
{
var api = new FakeFmodGlobalParameterApi();
api.SetByNameResults["missing"] = RESULT.ERR_EVENT_NOTFOUND;
var registry = new YarnGlobalParameterRegistry(api);
var warnings = new List<string>();
registry.RestoreFinalValues(
new Dictionary<string, float>
{
["stage"] = 2.37f,
["missing"] = 1f
},
warnings.Add);
registry.SetFromYarn("stage", 3f);
Assert.That(api.SetByNameCalls, Has.Count.EqualTo(3));
Assert.That(api.SetByNameCalls[0].Value, Is.EqualTo(2.37f));
Assert.That(api.SetByNameCalls[0].IgnoreSeekSpeed, Is.True);
Assert.That(api.SetByNameCalls[1].Name, Is.EqualTo("missing"));
Assert.That(api.SetByNameCalls[1].IgnoreSeekSpeed, Is.True);
Assert.That(api.SetByNameCalls[2].Value, Is.EqualTo(3f));
Assert.That(api.SetByNameCalls[2].IgnoreSeekSpeed, Is.False);
Assert.That(registry.TrackedNames, Is.EquivalentTo(new[] { "stage" }));
Assert.That(warnings, Has.Count.EqualTo(1));
StringAssert.Contains("missing", warnings[0]);
}
[Test]
public void AudioSnapshotDto_RoundTripsFinalValuesWithoutChangingSchema()
{
var source = new SaveSnapshot();
source.sections[SnapshotProviderIds.Audio] = new AudioSnapshotDto
{
ambState = "Dream",
yarnGlobalParameters = new Dictionary<string, float>
{
["reverb"] = 0.42f,
["hs1LogStage"] = 2.37f
}
};
var json = JsonConvert.SerializeObject(source);
var restored = JsonConvert.DeserializeObject<SaveSnapshot>(json);
var audio = ((JObject)restored.sections[SnapshotProviderIds.Audio])
.ToObject<AudioSnapshotDto>();
Assert.That(restored.schemaVersion, Is.EqualTo(2));
Assert.That(SaveSnapshotSchema.CurrentVersion, Is.EqualTo(2));
Assert.That(audio.yarnGlobalParameters["reverb"], Is.EqualTo(0.42f));
Assert.That(audio.yarnGlobalParameters["hs1LogStage"], Is.EqualTo(2.37f));
}
private readonly struct SetByNameCall
{
internal SetByNameCall(string name, float value, bool ignoreSeekSpeed)
{
Name = name;
Value = value;
IgnoreSeekSpeed = ignoreSeekSpeed;
}
internal string Name { get; }
internal float Value { get; }
internal bool IgnoreSeekSpeed { get; }
}
private readonly struct SetByIdCall
{
internal SetByIdCall(PARAMETER_ID id, float value, bool ignoreSeekSpeed)
{
Id = id;
Value = value;
IgnoreSeekSpeed = ignoreSeekSpeed;
}
internal PARAMETER_ID Id { get; }
internal float Value { get; }
internal bool IgnoreSeekSpeed { get; }
}
private readonly struct ParameterRead
{
internal ParameterRead(float value, float finalValue, RESULT result)
{
Value = value;
FinalValue = finalValue;
Result = result;
}
internal float Value { get; }
internal float FinalValue { get; }
internal RESULT Result { get; }
}
private readonly struct ParameterDescriptionRead
{
internal ParameterDescriptionRead(
PARAMETER_DESCRIPTION description,
RESULT result)
{
Description = description;
Result = result;
}
internal PARAMETER_DESCRIPTION Description { get; }
internal RESULT Result { get; }
}
private sealed class FakeFmodGlobalParameterApi : IFmodGlobalParameterApi
{
internal readonly Dictionary<string, RESULT> SetByNameResults =
new(System.StringComparer.OrdinalIgnoreCase);
internal readonly Dictionary<string, ParameterRead> Reads =
new(System.StringComparer.OrdinalIgnoreCase);
internal readonly Dictionary<string, ParameterDescriptionRead> Descriptions =
new(System.StringComparer.OrdinalIgnoreCase);
internal readonly Dictionary<PARAMETER_ID, RESULT> SetByIdResults = new();
internal readonly List<SetByNameCall> SetByNameCalls = new();
internal readonly List<SetByIdCall> SetByIdCalls = new();
public RESULT SetParameterByName(
string name,
float value,
bool ignoreSeekSpeed)
{
SetByNameCalls.Add(new SetByNameCall(name, value, ignoreSeekSpeed));
return SetByNameResults.TryGetValue(name, out var result)
? result
: RESULT.OK;
}
public RESULT GetParameterByName(
string name,
out float value,
out float finalValue)
{
if (Reads.TryGetValue(name, out var read))
{
value = read.Value;
finalValue = read.FinalValue;
return read.Result;
}
value = 0f;
finalValue = 0f;
return RESULT.OK;
}
public RESULT GetParameterDescriptionByName(
string name,
out PARAMETER_DESCRIPTION description)
{
if (Descriptions.TryGetValue(name, out var read))
{
description = read.Description;
return read.Result;
}
description = default;
return RESULT.ERR_EVENT_NOTFOUND;
}
public RESULT SetParameterById(
PARAMETER_ID id,
float value,
bool ignoreSeekSpeed)
{
SetByIdCalls.Add(new SetByIdCall(id, value, ignoreSeekSpeed));
return SetByIdResults.TryGetValue(id, out var result)
? result
: RESULT.OK;
}
}
}
}
-123
View File
@@ -1,123 +0,0 @@
using System.Collections.Generic;
using System.Globalization;
using System.Text.RegularExpressions;
using AibisDream.Utility;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
using Yarn.Markup;
using Yarn.Unity;
namespace AibisDream.SystemEditor.Tests
{
public sealed class AutoNextMetadataTests
{
[Test]
public void AutoNextWithoutParameter_UsesExistingFixedDelay()
{
var lineInfo = LineInfo.Generate(CreateLine("auto_next"));
Assert.That(lineInfo.isAutoSkip, Is.True);
Assert.That(lineInfo.autoNextDelaySeconds, Is.Null);
Assert.That(lineInfo.CalcAutoNextDelayTime(), Is.EqualTo(ConstRef.FixedDelay));
}
[Test]
public void AutoNextWithParameter_UsesSpecifiedSeconds()
{
var lineInfo = LineInfo.Generate(CreateLine("auto_next:4.5"));
Assert.That(lineInfo.isAutoSkip, Is.True);
Assert.That(lineInfo.autoNextDelaySeconds, Is.EqualTo(4.5f));
Assert.That(lineInfo.CalcAutoNextDelayTime(), Is.EqualTo(4500));
}
[Test]
public void AutoNextWithZeroDelay_IsValid()
{
var lineInfo = LineInfo.Generate(CreateLine("auto_next:0"));
Assert.That(lineInfo.isAutoSkip, Is.True);
Assert.That(lineInfo.autoNextDelaySeconds, Is.EqualTo(0f));
Assert.That(lineInfo.CalcAutoNextDelayTime(), Is.Zero);
}
[Test]
public void AutoNextParameter_UsesInvariantCulture()
{
var originalCulture = CultureInfo.CurrentCulture;
try
{
CultureInfo.CurrentCulture = new CultureInfo("de-DE");
var lineInfo = LineInfo.Generate(CreateLine("auto_next:4.5"));
Assert.That(lineInfo.autoNextDelaySeconds, Is.EqualTo(4.5f));
Assert.That(lineInfo.CalcAutoNextDelayTime(), Is.EqualTo(4500));
}
finally
{
CultureInfo.CurrentCulture = originalCulture;
}
}
[TestCase("auto_next:")]
[TestCase("auto_next:abc")]
[TestCase("auto_next:-1")]
[TestCase("auto_next:NaN")]
[TestCase("auto_next:Infinity")]
[TestCase("auto_next:2147484")]
public void AutoNextWithInvalidParameter_FallsBackToExistingFixedDelay(string metadata)
{
LogAssert.Expect(LogType.Warning, new Regex("auto_next 参数无效"));
var lineInfo = LineInfo.Generate(CreateLine(metadata));
Assert.That(lineInfo.isAutoSkip, Is.True);
Assert.That(lineInfo.autoNextDelaySeconds, Is.Null);
Assert.That(lineInfo.CalcAutoNextDelayTime(), Is.EqualTo(ConstRef.FixedDelay));
}
[TestCase("auto_next_extra")]
[TestCase("auto_next_extra:4.5")]
[TestCase("AUTO_NEXT")]
[TestCase("AUTO_NEXT:4.5")]
public void SimilarMetadata_IsNotRecognized(string metadata)
{
var line = CreateLine(metadata);
Assert.That(line.IsAutoSkipLine(), Is.False);
Assert.That(line.TryGetAutoNextDelaySeconds(out _), Is.False);
}
[Test]
public void ParameterizedTag_TakesPrecedenceOverPlainTag()
{
var lineInfo = LineInfo.Generate(CreateLine("auto_next", "auto_next:2.5"));
Assert.That(lineInfo.isAutoSkip, Is.True);
Assert.That(lineInfo.autoNextDelaySeconds, Is.EqualTo(2.5f));
Assert.That(lineInfo.CalcAutoNextDelayTime(), Is.EqualTo(2500));
}
[Test]
public void MultipleParameterizedTags_UseFirstAndWarn()
{
LogAssert.Expect(LogType.Warning, new Regex("存在多个 auto_next 参数标签"));
var lineInfo = LineInfo.Generate(CreateLine("auto_next:2.5", "auto_next:4.5"));
Assert.That(lineInfo.autoNextDelaySeconds, Is.EqualTo(2.5f));
Assert.That(lineInfo.CalcAutoNextDelayTime(), Is.EqualTo(2500));
}
private static LocalizedLine CreateLine(params string[] metadata)
{
return new LocalizedLine
{
TextID = "line:auto-next-test",
Metadata = metadata,
Text = new MarkupParseResult("Test line", new List<MarkupAttribute>())
};
}
}
}
@@ -1,117 +0,0 @@
#if UNITY_EDITOR
using System.Linq;
using AibisDream.Kit;
using AibisDream.UI;
using AibisDream.Utility;
using NUnit.Framework;
using UnityEditor;
using UnityEngine;
namespace AibisDream.DeveloperMode.Editor
{
public sealed class DeveloperModeCoreTests
{
[Test]
public void RuntimeLogBufferEvictsOldestAndPreservesOrder()
{
var buffer = new RuntimeLogBuffer(3);
buffer.Add(LogEntry.System(LogLevel.Info, LogCategory.General, "one"));
buffer.Add(LogEntry.System(LogLevel.Warning, LogCategory.Save, "two"));
buffer.Add(LogEntry.System(LogLevel.Error, LogCategory.Yarn, "three"));
buffer.Add(LogEntry.System(LogLevel.Fatal, LogCategory.Scene, "four"));
var snapshot = buffer.Snapshot();
Assert.That(snapshot.Select(item => item.Message), Is.EqualTo(new[] { "two", "three", "four" }));
Assert.That(snapshot.Select(item => item.Sequence), Is.Ordered.Ascending);
Assert.That(snapshot.Length, Is.EqualTo(buffer.Capacity));
}
[Test]
public void RuntimeLogBufferAcceptsConcurrentWritersAndClears()
{
var buffer = new RuntimeLogBuffer(128);
System.Threading.Tasks.Parallel.For(0, 1000, index =>
buffer.Add(LogEntry.System(LogLevel.Info, LogCategory.General, index.ToString())));
var snapshot = buffer.Snapshot();
Assert.That(snapshot.Length, Is.EqualTo(128));
Assert.That(snapshot.Select(item => item.Sequence), Is.Ordered.Ascending);
buffer.Clear();
Assert.That(buffer.Snapshot(), Is.Empty);
}
[Test]
public void YarnStorageTypedSettersAndDebugSnapshotAreReadOnlyAndConsistent()
{
var go = new GameObject("YarnVariableStorage Test");
try
{
var storage = go.AddComponent<YarnVariableStorage>();
storage.SetValue("$score", 12.5f);
storage.SetValue("$global_flag", true);
storage.SetValue("$name", "AIBIS");
var snapshot = DeveloperVariableSnapshot.Capture(storage, null);
Assert.That(snapshot.Select(item => item.Name),
Is.EquivalentTo(new[] { "$score", "$global_flag", "$name" }));
Assert.That(snapshot.Single(item => item.Name == "$score").Value, Is.EqualTo("12.5"));
Assert.That(snapshot.Single(item => item.Name == "$global_flag").IsGlobal, Is.True);
Assert.That(snapshot.All(item => item.HasRuntimeOverride), Is.True);
storage.ClearLocal();
var remaining = DeveloperVariableSnapshot.Capture(storage, null);
Assert.That(remaining.Select(item => item.Name), Is.EqualTo(new[] { "$global_flag" }));
storage.Clear();
Assert.That(DeveloperVariableSnapshot.Capture(storage, null), Is.Empty);
}
finally
{
Object.DestroyImmediate(go);
}
}
[Test]
public void EditorBuildEnablesDeveloperMode()
{
Assert.That(DeveloperModeGate.IsEnabled, Is.True);
}
[Test]
public void PrefabIsConfiguredAndPersistenceReferencesIt()
{
const string prefabPath = "Assets/GameContent/Feature_MainUI/Prefabs/DeveloperModePanel.prefab";
const string scenePath = "Assets/Scenes/Persistence.unity";
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(prefabPath);
Assert.That(prefab, Is.Not.Null);
Assert.That(prefab.activeSelf, Is.False);
var panel = prefab.GetComponent<DeveloperModePanel>();
Assert.That(panel, Is.Not.Null);
Assert.That(panel.IsConfigured, Is.True);
Assert.That(prefab.GetComponent<RectTransform>().sizeDelta, Is.EqualTo(new Vector2(840f, 760f)));
Assert.That(AssetDatabase.GetDependencies(scenePath), Does.Contain(prefabPath));
}
[Test]
public void PanelToggleIsNonModalAndEscapeClosesIt()
{
const string prefabPath = "Assets/GameContent/Feature_MainUI/Prefabs/DeveloperModePanel.prefab";
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(prefabPath);
var instance = Object.Instantiate(prefab);
try
{
var panel = instance.GetComponent<DeveloperModePanel>();
var originalScale = Time.timeScale;
panel.TogglePanel();
Assert.That(panel.IsOpen, Is.True);
Assert.That(Time.timeScale, Is.EqualTo(originalScale));
Assert.That(panel.HandleEscape(), Is.True);
Assert.That(panel.IsOpen, Is.False);
}
finally
{
Object.DestroyImmediate(instance);
}
}
}
}
#endif
@@ -1,476 +0,0 @@
#if UNITY_EDITOR
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using AibisDream.Framework;
using AibisDream.SaveSystem;
using Newtonsoft.Json;
using NUnit.Framework;
using UnityEngine;
namespace AibisDream.DeveloperMode.Editor.Tests
{
public sealed class TestSaveSystemTests
{
private string _root;
private TestSaveRepository _repository;
[SetUp]
public void SetUp()
{
_root = Path.Combine(
Path.GetTempPath(),
"AibisDream-TestSaveTests",
Guid.NewGuid().ToString("N"));
_repository = new TestSaveRepository(_root);
}
[TearDown]
public void TearDown()
{
if (Directory.Exists(_root))
{
Directory.Delete(_root, true);
}
}
[Test]
public void Record_SameAnchor_ReplacesSnapshotAndKeepsFirstSeenOrder()
{
var first = _repository.Record(CreateRequest("ChapterA", "ProjectA", "NodeA", "SceneOne"));
var second = _repository.Record(CreateRequest("ChapterA", "ProjectA", "NodeA", "SceneTwo"));
var scan = _repository.Scan("1.0");
Assert.That(scan.Entries.Count, Is.EqualTo(1));
Assert.That(second.Meta.firstSeenOrder, Is.EqualTo(first.Meta.firstSeenOrder));
Assert.That(scan.Entries[0].Meta.sceneName, Is.EqualTo("SceneTwo"));
Assert.That(_repository.TryLoad(scan.Entries[0], out var snapshot, out var error), Is.True, error);
Assert.That(snapshot.scene.sceneName, Is.EqualTo("SceneTwo"));
}
[Test]
public void Record_ReplacingEarlierEntry_DoesNotMoveItsOrder()
{
_repository.Record(CreateRequest("ChapterA", "ProjectA", "NodeA", "SceneA"));
_repository.Record(CreateRequest("ChapterA", "ProjectA", "NodeB", "SceneB"));
_repository.Record(CreateRequest("ChapterA", "ProjectA", "NodeA", "SceneA2"));
var entries = _repository.Scan().Entries.ToArray();
Assert.That(entries.Select(item => item.NodeName), Is.EqualTo(new[] { "NodeA", "NodeB" }));
Assert.That(entries[0].Meta.firstSeenOrder, Is.LessThan(entries[1].Meta.firstSeenOrder));
}
[Test]
public void Scan_RetainsMissingSnapshotAsInvalid()
{
var entry = _repository.Record(CreateRequest("ChapterA", "ProjectA", "NodeA", "SceneA"));
File.Delete(entry.SnapshotPath);
var scan = _repository.Scan();
Assert.That(scan.ValidCount, Is.Zero);
Assert.That(scan.InvalidCount, Is.EqualTo(1));
Assert.That(scan.Entries[0].Status, Is.EqualTo(TestSaveEntryStatus.MissingSnapshot));
}
[Test]
public void Scan_RetainsOrphanSnapshotWithBrokenMeta()
{
var directory = Path.Combine(_root, "chapter", "broken");
Directory.CreateDirectory(directory);
File.WriteAllText(
Path.Combine(directory, TestSaveRepository.SnapshotFileName),
"{}",
Encoding.UTF8);
var scan = _repository.Scan();
Assert.That(scan.InvalidCount, Is.EqualTo(1));
Assert.That(scan.Entries[0].Status, Is.EqualTo(TestSaveEntryStatus.CorruptMeta));
}
[Test]
public void Validate_CorruptSnapshot_IsRetainedAndDisabledOnNextScan()
{
var entry = _repository.Record(CreateRequest("ChapterA", "ProjectA", "NodeA", "SceneA"));
File.WriteAllText(entry.SnapshotPath, "{broken", Encoding.UTF8);
Assert.That(_repository.Validate(entry, out var error), Is.False);
var rescanned = _repository.Scan();
Assert.That(error, Does.Contain("无法解析"));
Assert.That(rescanned.InvalidCount, Is.EqualTo(1));
Assert.That(rescanned.Entries[0].Status, Is.EqualTo(TestSaveEntryStatus.CorruptSnapshot));
}
[Test]
public void Scan_RecoversCompleteStagingDirectory()
{
var final = Path.Combine(_root, "chapter", "node");
var staging = final + ".__staging";
Directory.CreateDirectory(staging);
var request = CreateRequest("ChapterA", "ProjectA", "NodeA", "SceneA");
var meta = CreateMeta(request, 1);
File.WriteAllText(
Path.Combine(staging, TestSaveRepository.MetaFileName),
JsonConvert.SerializeObject(meta),
Encoding.UTF8);
File.WriteAllText(
Path.Combine(staging, TestSaveRepository.SnapshotFileName),
SnapshotPersistence.Serialize(request.Snapshot),
Encoding.UTF8);
var scan = _repository.Scan();
Assert.That(Directory.Exists(final), Is.True);
Assert.That(Directory.Exists(staging), Is.False);
Assert.That(scan.ValidCount, Is.EqualTo(1));
}
[Test]
public void Record_ConcurrentRequests_ProducesCompleteUniqueEntries()
{
var tasks = Enumerable.Range(0, 12)
.Select(index => Task.Run(() =>
_repository.Record(CreateRequest(
"ChapterA",
"ProjectA",
$"Node{index:00}",
$"Scene{index:00}"))))
.ToArray();
Task.WaitAll(tasks);
var scan = _repository.Scan();
Assert.That(scan.ValidCount, Is.EqualTo(12));
Assert.That(scan.InvalidCount, Is.Zero);
Assert.That(scan.Entries.Select(item => item.Meta.firstSeenOrder).Distinct().Count(), Is.EqualTo(12));
}
[Test]
public void DeleteInvalid_RemovesOnlyInvalidEntries()
{
_repository.Record(CreateRequest("ChapterA", "ProjectA", "NodeA", "SceneA"));
var invalid = _repository.Record(CreateRequest("ChapterA", "ProjectA", "NodeB", "SceneB"));
File.Delete(invalid.SnapshotPath);
var removed = _repository.DeleteInvalid(out var error);
var scan = _repository.Scan();
Assert.That(error, Is.Null);
Assert.That(removed, Is.EqualTo(1));
Assert.That(scan.ValidCount, Is.EqualTo(1));
Assert.That(scan.InvalidCount, Is.Zero);
}
[TestCase(new[] { "hub" }, true)]
[TestCase(new[] { "linear" }, true)]
[TestCase(new[] { "content" }, true)]
[TestCase(new[] { "event" }, false)]
[TestCase(new[] { "hub", "no_save" }, false)]
[TestCase(new string[0], true)]
public void SilentCoverageEvaluator_MatchesSavePointTagSemantics(string[] tags, bool expected)
{
var actual = SavePointEvaluator.EvaluateNodeTagsForAutoSaveSilently(
"Node",
tags,
out _);
Assert.That(actual, Is.EqualTo(expected));
}
[TestCase(new[] { "content" }, NodeSaveTiming.NodeEnter, SaveResumeMode.RestartNode)]
[TestCase(new[] { "interaction" }, NodeSaveTiming.DialogueExit, SaveResumeMode.StateOnly)]
[TestCase(new[] { "content", "save_on_exit" }, NodeSaveTiming.DialogueExit, SaveResumeMode.StateOnly)]
[TestCase(new[] { "event", "save_on_exit" }, NodeSaveTiming.DialogueExit, SaveResumeMode.StateOnly)]
[TestCase(new[] { "interaction", "save_on_exit" }, NodeSaveTiming.DialogueExit, SaveResumeMode.StateOnly)]
[TestCase(new[] { "interaction", "no_save" }, NodeSaveTiming.None, SaveResumeMode.RestartNode)]
[TestCase(new[] { "content", "event" }, NodeSaveTiming.None, SaveResumeMode.RestartNode)]
public void ResolveNodePolicy_MapsNodeTypeAndModifiers(
string[] tags,
NodeSaveTiming expectedTiming,
SaveResumeMode expectedResumeMode)
{
var policy = SavePointEvaluator.ResolveNodePolicy(
"Node",
tags,
out _,
logWarnings: false);
Assert.That(policy.Timing, Is.EqualTo(expectedTiming));
Assert.That(policy.ResumeMode, Is.EqualTo(expectedResumeMode));
}
[Test]
public void Repository_StateOnlySnapshot_PreservesAnchorNode()
{
var request = CreateRequest("ChapterA", "ProjectA", "InteractionNode", "SceneA");
request.Snapshot.anchor.startDialogueOnRestore = false;
request.SaveTrigger = "DialogueExit";
request.ResumeMode = nameof(SaveResumeMode.StateOnly);
request.StartDialogueOnRestore = false;
request.DedupeKey = TestSaveRecorder.BuildDedupeKey(
request.SceneSoName,
request.YarnProjectId,
request.NodeName,
request.ResumeMode);
request.EntryId = TestSaveRepository.StableHash(request.DedupeKey);
var entry = _repository.Record(request);
var loaded = _repository.TryLoad(entry, out var snapshot, out var error);
Assert.That(loaded, Is.True, error);
Assert.That(entry.NodeName, Is.EqualTo("InteractionNode"));
Assert.That(entry.ResumeMode, Is.EqualTo(nameof(SaveResumeMode.StateOnly)));
Assert.That(snapshot.anchor.nodeName, Is.EqualTo("InteractionNode"));
Assert.That(snapshot.anchor.startDialogueOnRestore, Is.False);
}
[Test]
public void SnapshotPersistence_MissingRestoreFlag_IsRejected()
{
const string json =
"{\"schemaVersion\":2,\"anchor\":{\"sceneSoName\":\"ChapterA\"," +
"\"yarnProjectId\":\"ProjectA\",\"nodeName\":\"NodeA\"}}";
Assert.Throws<Newtonsoft.Json.JsonSerializationException>(
() => SnapshotPersistence.Deserialize(json));
}
[Test]
public void SerialTaskQueue_DoesNotDropOrReorderRequests()
{
var queue = new SerialTaskQueue();
var releaseFirst = new TaskCompletionSource<bool>();
var firstStarted = new TaskCompletionSource<bool>();
var order = new List<int>();
var first = queue.Enqueue(async () =>
{
order.Add(1);
firstStarted.SetResult(true);
await releaseFirst.Task;
order.Add(2);
});
var second = queue.Enqueue(() =>
{
order.Add(3);
return Task.CompletedTask;
});
Assert.That(firstStarted.Task.Wait(TimeSpan.FromSeconds(2)), Is.True);
Assert.That(order, Is.EqualTo(new[] { 1 }));
Assert.That(queue.PendingCount, Is.EqualTo(2));
releaseFirst.SetResult(true);
Assert.That(
Task.WaitAll(
new[] { first.Completion, second.Completion },
TimeSpan.FromSeconds(2)),
Is.True);
Assert.That(first.Sequence, Is.LessThan(second.Sequence));
Assert.That(order, Is.EqualTo(new[] { 1, 2, 3 }));
}
[Test]
public void SaveRequest_MapsResumeModeToExplicitAnchorFlag()
{
var enter = SaveRequest.NodeEnter("NodeA", "ProjectA", "ChapterA", 3, 7);
var exit = SaveRequest.DialogueExit("NodeB", "ProjectA", "ChapterA", 3, 9);
Assert.That(enter.AnchorSpec.NodeName, Is.EqualTo("NodeA"));
Assert.That(enter.AnchorSpec.StartDialogueOnRestore, Is.True);
Assert.That(exit.AnchorSpec.NodeName, Is.EqualTo("NodeB"));
Assert.That(exit.AnchorSpec.StartDialogueOnRestore, Is.False);
}
[TestCase(false, "DetourNode", true)]
[TestCase(false, "NoSaveNode", false)]
[TestCase(true, "AnotherNode", true)]
public void ValidateDialogueTiming_NonExitRequestIgnoresLaterDialogueState(
bool explicitCommand,
string currentNodeName,
bool isDialogueRunning)
{
var request = explicitCommand
? SaveRequest.Explicit("SourceNode", "ProjectA", "ChapterA", 3, 7)
: SaveRequest.NodeEnter("SourceNode", "ProjectA", "ChapterA", 3, 7);
var valid = SaveRestoreOrchestrator.ValidateDialogueTiming(
request,
currentDialogueRunId: 99,
currentFlowRevision: 101,
currentNodeName: currentNodeName,
isDialogueRunning: isDialogueRunning,
out var reason);
Assert.That(valid, Is.True, reason);
Assert.That(request.AnchorSpec.NodeName, Is.EqualTo("SourceNode"));
}
[Test]
public void ValidateDialogueTiming_ConsecutiveNodeEnterRequestsRemainEligible()
{
var first = SaveRequest.NodeEnter("FirstNode", "ProjectA", "ChapterA", 3, 7);
var second = SaveRequest.NodeEnter("SecondNode", "ProjectA", "ChapterA", 3, 8);
var firstValid = SaveRestoreOrchestrator.ValidateDialogueTiming(
first,
currentDialogueRunId: 3,
currentFlowRevision: 8,
currentNodeName: "SecondNode",
isDialogueRunning: true,
out var firstReason);
var secondValid = SaveRestoreOrchestrator.ValidateDialogueTiming(
second,
currentDialogueRunId: 3,
currentFlowRevision: 8,
currentNodeName: "SecondNode",
isDialogueRunning: true,
out var secondReason);
Assert.That(firstValid, Is.True, firstReason);
Assert.That(secondValid, Is.True, secondReason);
Assert.That(first.AnchorSpec.NodeName, Is.EqualTo("FirstNode"));
Assert.That(second.AnchorSpec.NodeName, Is.EqualTo("SecondNode"));
}
[Test]
public void ValidateDialogueTiming_DialogueExitAcceptsStableCompletedFlow()
{
var request = SaveRequest.DialogueExit(
"InteractionNode",
"ProjectA",
"ChapterA",
3,
9);
var valid = SaveRestoreOrchestrator.ValidateDialogueTiming(
request,
currentDialogueRunId: 3,
currentFlowRevision: 9,
currentNodeName: null,
isDialogueRunning: false,
out var reason);
Assert.That(valid, Is.True, reason);
}
[TestCase(4, 9, null, false, "dialogue run changed")]
[TestCase(3, 10, null, false, "flow revision changed")]
[TestCase(3, 9, "DetourNode", false, "current Yarn node")]
[TestCase(3, 9, "NoSaveNode", false, "current Yarn node")]
[TestCase(3, 9, "JumpTarget", true, "still running")]
public void ValidateDialogueTiming_DialogueExitRejectsChangedOrRunningFlow(
long currentDialogueRunId,
long currentFlowRevision,
string currentNodeName,
bool isDialogueRunning,
string expectedReason)
{
var request = SaveRequest.DialogueExit(
"InteractionNode",
"ProjectA",
"ChapterA",
3,
9);
var valid = SaveRestoreOrchestrator.ValidateDialogueTiming(
request,
currentDialogueRunId,
currentFlowRevision,
currentNodeName,
isDialogueRunning,
out var reason);
Assert.That(valid, Is.False);
Assert.That(reason, Does.Contain(expectedReason));
}
[Test]
public void InteractionLock_ComposesWithDialogueLock()
{
var gameObject = new GameObject("EventSystemEx-Test");
try
{
var eventSystem = gameObject.AddComponent<EventSystemEx>();
EnumEventSystem.Global.Send(InteractionEventEnum.DialogStart);
var checkpointLock = eventSystem.AcquireInteractionLock("test");
EnumEventSystem.Global.Send(InteractionEventEnum.DialogEnd);
Assert.That(eventSystem.isLocked, Is.True);
checkpointLock.Dispose();
Assert.That(eventSystem.isLocked, Is.False);
}
finally
{
UnityEngine.Object.DestroyImmediate(gameObject);
}
}
private static TestSaveRecordRequest CreateRequest(
string sceneSoName,
string yarnProject,
string nodeName,
string sceneName)
{
var key = TestSaveRecorder.BuildDedupeKey(sceneSoName, yarnProject, nodeName);
return new TestSaveRecordRequest
{
Snapshot = new SaveSnapshot
{
gameVersion = "1.0",
savedAt = "2026-01-01 00:00:00",
scene = new SceneSnapshotDto { sceneName = sceneName },
anchor = new AnchorSnapshot
{
sceneSoName = sceneSoName,
yarnProjectId = yarnProject,
nodeName = nodeName,
startDialogueOnRestore = true
}
},
DedupeKey = key,
EntryId = TestSaveRepository.StableHash(key),
ChapterId = sceneSoName,
ChapterTitle = sceneSoName,
SceneSoName = sceneSoName,
YarnProjectId = yarnProject,
NodeName = nodeName,
SaveTrigger = "NodeEnter",
ResumeMode = nameof(SaveResumeMode.RestartNode),
StartDialogueOnRestore = true,
SceneName = sceneName,
GameVersion = "1.0"
};
}
private static TestSaveMeta CreateMeta(TestSaveRecordRequest request, long order)
{
return new TestSaveMeta
{
entryId = request.EntryId,
dedupeKey = request.DedupeKey,
chapterId = request.ChapterId,
chapterTitle = request.ChapterTitle,
sceneSoName = request.SceneSoName,
yarnProjectId = request.YarnProjectId,
nodeName = request.NodeName,
saveTrigger = request.SaveTrigger,
resumeMode = request.ResumeMode,
startDialogueOnRestore = request.StartDialogueOnRestore,
sceneName = request.SceneName,
firstSeenOrder = order,
firstRecordedAt = "2026-01-01 00:00:00",
lastRecordedAt = "2026-01-01 00:00:00",
snapshotSchemaVersion = SaveSnapshotSchema.CurrentVersion,
gameVersion = request.GameVersion
};
}
}
}
#endif
@@ -1803,6 +1803,14 @@ namespace AibisDream.FrameAnimation.Editor
AddReadOnly(body, "ID", flow.Id);
AddReadOnly(body, "Entry Node", flow.EntryNodeId);
AddBoundProperty(body, serialized, property.FindPropertyRelative("displayName"), "Display Name");
AddBoundProperty(
body,
serialized,
property.FindPropertyRelative("asyncCompletionMode"),
"Async Completion");
body.Add(CreateCallout(
"Only affects async playback when the resolved terminal clip loops.",
"fa-callout--info"));
AddBoundProperty(body, serialized, property.FindPropertyRelative("hasEndBehaviorOverride"), "Override End Behavior");
AddBoundProperty(body, serialized, property.FindPropertyRelative("endBehaviorOverride"), "End Behavior");
@@ -2513,6 +2521,14 @@ namespace AibisDream.FrameAnimation.Editor
EditorGUILayout.PropertyField(property.FindPropertyRelative("entryNodeId"));
}
EditorGUILayout.PropertyField(property.FindPropertyRelative("displayName"));
EditorGUILayout.PropertyField(
property.FindPropertyRelative("asyncCompletionMode"),
new GUIContent(
"Async Completion",
"Only affects async playback when the resolved terminal clip loops."));
EditorGUILayout.HelpBox(
"Async Completion only affects a Flow whose resolved terminal clip loops.",
MessageType.Info);
EditorGUILayout.PropertyField(property.FindPropertyRelative("hasEndBehaviorOverride"));
if (property.FindPropertyRelative("hasEndBehaviorOverride").boolValue)
{
@@ -1,77 +0,0 @@
using AibisDream.FrameAnimation;
using AibisDream.SaveSystem;
using AibisDream.Utility;
using NUnit.Framework;
using UnityEditor;
using UnityEditor.AddressableAssets;
using UnityEngine;
namespace AibisDream.SystemEditor.Tests
{
public sealed class FrameAnimationActorIntegrationTests
{
private const string PrefabPath =
"Assets/Prefabs/FixSystemPrefabs/FrameAnimationActor.prefab";
[Test]
public void ActorType_AppendsFrameAnimationWithoutChangingExistingValues()
{
Assert.That((int)ActorType.Sprite, Is.EqualTo(0));
Assert.That((int)ActorType.Anima, Is.EqualTo(1));
Assert.That((int)ActorType.AnimaEx, Is.EqualTo(2));
Assert.That((int)ActorType.FrameAnimation, Is.EqualTo(3));
Assert.That(
System.Enum.TryParse("FrameAnimation", out ActorType parsed),
Is.True);
Assert.That(parsed, Is.EqualTo(ActorType.FrameAnimation));
}
[Test]
public void Prefab_HasRequiredComponentsAndPersistentAddress()
{
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(PrefabPath);
Assert.That(prefab, Is.Not.Null);
Assert.That(prefab.GetComponent<SpriteRenderer>(), Is.Not.Null);
Assert.That(prefab.GetComponent<FrameAnimationPlayer>(), Is.Not.Null);
Assert.That(prefab.GetComponent<FrameAnimationActor>(), Is.Not.Null);
Assert.That(prefab.GetComponent<FrameAnimationPlayer>().PlayOnEnable, Is.False);
Assert.That(prefab.GetComponent<FrameAnimationPlayer>().Graph, Is.Null);
var guid = AssetDatabase.AssetPathToGUID(PrefabPath);
var entry = AddressableAssetSettingsDefaultObject.Settings.FindAssetEntry(guid);
Assert.That(entry, Is.Not.Null);
Assert.That(entry.address, Is.EqualTo(ConstRef.FrameAnimationActorPrefabName));
}
[Test]
public void CaptureEntry_ReusesActorSnapshotFields()
{
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(PrefabPath);
var actorObject = Object.Instantiate(prefab);
var slotObject = new GameObject("Slot");
try
{
var slot = slotObject.AddComponent<ActorSlot>();
slot.slotName = "test-slot";
slot.sortingLayer = 0;
slot.sortingOrder = 17;
var actor = actorObject.GetComponent<FrameAnimationActor>();
actor.Init("TestActor", slot, ActorType.FrameAnimation);
actor.Show();
ActorEntrySnapshotDto entry = actor.CaptureEntry();
Assert.That(entry.actorName, Is.EqualTo("TestActor"));
Assert.That(entry.slotName, Is.EqualTo("test-slot"));
Assert.That(entry.actorType, Is.EqualTo(nameof(ActorType.FrameAnimation)));
Assert.That(entry.alpha, Is.EqualTo(1f));
Assert.That(entry.stateName, Is.Empty);
}
finally
{
Object.DestroyImmediate(actorObject);
Object.DestroyImmediate(slotObject);
}
}
}
}
@@ -1,577 +0,0 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using AibisDream.Framework;
using AibisDream.MiniGame.Language;
using AibisDream.Utility;
using NUnit.Framework;
using UnityEditor;
using UnityEditor.Localization;
using UnityEngine;
using UnityEngine.Localization;
using UnityEngine.Localization.Settings;
using UnityEngine.Localization.Tables;
using UnityEngine.TestTools;
namespace AibisDream.EditorTests.Huoshan
{
public sealed class ExpressionLocalizationTests
{
private const string CatalogPath =
"Assets/GameContent/Feature_Huoshan/Expression/ExpressionContentCatalog.asset";
private const string ParticleProfilePath =
"Assets/GameContent/Feature_Huoshan/Expression/ExpressionParticleLanguageProfile.asset";
[TestCase(null, false)]
[TestCase("", false)]
[TestCase("plain", false)]
[TestCase("L10N.key", false)]
[TestCase("l10n.key", true)]
public void LocalizedParamPrefix_IsStrict(string value, bool expected)
{
Assert.That(LocalizationKit.IsLocalizedParam(value), Is.EqualTo(expected));
}
[Test]
public void LocalizedParamKey_StripsOnlyKnownPrefix()
{
Assert.That(LocalizationKit.GetL10NParamKey("l10n.hs.exp.log1.target"),
Is.EqualTo("hs.exp.log1.target"));
Assert.That(LocalizationKit.GetL10NParamKey("raw"), Is.EqualTo("raw"));
}
[Test]
public void MissingParamResult_RequiresMatchingLocalizedReference()
{
Assert.That(
LocalizationKit.IsMissingParamResult(
"l10n.hs.exp.pool",
"⟦hs.exp.pool⟧"),
Is.True);
Assert.That(
LocalizationKit.IsMissingParamResult(
"l10n.hs.exp.pool",
"⟦another.key⟧"),
Is.False);
Assert.That(
LocalizationKit.IsMissingParamResult(
"raw",
"⟦raw⟧"),
Is.False);
}
[UnityTest]
public IEnumerator LocalizeParamAsync_UsesExplicitLocaleAndDoesNotFallback()
{
Locale chinese = LocalizationEditorSettings.GetLocale(
new LocaleIdentifier("zh-Hans"));
Locale english = LocalizationEditorSettings.GetLocale(
new LocaleIdentifier("en"));
Assert.That(chinese, Is.Not.Null);
Assert.That(english, Is.Not.Null);
Locale previous = LocalizationSettings.SelectedLocale;
LocalizationSettings.SelectedLocale = english;
Task<string> chineseTask = LocalizationKit.LocalizeParamAsync(
"l10n.hs.exp.log1.target",
chinese);
Task<string> englishTask = LocalizationKit.LocalizeParamAsync(
"l10n.hs.exp.log1.target",
english);
while (!chineseTask.IsCompleted || !englishTask.IsCompleted)
yield return null;
LocalizationSettings.SelectedLocale = previous;
Assert.That(chineseTask.Result, Is.EqualTo("我就是个笑话"));
Assert.That(englishTask.Result, Is.EqualTo("I am nothing but a joke"));
}
[UnityTest]
public IEnumerator LocalizeParamAsync_ReturnsRawTextAndMissingKeyMarker()
{
Task<string> rawTask = LocalizationKit.LocalizeParamAsync("raw text");
while (!rawTask.IsCompleted)
yield return null;
Assert.That(rawTask.Result, Is.EqualTo("raw text"));
Locale chinese = LocalizationEditorSettings.GetLocale(
new LocaleIdentifier("zh-Hans"));
LogAssert.Expect(
LogType.Error,
new Regex(@"Params 缺少条目.*Key=hs\.exp\.missing.*Locale=zh-Hans"));
Task<string> missingTask = LocalizationKit.LocalizeParamAsync(
"l10n.hs.exp.missing",
chinese);
while (!missingTask.IsCompleted)
yield return null;
Assert.That(missingTask.Result, Is.EqualTo("⟦hs.exp.missing⟧"));
}
[Test]
public void LegacyCommonParams_ArePresentInUnityParams()
{
StringTableCollection collection =
LocalizationEditorSettings.GetStringTableCollection(ConstRef.ParamsTable);
Assert.That(collection, Is.Not.Null);
string[] legacyKeys = { "天空", "海报", "植物", "朋友", "情绪", "记忆", "逻辑" };
foreach (string key in legacyKeys)
Assert.That(collection.SharedData.GetEntry(key), Is.Not.Null, key);
Assert.That(AssetDatabase.LoadAssetAtPath<TextAsset>(
"Assets/StreamingAssets/Config/params.csv"), Is.Null);
}
[Test]
public void ExpressionCatalog_IsValidAndCaseSensitive()
{
ExpressionContentCatalog catalog =
AssetDatabase.LoadAssetAtPath<ExpressionContentCatalog>(CatalogPath);
Assert.That(catalog, Is.Not.Null);
Assert.That(catalog.GetValidationErrors(), Is.Empty);
Assert.That(catalog.Rounds.Select(round => round.Id),
Is.EqualTo(new[] { "log1", "log2", "log3" }));
Assert.That(catalog.TryGetRound("log1", out ExpressionRoundDefinition log1), Is.True);
Assert.That(log1.NonTargetParticleCount, Is.EqualTo(16));
Assert.That(catalog.TryGetRound("LOG1", out _), Is.False);
Assert.That(catalog.TryGetRound("missing", out _), Is.False);
}
[Test]
public void ExpressionCatalog_RejectsDuplicateIdsAndNonLocalizedReferences()
{
ExpressionContentCatalog catalog =
ScriptableObject.CreateInstance<ExpressionContentCatalog>();
try
{
var first = new ExpressionRoundDefinition();
var second = new ExpressionRoundDefinition();
ConfigureRound(first, "log1", "l10n.target", "l10n.tokens", 1);
ConfigureRound(second, "log1", "raw", "l10n.tokens", 1);
SetPrivateField(
catalog,
"rounds",
new List<ExpressionRoundDefinition> { first, second });
string errors = string.Join("\n", catalog.GetValidationErrors());
Assert.That(errors, Does.Contain("重复"));
Assert.That(errors, Does.Contain("l10n.*"));
}
finally
{
Object.DestroyImmediate(catalog);
}
}
[Test]
public void TokenParser_TrimsDropsEmptyAndRejectsFullwidthSeparator()
{
Assert.That(
LanguageYarnCommand.TryParseExpressionTokens(
" 哈哈 | | 嘿嘿|呵呵 ",
out var tokens),
Is.True);
Assert.That(tokens, Is.EqualTo(new[] { "哈哈", "嘿嘿", "呵呵" }));
LogAssert.Expect(LogType.Error, new Regex("全角分隔符"));
Assert.That(
LanguageYarnCommand.TryParseExpressionTokens("哈哈|嘿嘿", out _),
Is.False);
Assert.That(
LanguageYarnCommand.TryParseExpressionTokens(" | | ", out _),
Is.False);
}
[Test]
public void CharacterPoolParser_PreservesUnicodeDuplicatesAndRejectsSeparators()
{
string combining = "e\u0301";
Assert.That(
LanguageYarnCommand.TryParseExpressionPool(
$" 中 中 あ {combining} 😀。 ",
out List<string> pool),
Is.True);
Assert.That(
pool,
Is.EqualTo(new[] { "中", "中", "あ", combining, "😀", "。" }));
Assert.That(
LanguageYarnCommand.TryParseExpressionPool("中|あ", out _),
Is.False);
Assert.That(
LanguageYarnCommand.TryParseExpressionPool("中|あ", out _),
Is.False);
Assert.That(
LanguageYarnCommand.TryParseExpressionPool(" \t ", out _),
Is.False);
}
[Test]
public void ParticleLanguageProfile_ResolvesExactPrefixAndFallbackModes()
{
ExpressionParticleLanguageProfile profile =
AssetDatabase.LoadAssetAtPath<ExpressionParticleLanguageProfile>(
ParticleProfilePath);
Assert.That(profile, Is.Not.Null);
Assert.That(profile.GetValidationErrors(), Is.Empty);
Assert.That(
profile.Resolve(new LocaleIdentifier("zh-Hans")).UnitMode,
Is.EqualTo(ExpressionParticleUnitMode.Grapheme));
Assert.That(
profile.Resolve(new LocaleIdentifier("ja-JP")).UnitMode,
Is.EqualTo(ExpressionParticleUnitMode.Grapheme));
foreach (string localeCode in new[] { "en", "es", "ru", "pt-BR" })
{
Assert.That(
profile.Resolve(new LocaleIdentifier(localeCode)).UnitMode,
Is.EqualTo(ExpressionParticleUnitMode.Word),
localeCode);
}
Assert.That(
profile.Resolve(new LocaleIdentifier("en-US")).LocaleCode,
Is.EqualTo("en"));
LogAssert.Expect(
LogType.Warning,
new Regex(@"Locale 'zz-ZZ'.*Grapheme"));
Assert.That(
profile.Resolve(new LocaleIdentifier("zz-ZZ")).UnitMode,
Is.EqualTo(ExpressionParticleUnitMode.Grapheme));
}
[Test]
public void WordTokenizer_UsesWhitespaceBoundariesAndKeepsPunctuation()
{
Assert.That(
ExpressionTextTokenizer.TokenizeText(
"I am not a joke",
ExpressionParticleUnitMode.Word),
Is.EqualTo(new[] { "I", "am", "not", "a", "joke" }));
Assert.That(
ExpressionTextTokenizer.TokenizeText(
"don't self-doubt ¿Por qué? веришь?",
ExpressionParticleUnitMode.Word),
Is.EqualTo(new[]
{
"don't",
"self-doubt",
"¿Por",
"qué?",
"веришь?"
}));
Assert.That(
ExpressionTextTokenizer.TokenizeText(
"one \t\r\n two",
ExpressionParticleUnitMode.Word),
Is.EqualTo(new[] { "one", "two" }));
}
[Test]
public void GraphemeTokenizer_PreservesCjkKanaAndCombiningCharacters()
{
const string combining = "e\u0301";
Assert.That(
ExpressionTextTokenizer.TokenizeText(
$"汉 あ {combining}",
ExpressionParticleUnitMode.Grapheme),
Is.EqualTo(new[] { "汉", "あ", combining }));
}
[Test]
public void WordPool_PreservesDuplicateWeightsAndRejectsPipeSeparators()
{
Assert.That(
LanguageYarnCommand.TryParseExpressionPool(
"noise doubt noise",
ExpressionParticleUnitMode.Word,
out List<string> pool),
Is.True);
Assert.That(pool, Is.EqualTo(new[] { "noise", "doubt", "noise" }));
Assert.That(
LanguageYarnCommand.TryParseExpressionPool(
"noise|doubt",
ExpressionParticleUnitMode.Word,
out _),
Is.False);
Assert.That(
LanguageYarnCommand.TryParseExpressionPool(
"noisedoubt",
ExpressionParticleUnitMode.Word,
out _),
Is.False);
}
[Test]
public void RoundSnapshot_IsParsedOnceAndKeepsItsLocaleAndWordUnits()
{
ExpressionParticleLanguageProfile languageProfile =
AssetDatabase.LoadAssetAtPath<ExpressionParticleLanguageProfile>(
ParticleProfilePath);
ExpressionParticleLocaleSettings english =
languageProfile.Resolve(new LocaleIdentifier("en"));
Assert.That(
ExpressionRoundTextSnapshot.TryCreate(
new LocaleIdentifier("en"),
english,
"I am not a joke",
new[] { "false alarm", "false alarm" },
"noise doubt noise",
out ExpressionRoundTextSnapshot snapshot,
out string error),
Is.True,
error);
Locale previous = LocalizationSettings.SelectedLocale;
try
{
LocalizationSettings.SelectedLocale =
LocalizationEditorSettings.GetLocale(
new LocaleIdentifier("zh-Hans"));
Assert.That(snapshot.Locale.Code, Is.EqualTo("en"));
Assert.That(
snapshot.TargetUnits,
Is.EqualTo(new[] { "I", "am", "not", "a", "joke" }));
Assert.That(
snapshot.InterferencePoolUnits,
Is.EqualTo(new[] { "false", "alarm", "false", "alarm" }));
Assert.That(
snapshot.DefaultPoolUnits,
Is.EqualTo(new[] { "noise", "doubt", "noise" }));
}
finally
{
LocalizationSettings.SelectedLocale = previous;
}
}
[Test]
public void WordProfile_ScalesConfiguredNonTargetCounts()
{
ExpressionParticleLanguageProfile profile =
AssetDatabase.LoadAssetAtPath<ExpressionParticleLanguageProfile>(
ParticleProfilePath);
ExpressionParticleLocaleSettings english =
profile.Resolve(new LocaleIdentifier("en"));
Assert.That(english.ScaleNonTargetCount(16), Is.EqualTo(10));
Assert.That(english.ScaleNonTargetCount(28), Is.EqualTo(18));
Assert.That(english.ScaleNonTargetCount(38), Is.EqualTo(25));
}
[Test]
public void ParticleGeometry_UsesVisualEdgeDistanceAndShortestSeparationAxis()
{
var left = new Bounds(Vector3.zero, new Vector3(4f, 1f, 0.01f));
var right = new Bounds(
new Vector3(4.15f, 0f, 0f),
new Vector3(4f, 1f, 0.01f));
Assert.That(
ExpressionParticleGeometry.BoundsDistance(left, right),
Is.EqualTo(0.15f).Within(0.0001f));
right.center = new Vector3(3.8f, 0f, 0f);
Assert.That(
ExpressionParticleGeometry.TryGetSeparation(
left,
right,
0.1f,
out Vector2 direction,
out float overlap),
Is.True);
Assert.That(direction, Is.EqualTo(Vector2.left));
Assert.That(overlap, Is.EqualTo(0.3f).Within(0.0001f));
}
[Test]
public void TextParticle_VisualBoundsIncludesTheWholeWordForPointerDistance()
{
var gameObject = new GameObject("TextParticleBoundsTest");
try
{
CandidateParticle particle =
gameObject.AddComponent<CandidateParticle>();
particle.ForceSetUnitText("self-doubt");
Bounds bounds = particle.GetVisualWorldBounds();
Assert.That(bounds.size.x, Is.GreaterThan(0f));
Assert.That(
particle.DistanceToVisualBounds(
new Vector2(bounds.max.x, bounds.center.y)),
Is.EqualTo(0f).Within(0.0001f));
}
finally
{
Object.DestroyImmediate(gameObject);
}
}
[Test]
public void Params_ContainsEnglishWordPoolAndPendingPoolsForOtherLocales()
{
StringTableCollection collection =
LocalizationEditorSettings.GetStringTableCollection(ConstRef.ParamsTable);
Assert.That(collection, Is.Not.Null);
string key =
LocalizationKit.GetL10NParamKey(ConstRef.ExpressParticlePoolParam);
Assert.That(collection.SharedData.GetEntry(key), Is.Not.Null);
string[] localeCodes = { "zh-Hans", "en", "ja-JP", "es", "ru", "pt-BR" };
foreach (string localeCode in localeCodes)
{
StringTable table =
collection.GetTable(new LocaleIdentifier(localeCode)) as StringTable;
Assert.That(table, Is.Not.Null, localeCode);
Assert.That(table.GetEntry(key), Is.Not.Null, localeCode);
}
StringTable chinese =
collection.GetTable(new LocaleIdentifier("zh-Hans")) as StringTable;
Assert.That(
ExpressionTextTokenizer.GetVisibleElements(
chinese.GetEntry(key).LocalizedValue),
Is.Not.Empty);
StringTable english =
collection.GetTable(new LocaleIdentifier("en")) as StringTable;
Assert.That(
LanguageYarnCommand.TryParseExpressionPool(
english.GetEntry(key).LocalizedValue,
ExpressionParticleUnitMode.Word,
out List<string> englishPool),
Is.True);
Assert.That(englishPool, Does.Contain("truth"));
Assert.That(englishPool, Does.Contain("believe"));
foreach (string localeCode in localeCodes.Skip(2))
{
StringTable table =
collection.GetTable(new LocaleIdentifier(localeCode)) as StringTable;
Assert.That(table.GetEntry(key).LocalizedValue, Is.Empty, localeCode);
}
}
[Test]
public void TextParticle_UsesConfiguredRoleAndOverridePoolsWithoutFirstFrameFallback()
{
var gameObject = new GameObject("TextParticlePoolTest");
try
{
CandidateParticle particle = gameObject.AddComponent<CandidateParticle>();
Assert.That(particle.CurrentUnitText, Is.Null.Or.Empty);
string combining = "e\u0301";
particle.SetUnitPools(
new[] { "😀" },
new[] { combining });
particle.originalIsRed = true;
particle.InitializeRandomUnit();
Assert.That(particle.CurrentUnitText, Is.EqualTo("😀"));
particle.originalIsRed = false;
particle.InitializeRandomUnit();
Assert.That(particle.CurrentUnitText, Is.EqualTo(combining));
particle.SetOverrideUnitPool(new[] { "。" });
particle.InitializeRandomUnit();
Assert.That(particle.CurrentUnitText, Is.EqualTo("。"));
particle.SetOverrideUnitPool((IReadOnlyList<string>)null);
particle.InitializeRandomUnit();
Assert.That(particle.CurrentUnitText, Is.EqualTo(combining));
}
finally
{
Object.DestroyImmediate(gameObject);
}
}
[Test]
public void UnicodeTokenizer_PreservesTextElementsAndDropsWhitespace()
{
string combining = "e\u0301";
var elements =
ExpressionTextTokenizer.GetVisibleElements($"中 A あ {combining} 😀。");
Assert.That(
elements,
Is.EqualTo(new[] { "中", "A", "あ", combining, "😀", "。" }));
}
[Test]
public void UnicodeLayout_PreservesWordGapWithoutWhitespaceParticle()
{
ExpressionTextTokenizer.Layout layout =
ExpressionTextTokenizer.BuildLayout("A B", 1f, 0.6f);
Assert.That(layout.VisibleElements, Is.EqualTo(new[] { "A", "B" }));
Assert.That(layout.Offsets[0], Is.EqualTo(-0.8f).Within(0.0001f));
Assert.That(layout.Offsets[1], Is.EqualTo(0.8f).Within(0.0001f));
}
[Test]
public void UnicodeSequenceMatch_UsesWholeTextElements()
{
var source = ExpressionTextTokenizer.GetVisibleElements("A😀e\u0301。");
var fragment = ExpressionTextTokenizer.GetVisibleElements("😀e\u0301");
Assert.That(ExpressionTextTokenizer.FindVisibleSequence(source, fragment),
Is.EqualTo(1));
Assert.That(
ExpressionTextTokenizer.FindVisibleSequence(
source,
ExpressionTextTokenizer.GetVisibleElements("😀x")),
Is.EqualTo(-1));
}
[Test]
public void ExpressContentValidation_HasNoBlockingErrors()
{
var issues = ExpressionLocalizationValidator.Validate();
string errors = string.Join(
"\n",
issues
.Where(issue =>
issue.Severity == ExpressionLocalizationIssueSeverity.Error)
.Select(issue => issue.Message));
Assert.That(errors, Is.Empty);
Assert.That(
issues.Any(issue =>
issue.Severity == ExpressionLocalizationIssueSeverity.Warning),
Is.True,
"未翻译 Locale 的空条目应作为待翻译警告保留。");
}
private static void ConfigureRound(
ExpressionRoundDefinition round,
string id,
string target,
string tokens,
int nonTargetCount)
{
SetPrivateField(round, "id", id);
SetPrivateField(round, "targetReference", target);
SetPrivateField(round, "tokenReference", tokens);
SetPrivateField(round, "nonTargetParticleCount", nonTargetCount);
}
private static void SetPrivateField(object target, string fieldName, object value)
{
FieldInfo field = target.GetType().GetField(
fieldName,
BindingFlags.Instance | BindingFlags.NonPublic);
Assert.That(field, Is.Not.Null, fieldName);
field.SetValue(target, value);
}
}
}
@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: d6b8797332ac4876b66e381ea3c98723
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,216 +0,0 @@
using System;
using System.Collections;
using System.Reflection;
using AibisDream.MiniGame.Language;
using NUnit.Framework;
using UnityEngine;
namespace AibisDream.EditorTests.Huoshan
{
public sealed class LogReleasePresentationTests
{
[Test]
public void LineClip_RejectsOutsideSegment()
{
Bounds bounds = new Bounds(Vector3.zero, new Vector3(2f, 2f, 0f));
bool visible = ConnectionRenderer.TryClipLineToBounds(
new Vector3(-3f, 2f),
new Vector3(3f, 2f),
bounds,
out _,
out _);
Assert.That(visible, Is.False);
}
[TestCase(-0.5f, 0f, 0.5f, 0f, -0.5f, 0f, 0.5f, 0f)]
[TestCase(-2f, 0f, 2f, 0f, -1f, 0f, 1f, 0f)]
[TestCase(0f, -2f, 0f, 0.5f, 0f, -1f, 0f, 0.5f)]
[TestCase(-2f, -2f, 2f, 2f, -1f, -1f, 1f, 1f)]
public void LineClip_ReturnsExpectedInteriorSegment(
float ax,
float ay,
float bx,
float by,
float expectedAx,
float expectedAy,
float expectedBx,
float expectedBy)
{
Bounds bounds = new Bounds(Vector3.zero, new Vector3(2f, 2f, 0f));
bool visible = ConnectionRenderer.TryClipLineToBounds(
new Vector3(ax, ay),
new Vector3(bx, by),
bounds,
out Vector3 clippedA,
out Vector3 clippedB);
Assert.That(visible, Is.True);
Assert.That(clippedA.x, Is.EqualTo(expectedAx).Within(0.0001f));
Assert.That(clippedA.y, Is.EqualTo(expectedAy).Within(0.0001f));
Assert.That(clippedB.x, Is.EqualTo(expectedBx).Within(0.0001f));
Assert.That(clippedB.y, Is.EqualTo(expectedBy).Within(0.0001f));
}
[TestCase(CompletionPhase.None, false)]
[TestCase(CompletionPhase.Completed, false)]
[TestCase(CompletionPhase.Focusing, true)]
[TestCase(CompletionPhase.FocusHolding, true)]
[TestCase(CompletionPhase.Finished, true)]
public void ConnectionClipping_OnlyStartsAfterOutput(
CompletionPhase phase,
bool expected)
{
Assert.That(LanguageParticleManager.ShouldClipConnections(phase), Is.EqualTo(expected));
}
[Test]
public void LieEdgePosition_LandsOnScreenPerimeterAndStaysInside()
{
Bounds bounds = new Bounds(Vector3.zero, new Vector3(6f, 4f, 0f));
const float inset = 0.1f;
for (int i = 0; i < 8; i++)
{
Vector3 position = LanguageParticleManager.CalculateScreenEdgePosition(
bounds,
i,
8,
inset);
Assert.That(position.x, Is.InRange(bounds.min.x, bounds.max.x));
Assert.That(position.y, Is.InRange(bounds.min.y, bounds.max.y));
bool touchesVerticalEdge =
Mathf.Abs(Mathf.Abs(position.x) - (bounds.extents.x - inset)) < 0.0001f;
bool touchesHorizontalEdge =
Mathf.Abs(Mathf.Abs(position.y) - (bounds.extents.y - inset)) < 0.0001f;
Assert.That(touchesVerticalEdge || touchesHorizontalEdge, Is.True);
}
}
[TestCase(6, 0.70f)]
[TestCase(6, 0.90f)]
[TestCase(6, 1.10f)]
public void ResolveTiming_LastCharacterCompletesWithinPreset(int characterCount, float totalDuration)
{
LanguageParticleManager.CalculateResolveTiming(
characterCount,
totalDuration,
3.5f,
0.4f,
out float characterDuration,
out float stagger);
float lastCompletion = characterDuration + stagger * (characterCount - 1);
Assert.That(lastCompletion, Is.LessThanOrEqualTo(totalDuration + 0.0001f));
Assert.That(lastCompletion, Is.EqualTo(totalDuration).Within(0.0001f));
}
[TestCase("LightPreset", 0.10f)]
[TestCase("MediumPreset", 0.20f)]
[TestCase("HeavyPreset", 0.35f)]
public void PresentationPreset_UsesApprovedValues(
string presetFieldName,
float baseDistortion)
{
const BindingFlags flags = BindingFlags.Static | BindingFlags.NonPublic;
FieldInfo presetField = typeof(LogReleasePresentationController).GetField(presetFieldName, flags);
Assert.That(presetField, Is.Not.Null);
object preset = presetField.GetValue(null);
Type presetType = preset.GetType();
AssertPresetField(presetType, preset, "BaseDistortion", baseDistortion);
}
[Test]
public void TmpClipRect_ConvertsWorldCornersIntoTextLocalSpace()
{
GameObject rectObject = new GameObject("ScreenRect", typeof(RectTransform));
GameObject textObject = new GameObject("TextTransform");
try
{
RectTransform rect = rectObject.GetComponent<RectTransform>();
rect.sizeDelta = new Vector2(4f, 2f);
rect.position = new Vector3(2f, -1f, 0f);
rect.rotation = Quaternion.Euler(0f, 0f, 17f);
rect.localScale = new Vector3(1.2f, 0.8f, 1f);
textObject.transform.position = new Vector3(-0.5f, 0.75f, 0f);
textObject.transform.rotation = Quaternion.Euler(0f, 0f, -11f);
textObject.transform.localScale = new Vector3(1.4f, 0.65f, 1f);
Vector4 actual = TMPRectClipper.CalculateLocalClipRect(textObject.transform, rect);
Vector3[] corners = new Vector3[4];
rect.GetWorldCorners(corners);
Vector3 first = textObject.transform.InverseTransformPoint(corners[0]);
float minX = first.x;
float minY = first.y;
float maxX = first.x;
float maxY = first.y;
for (int i = 1; i < corners.Length; i++)
{
Vector3 local = textObject.transform.InverseTransformPoint(corners[i]);
minX = Mathf.Min(minX, local.x);
minY = Mathf.Min(minY, local.y);
maxX = Mathf.Max(maxX, local.x);
maxY = Mathf.Max(maxY, local.y);
}
Assert.That(actual.x, Is.EqualTo(minX).Within(0.0001f));
Assert.That(actual.y, Is.EqualTo(minY).Within(0.0001f));
Assert.That(actual.z, Is.EqualTo(maxX).Within(0.0001f));
Assert.That(actual.w, Is.EqualTo(maxY).Within(0.0001f));
}
finally
{
UnityEngine.Object.DestroyImmediate(rectObject);
UnityEngine.Object.DestroyImmediate(textObject);
}
}
[TestCase(null, null)]
[TestCase("", null)]
[TestCase(" | ", null)]
[TestCase("没问题|冇", "没问题冇")]
[TestCase("没 问 题", "没问题")]
public void SlotMachinePool_StripsSeparatorsAndWhitespace(string input, string expected)
{
Assert.That(LanguageParticleManager.SanitizeSlotMachinePool(input), Is.EqualTo(expected));
}
[Test]
public void ActorPhraseList_PreservesWordsAndDropsEmptyEntries()
{
string[] phrases = LogReleasePresentationController.ParseActorPhrases(
" 没问题 | | 冇问题|帽问题 ");
Assert.That(phrases, Is.EqualTo(new[] { "没问题", "冇问题", "帽问题" }));
}
[Test]
public void ActorFlash_OutsideMemoryState_CompletesImmediatelyWithoutStateChange()
{
GameObject host = new GameObject("PresentationHost");
try
{
var controller = host.AddComponent<LogReleasePresentationController>();
IEnumerator routine = controller.FlashActorLie("没问题", 0.6f);
Assert.That(routine.MoveNext(), Is.False);
Assert.That(
controller.State,
Is.EqualTo(LogReleasePresentationController.PresentationState.Idle));
}
finally
{
UnityEngine.Object.DestroyImmediate(host);
}
}
private static void AssertPresetField(Type presetType, object preset, string name, float expected)
{
FieldInfo field = presetType.GetField(name, BindingFlags.Instance | BindingFlags.Public);
Assert.That(field, Is.Not.Null);
Assert.That((float)field.GetValue(preset), Is.EqualTo(expected).Within(0.0001f));
}
}
}
@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 5f75b14634314395a3a67721065f86bd
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 638cd3fa1c2470d42aceb69214c01786
guid: d3a91a90ebc2fd742bd10a66893940b8
folderAsset: yes
DefaultImporter:
externalObjects: {}
@@ -1,44 +0,0 @@
using AibisDream.Utility;
using NUnit.Framework;
using Yarn.Unity;
namespace AibisDream.SystemEditor.Tests
{
public sealed class OptionPromptMetadataTests
{
[Test]
public void IsOptionPromptLine_WithExactMetadata_ReturnsTrue()
{
var line = new LocalizedLine
{
Metadata = new[] { "line:0123456", YarnUtil.OptionPrompt }
};
Assert.That(line.IsOptionPromptLine(), Is.True);
}
[TestCase("option_prompts")]
[TestCase("dream_option_prompt")]
[TestCase("OPTION_PROMPT")]
public void IsOptionPromptLine_WithSimilarMetadata_ReturnsFalse(string metadata)
{
var line = new LocalizedLine
{
Metadata = new[] { metadata }
};
Assert.That(line.IsOptionPromptLine(), Is.False);
}
[Test]
public void IsOptionPromptLine_WithoutMetadata_ReturnsFalse()
{
var line = new LocalizedLine
{
Metadata = null
};
Assert.That(line.IsOptionPromptLine(), Is.False);
}
}
}
@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: d5e819fca9154e979c37ac490184620b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: d6e75cb49ad84a8ebe0520a529e7a67e
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,50 @@
using AibisDream.FrameAnimation;
using AibisDream.Utility;
using NUnit.Framework;
using UnityEditor;
using UnityEditor.AddressableAssets;
using UnityEngine;
namespace AibisDream.SystemEditor.Tests
{
public sealed class ActorPrefabContractTests
{
private const string PrefabPath =
"Assets/Prefabs/FixSystemPrefabs/FrameAnimationActor.prefab";
[Test]
public void FrameAnimationActor_PrefabAndSnapshotContractAreConsistent()
{
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(PrefabPath);
Assert.That(prefab, Is.Not.Null);
Assert.That(prefab.GetComponent<SpriteRenderer>(), Is.Not.Null);
Assert.That(prefab.GetComponent<FrameAnimationPlayer>(), Is.Not.Null);
Assert.That(prefab.GetComponent<FrameAnimationActor>(), Is.Not.Null);
var entry = AddressableAssetSettingsDefaultObject.Settings.FindAssetEntry(
AssetDatabase.AssetPathToGUID(PrefabPath));
Assert.That(entry, Is.Not.Null);
Assert.That(entry.address, Is.EqualTo(ConstRef.FrameAnimationActorPrefabName));
var actorObject = Object.Instantiate(prefab);
var slotObject = new GameObject("Actor Slot");
try
{
var slot = slotObject.AddComponent<ActorSlot>();
slot.slotName = "test-slot";
var actor = actorObject.GetComponent<FrameAnimationActor>();
actor.Init("TestActor", slot, ActorType.FrameAnimation);
var snapshot = actor.CaptureEntry();
Assert.That(snapshot.actorName, Is.EqualTo("TestActor"));
Assert.That(snapshot.slotName, Is.EqualTo("test-slot"));
Assert.That(snapshot.actorType, Is.EqualTo(nameof(ActorType.FrameAnimation)));
}
finally
{
Object.DestroyImmediate(actorObject);
Object.DestroyImmediate(slotObject);
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 23aa0707ef5c4618b55c455f2a03a612
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,152 @@
using System.Collections.Generic;
using System.Linq;
using AibisDream.Kit;
using AibisDream.SaveSystem;
using FMOD;
using FMOD.Studio;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NUnit.Framework;
namespace AibisDream.SystemEditor.Tests
{
public sealed class AudioSnapshotContractTests
{
[Test]
public void Registry_CapturesFinalValuesOnlyForSuccessfulParameters()
{
var api = new FakeFmodGlobalParameterApi();
api.Reads["stage"] = new ParameterRead(9f, 2.37f, RESULT.OK);
api.SetResults["missing"] = RESULT.ERR_EVENT_NOTFOUND;
var registry = new YarnGlobalParameterRegistry(api);
registry.SetFromYarn("stage", 9f);
registry.SetFromYarn("STAGE", 10f);
registry.SetFromYarn("missing", 1f);
var snapshot = registry.CaptureFinalValues(_ => { });
Assert.That(registry.TrackedNames, Is.EqualTo(new[] { "stage" }));
Assert.That(snapshot["stage"], Is.EqualTo(2.37f));
Assert.That(api.SetCalls.All(call => !call.IgnoreSeekSpeed), Is.True);
}
[Test]
public void Registry_RestoreJumpsImmediatelyThenYarnUsesSeekSpeed()
{
var api = new FakeFmodGlobalParameterApi();
var registry = new YarnGlobalParameterRegistry(api);
registry.RestoreFinalValues(
new Dictionary<string, float> { ["stage"] = 2.37f },
_ => { });
registry.SetFromYarn("stage", 3f);
Assert.That(api.SetCalls, Has.Count.EqualTo(2));
Assert.That(api.SetCalls[0].Value, Is.EqualTo(2.37f));
Assert.That(api.SetCalls[0].IgnoreSeekSpeed, Is.True);
Assert.That(api.SetCalls[1].Value, Is.EqualTo(3f));
Assert.That(api.SetCalls[1].IgnoreSeekSpeed, Is.False);
}
[Test]
public void AudioSnapshot_RoundTripsWithoutChangingSchema()
{
var source = new SaveSnapshot();
source.sections[SnapshotProviderIds.Audio] = new AudioSnapshotDto
{
ambState = "Dream",
yarnGlobalParameters = new Dictionary<string, float>
{
["reverb"] = 0.42f,
["hs1LogStage"] = 2.37f
}
};
var restored = JsonConvert.DeserializeObject<SaveSnapshot>(
JsonConvert.SerializeObject(source));
var audio = ((JObject)restored.sections[SnapshotProviderIds.Audio])
.ToObject<AudioSnapshotDto>();
Assert.That(restored.schemaVersion, Is.EqualTo(SaveSnapshotSchema.CurrentVersion));
Assert.That(audio.yarnGlobalParameters["reverb"], Is.EqualTo(0.42f));
Assert.That(audio.yarnGlobalParameters["hs1LogStage"], Is.EqualTo(2.37f));
}
private readonly struct SetCall
{
internal SetCall(float value, bool ignoreSeekSpeed)
{
Value = value;
IgnoreSeekSpeed = ignoreSeekSpeed;
}
internal float Value { get; }
internal bool IgnoreSeekSpeed { get; }
}
private readonly struct ParameterRead
{
internal ParameterRead(float value, float finalValue, RESULT result)
{
Value = value;
FinalValue = finalValue;
Result = result;
}
internal float Value { get; }
internal float FinalValue { get; }
internal RESULT Result { get; }
}
private sealed class FakeFmodGlobalParameterApi : IFmodGlobalParameterApi
{
internal readonly Dictionary<string, RESULT> SetResults =
new Dictionary<string, RESULT>(System.StringComparer.OrdinalIgnoreCase);
internal readonly Dictionary<string, ParameterRead> Reads =
new Dictionary<string, ParameterRead>(System.StringComparer.OrdinalIgnoreCase);
internal readonly List<SetCall> SetCalls = new List<SetCall>();
public RESULT SetParameterByName(
string name,
float value,
bool ignoreSeekSpeed)
{
SetCalls.Add(new SetCall(value, ignoreSeekSpeed));
return SetResults.TryGetValue(name, out var result) ? result : RESULT.OK;
}
public RESULT GetParameterByName(
string name,
out float value,
out float finalValue)
{
if (Reads.TryGetValue(name, out var read))
{
value = read.Value;
finalValue = read.FinalValue;
return read.Result;
}
value = 0f;
finalValue = 0f;
return RESULT.ERR_EVENT_NOTFOUND;
}
public RESULT GetParameterDescriptionByName(
string name,
out PARAMETER_DESCRIPTION description)
{
description = default;
return RESULT.ERR_EVENT_NOTFOUND;
}
public RESULT SetParameterById(
PARAMETER_ID id,
float value,
bool ignoreSeekSpeed)
{
return RESULT.OK;
}
}
}
}
@@ -0,0 +1,131 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using AibisDream.Framework;
using AibisDream.Kit;
using AibisDream.MiniGame.Language;
using AibisDream.Utility;
using NUnit.Framework;
using UnityEditor;
using UnityEngine;
using UnityEngine.Localization;
namespace AibisDream.SystemEditor.Tests
{
public sealed class LocalizationContractTests
{
private const string ParticleProfilePath =
"Assets/GameContent/Feature_Huoshan/Expression/ExpressionParticleLanguageProfile.asset";
[Test]
public void LocaleMappings_ResolveSupportedCodesAndSystemLanguages()
{
var localeCases = new Dictionary<string, LocaleKind>
{
["ru"] = LocaleKind.Ru,
["ru-RU"] = LocaleKind.Ru,
["es"] = LocaleKind.Es,
["es-ES"] = LocaleKind.Es,
["pt"] = LocaleKind.PtBr,
["pt-BR"] = LocaleKind.PtBr
};
foreach (var pair in localeCases)
Assert.That(LocalizationKit.GetLocaleKind(pair.Key), Is.EqualTo(pair.Value), pair.Key);
Assert.That(LocalizationKit.MapSystemLanguage(SystemLanguage.Russian), Is.EqualTo("ru"));
Assert.That(LocalizationKit.MapSystemLanguage(SystemLanguage.Spanish), Is.EqualTo("es"));
Assert.That(LocalizationKit.MapSystemLanguage(SystemLanguage.Portuguese), Is.EqualTo("pt-BR"));
}
[Test]
public void UnknownCharacter_UsesItsKeyAsEveryLocalizedName()
{
var method = typeof(YarnUtil).GetMethod(
"GetCharacterVoByKey",
BindingFlags.Static | BindingFlags.NonPublic);
Assert.That(method, Is.Not.Null);
const string missingKey = "missing_character_contract_test";
var character = (CharacterVo)method.Invoke(null, new object[] { missingKey });
Assert.That(character.key, Is.EqualTo(missingKey));
Assert.That(
new[]
{
character.cn,
character.en,
character.ja,
character.ru,
character.es,
character.ptBr
},
Has.All.EqualTo(missingKey));
}
[Test]
public void CharacterCsv_ParsesExpandedLocaleColumns()
{
var characters = CsvUtil.ReadAsBean<Character>(Path.Combine(
Application.streamingAssetsPath,
"Config",
"character.csv"));
Assert.That(characters, Is.Not.Empty);
var peipei = characters.Single(character => character.Key == "peipei");
Assert.That(peipei.Ru, Is.Not.Null);
Assert.That(peipei.Es, Is.Not.Null);
Assert.That(peipei.PtBr, Is.Not.Null);
}
[Test]
public void ExpressionParsing_PreservesUnicodeAndWordBoundaries()
{
const string combining = "e\u0301";
Assert.That(
LanguageYarnCommand.TryParseExpressionPool(
$"中 中 あ {combining} 😀。",
out List<string> graphemePool),
Is.True);
Assert.That(
graphemePool,
Is.EqualTo(new[] { "中", "中", "あ", combining, "😀", "。" }));
Assert.That(
ExpressionTextTokenizer.TokenizeText(
"don't self-doubt ¿Por qué? веришь?",
ExpressionParticleUnitMode.Word),
Is.EqualTo(new[]
{
"don't",
"self-doubt",
"¿Por",
"qué?",
"веришь?"
}));
}
[Test]
public void ExpressionParticleProfile_MapsLanguageFamilies()
{
var profile = AssetDatabase.LoadAssetAtPath<ExpressionParticleLanguageProfile>(
ParticleProfilePath);
Assert.That(profile, Is.Not.Null);
Assert.That(profile.GetValidationErrors(), Is.Empty);
Assert.That(
profile.Resolve(new LocaleIdentifier("zh-Hans")).UnitMode,
Is.EqualTo(ExpressionParticleUnitMode.Grapheme));
Assert.That(
profile.Resolve(new LocaleIdentifier("ja-JP")).UnitMode,
Is.EqualTo(ExpressionParticleUnitMode.Grapheme));
foreach (var localeCode in new[] { "en", "es", "ru", "pt-BR" })
{
Assert.That(
profile.Resolve(new LocaleIdentifier(localeCode)).UnitMode,
Is.EqualTo(ExpressionParticleUnitMode.Word),
localeCode);
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7f556352cf7f71c4a98e967b54dffc3b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -5,10 +5,10 @@ using NUnit.Framework;
namespace AibisDream.SystemEditor.Tests
{
public sealed class PresentationSnapshotContractTests
public sealed class SaveSnapshotContractTests
{
[Test]
public void PresentationProviders_UseStableIdsAndRestoreInDependencyOrder()
public void PresentationProviders_KeepStableIdsAndRestoreOrder()
{
var showcase = new ShowcaseSnapshotProvider();
var day2Sleep = new Day2SleepPresentationSnapshotProvider();
@@ -24,31 +24,7 @@ namespace AibisDream.SystemEditor.Tests
}
[Test]
public void PlayToolSection_RoundTripsThroughSnapshotJson()
{
var source = new SaveSnapshot();
source.sections[SnapshotProviderIds.PlayTool] = new PlayToolSnapshotDto
{
isObjVisible = true,
objPicName = "证物",
isFullScreenVisible = true,
fullScreenPicName = "教室"
};
var json = JsonConvert.SerializeObject(source);
var restored = JsonConvert.DeserializeObject<SaveSnapshot>(json);
var playTool = ((JObject)restored.sections[SnapshotProviderIds.PlayTool])
.ToObject<PlayToolSnapshotDto>();
Assert.That(playTool.isObjVisible, Is.True);
Assert.That(playTool.objPicName, Is.EqualTo("证物"));
Assert.That(playTool.isFullScreenVisible, Is.True);
Assert.That(playTool.fullScreenPicName, Is.EqualTo("教室"));
Assert.That(restored.schemaVersion, Is.EqualTo(SaveSnapshotSchema.CurrentVersion));
}
[Test]
public void ShowcaseAndDay2SleepSections_RoundTripSemanticState()
public void PresentationSections_RoundTripAndLegacySnapshotsRemainReadable()
{
var source = new SaveSnapshot();
source.sections[SnapshotProviderIds.Showcase] = new ShowcaseSnapshotDto
@@ -57,42 +33,40 @@ namespace AibisDream.SystemEditor.Tests
picName = "D2S星图",
isBackgroundVisible = true
};
source.sections[SnapshotProviderIds.Day2SleepPresentation] =
new Day2SleepPresentationSnapshotDto
{
mode = Day2SleepPresentationMode.LargeEffects.ToString(),
largeBlurAmount = 0.72f,
largeBlurSize = 0.015f,
largeScaleMultiplier = 1.1f,
largeAlpha = 0.4f
};
source.sections[SnapshotProviderIds.PlayTool] = new PlayToolSnapshotDto
{
isObjVisible = true,
objPicName = "证物",
objIsLocalized = true,
isFullScreenVisible = true,
fullScreenPicName = "病历",
fullScreenIsLocalized = true
};
var json = JsonConvert.SerializeObject(source);
var restored = JsonConvert.DeserializeObject<SaveSnapshot>(json);
var restored = JsonConvert.DeserializeObject<SaveSnapshot>(
JsonConvert.SerializeObject(source));
var showcase = ((JObject)restored.sections[SnapshotProviderIds.Showcase])
.ToObject<ShowcaseSnapshotDto>();
var day2Sleep = ((JObject)restored.sections[SnapshotProviderIds.Day2SleepPresentation])
.ToObject<Day2SleepPresentationSnapshotDto>();
var playTool = ((JObject)restored.sections[SnapshotProviderIds.PlayTool])
.ToObject<PlayToolSnapshotDto>();
Assert.That(restored.schemaVersion, Is.EqualTo(SaveSnapshotSchema.CurrentVersion));
Assert.That(showcase.displayMode, Is.EqualTo("Large"));
Assert.That(showcase.picName, Is.EqualTo("D2S星图"));
Assert.That(showcase.isBackgroundVisible, Is.True);
Assert.That(day2Sleep.mode, Is.EqualTo("LargeEffects"));
Assert.That(day2Sleep.largeBlurAmount, Is.EqualTo(0.72f));
Assert.That(day2Sleep.largeScaleMultiplier, Is.EqualTo(1.1f));
Assert.That(day2Sleep.largeAlpha, Is.EqualTo(0.4f));
}
Assert.That(playTool.objPicName, Is.EqualTo("证物"));
Assert.That(playTool.objIsLocalized, Is.True);
Assert.That(playTool.fullScreenPicName, Is.EqualTo("病历"));
Assert.That(playTool.fullScreenIsLocalized, Is.True);
[Test]
public void OldSnapshotWithoutPresentationSections_DeserializesWithoutSynthesizingSections()
{
var restored = JsonConvert.DeserializeObject<SaveSnapshot>(
var legacyPlayTool = JsonConvert.DeserializeObject<PlayToolSnapshotDto>(
"{\"isObjVisible\":true,\"objPicName\":\"legacy\"}");
Assert.That(legacyPlayTool.objIsLocalized, Is.False);
Assert.That(legacyPlayTool.fullScreenIsLocalized, Is.False);
var legacy = JsonConvert.DeserializeObject<SaveSnapshot>(
"{\"schemaVersion\":1,\"sections\":{}}");
Assert.That(restored.schemaVersion, Is.EqualTo(1));
Assert.That(restored.sections.ContainsKey(SnapshotProviderIds.Showcase), Is.False);
Assert.That(restored.sections.ContainsKey(SnapshotProviderIds.Day2SleepPresentation), Is.False);
Assert.That(restored.sections.ContainsKey(SnapshotProviderIds.PlayTool), Is.False);
Assert.That(legacy.schemaVersion, Is.EqualTo(1));
Assert.That(legacy.sections, Is.Empty);
}
}
}
@@ -0,0 +1,94 @@
using NUnit.Framework;
namespace AibisDream.SystemEditor.Tests
{
public sealed class TextSpeedSettingsTests
{
[SetUp]
public void SetUp()
{
TextSpeedSettings.Reset();
}
[TearDown]
public void TearDown()
{
TextSpeedSettings.Reset();
}
[Test]
public void Normalize_MapsEverySupportedValueToCanonicalForm()
{
AssertNormalized(
"0.5",
TextSpeedSettings.SlowMultiplier,
TextSpeedSettings.SlowValue);
AssertNormalized(
"1.0",
TextSpeedSettings.DefaultMultiplier,
TextSpeedSettings.DefaultValue);
AssertNormalized(
"3",
TextSpeedSettings.FastMultiplier,
TextSpeedSettings.FastValue);
}
[Test]
public void Normalize_RejectsInvalidAndUnsupportedValues()
{
foreach (var rawValue in new[]
{
null,
string.Empty,
"invalid",
"NaN",
"Infinity",
"0",
"2",
"4"
})
{
Assert.That(
TextSpeedSettings.TryNormalize(
rawValue,
out var multiplier,
out var canonicalValue),
Is.False,
rawValue);
Assert.That(multiplier, Is.EqualTo(TextSpeedSettings.DefaultMultiplier));
Assert.That(canonicalValue, Is.EqualTo(TextSpeedSettings.DefaultValue));
}
}
[Test]
public void Reset_RestoresDefaultAfterRuntimeChange()
{
Assert.That(TextSpeedSettings.ApplyStoredValue("3"), Is.True);
TextSpeedSettings.Reset();
Assert.That(
TextSpeedSettings.CurrentMultiplier,
Is.EqualTo(TextSpeedSettings.DefaultMultiplier));
Assert.That(
TextSpeedSettings.CurrentCanonicalValue,
Is.EqualTo(TextSpeedSettings.DefaultValue));
}
private static void AssertNormalized(
string rawValue,
float expectedMultiplier,
string expectedCanonicalValue)
{
Assert.That(
TextSpeedSettings.TryNormalize(
rawValue,
out var multiplier,
out var canonicalValue),
Is.True,
rawValue);
Assert.That(multiplier, Is.EqualTo(expectedMultiplier));
Assert.That(canonicalValue, Is.EqualTo(expectedCanonicalValue));
}
}
}
@@ -6,6 +6,6 @@ MonoImporter:
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,182 @@
using System;
using System.Collections.Generic;
using System.Linq;
using AibisDream.Framework;
using AibisDream.EditorTools;
using AibisDream.UI;
using NUnit.Framework;
using TMPro;
using UnityEditor;
using UnityEditor.AddressableAssets;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace AibisDream.SystemEditor.Tests
{
public sealed class TypographyContractTests
{
private const string ProfileFolder = "Assets/GameContent/Common/Typography/Profiles";
[TestCase(LocaleKind.Cn, TypographyProfileKind.ZhHans)]
[TestCase(LocaleKind.Ja, TypographyProfileKind.Japanese)]
[TestCase(LocaleKind.En, TypographyProfileKind.Latin)]
[TestCase(LocaleKind.Es, TypographyProfileKind.Latin)]
[TestCase(LocaleKind.PtBr, TypographyProfileKind.Latin)]
[TestCase(LocaleKind.Ru, TypographyProfileKind.Russian)]
public void LocaleMapping_UsesExpectedProfile(LocaleKind locale, TypographyProfileKind expected)
{
Assert.That(TypographyLocaleMapping.GetProfileKind(locale), Is.EqualTo(expected));
Assert.That(
TypographyLocaleMapping.GetProfileKey(locale),
Is.EqualTo($"Typography/Profile/{expected}"));
}
[Test]
public void Resolve_MissingRoleFallsBackToBody()
{
TMP_FontAsset font = AssetDatabase.LoadAssetAtPath<TMP_FontAsset>(
"Assets/Font/Assets/ChillBitmap_16px SDF.asset");
var profile = ScriptableObject.CreateInstance<TypographyProfile>();
try
{
var body = new TypographyStyle(TypographyRole.Body, font);
profile.Configure(TypographyProfileKind.Latin, new[] { body });
Assert.That(profile.Resolve(TypographyRole.Dialogue), Is.SameAs(body));
}
finally
{
UnityEngine.Object.DestroyImmediate(profile);
}
}
[Test]
public void Binding_ReapplyUsesCapturedBaselineWithoutAccumulatingMetrics()
{
TMP_FontAsset font = AssetDatabase.LoadAssetAtPath<TMP_FontAsset>(
"Assets/Font/Assets/ChillBitmap_16px SDF.asset");
var profile = ScriptableObject.CreateInstance<TypographyProfile>();
var go = new GameObject("TypographyMetricContract", typeof(RectTransform));
try
{
TextMeshProUGUI text = go.AddComponent<TextMeshProUGUI>();
text.fontSize = 40f;
text.lineSpacing = 3f;
text.characterSpacing = 2f;
LocalizedTypography binding = go.AddComponent<LocalizedTypography>();
profile.Configure(
TypographyProfileKind.Latin,
new[]
{
new TypographyStyle(
TypographyRole.Body,
font,
fontSizeMultiplier: 0.75f,
lineSpacingOffset: 1f,
characterSpacingOffset: 2f)
});
binding.ApplyProfile(profile);
binding.ApplyProfile(profile);
Assert.That(text.fontSize, Is.EqualTo(30f));
Assert.That(text.lineSpacing, Is.EqualTo(4f));
Assert.That(text.characterSpacing, Is.EqualTo(4f));
}
finally
{
UnityEngine.Object.DestroyImmediate(go);
UnityEngine.Object.DestroyImmediate(profile);
}
}
[Test]
public void FirstRoundProfiles_AreCompleteAndAddressableByConvention()
{
foreach (TypographyProfileKind kind in Enum.GetValues(typeof(TypographyProfileKind)))
{
TypographyProfile profile = AssetDatabase.LoadAssetAtPath<TypographyProfile>(
$"{ProfileFolder}/{kind}.asset");
Assert.That(profile, Is.Not.Null, kind.ToString());
Assert.That(profile.ProfileKind, Is.EqualTo(kind));
Assert.That(profile.GetValidationErrors(), Is.Empty, kind.ToString());
Assert.That(
Enum.GetValues(typeof(TypographyRole)).Cast<TypographyRole>()
.All(role => profile.Resolve(role) != null),
Is.True,
kind.ToString());
string guid = AssetDatabase.AssetPathToGUID($"{ProfileFolder}/{kind}.asset");
var entry = AddressableAssetSettingsDefaultObject.Settings.FindAssetEntry(guid);
Assert.That(entry, Is.Not.Null, kind.ToString());
Assert.That(entry.address, Is.EqualTo($"Typography/Profile/{kind}"));
}
}
[Test]
public void TerminalPrefabs_BindEveryTextExactlyOnce()
{
foreach (string path in TerminalTypographyMigration.PrefabPaths)
{
GameObject prefab = AssetDatabase.LoadAssetAtPath<GameObject>(path);
Assert.That(prefab, Is.Not.Null, path);
TMP_Text[] texts = prefab.GetComponentsInChildren<TMP_Text>(true);
Assert.That(texts, Is.Not.Empty, path);
foreach (TMP_Text text in texts)
{
LocalizedTypography[] bindings = text.GetComponents<LocalizedTypography>();
Assert.That(bindings, Has.Length.EqualTo(1), $"{path}: {text.name}");
Assert.That(bindings[0].Role, Is.EqualTo(TypographyRole.Terminal),
$"{path}: {text.name}");
}
}
}
[Test]
public void PersistenceTerminalComposition_InheritsTypographyBindings()
{
Scene scene = SceneManager.GetSceneByPath(TerminalTypographyMigration.PersistenceScenePath);
bool openedForTest = !scene.IsValid() || !scene.isLoaded;
if (openedForTest)
{
scene = EditorSceneManager.OpenScene(
TerminalTypographyMigration.PersistenceScenePath,
OpenSceneMode.Additive);
}
try
{
Component[] hosts = scene.GetRootGameObjects()
.SelectMany(root => root.GetComponentsInChildren<Component>(true))
.Where(component => component is TerminalPanel || component is InGameTerminalPanel)
.ToArray();
Assert.That(hosts, Has.Length.EqualTo(2));
var violations = new List<string>();
foreach (Component host in hosts)
{
foreach (TMP_Text text in host.GetComponentsInChildren<TMP_Text>(true))
{
LocalizedTypography[] bindings = text.GetComponents<LocalizedTypography>();
if (bindings.Length != 1)
{
violations.Add($"{host.name}: {text.name} has {bindings.Length} bindings");
continue;
}
if (bindings[0].Role != TypographyRole.Terminal)
violations.Add($"{host.name}: {text.name} uses {bindings[0].Role}");
}
}
Assert.That(violations, Is.Empty, string.Join("\n", violations));
}
finally
{
if (openedForTest)
EditorSceneManager.CloseScene(scene, true);
}
}
}
}
@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: cbbba0c3621cf204483f453bad2d75a4
guid: 895c6fa68b9b4ed4286362a258247f4a
MonoImporter:
externalObjects: {}
serializedVersion: 2
@@ -0,0 +1,75 @@
using System.Collections.Generic;
using System.Globalization;
using System.Text.RegularExpressions;
using AibisDream.Utility;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
using Yarn.Markup;
using Yarn.Unity;
namespace AibisDream.SystemEditor.Tests
{
public sealed class YarnLineMetadataTests
{
[Test]
public void AutoNext_UsesDefaultOrInvariantParameterizedDelay()
{
var plain = LineInfo.Generate(CreateLine("auto_next"));
Assert.That(plain.isAutoSkip, Is.True);
Assert.That(plain.CalcAutoNextDelayTime(), Is.EqualTo(ConstRef.FixedDelay));
var originalCulture = CultureInfo.CurrentCulture;
try
{
CultureInfo.CurrentCulture = new CultureInfo("de-DE");
var parameterized = LineInfo.Generate(CreateLine("auto_next:4.5"));
Assert.That(parameterized.isAutoSkip, Is.True);
Assert.That(parameterized.autoNextDelaySeconds, Is.EqualTo(4.5f));
Assert.That(parameterized.CalcAutoNextDelayTime(), Is.EqualTo(4500));
}
finally
{
CultureInfo.CurrentCulture = originalCulture;
}
}
[Test]
public void AutoNext_InvalidValueFallsBackAndSimilarTagIsIgnored()
{
LogAssert.Expect(LogType.Warning, new Regex("auto_next 参数无效"));
var invalid = LineInfo.Generate(CreateLine("auto_next:abc"));
Assert.That(invalid.isAutoSkip, Is.True);
Assert.That(invalid.autoNextDelaySeconds, Is.Null);
Assert.That(invalid.CalcAutoNextDelayTime(), Is.EqualTo(ConstRef.FixedDelay));
var similar = CreateLine("AUTO_NEXT", "auto_next_extra:4.5");
Assert.That(similar.IsAutoSkipLine(), Is.False);
Assert.That(similar.TryGetAutoNextDelaySeconds(out _), Is.False);
}
[Test]
public void OptionPrompt_RequiresExactMetadataTag()
{
Assert.That(CreateLine(YarnUtil.OptionPrompt).IsOptionPromptLine(), Is.True);
Assert.That(CreateLine("OPTION_PROMPT").IsOptionPromptLine(), Is.False);
Assert.That(CreateLine("option_prompts").IsOptionPromptLine(), Is.False);
Assert.That(
new LocalizedLine { Metadata = null }.IsOptionPromptLine(),
Is.False);
}
private static LocalizedLine CreateLine(params string[] metadata)
{
return new LocalizedLine
{
TextID = "line:yarn-metadata-contract",
Metadata = metadata,
Text = new MarkupParseResult(
"Test line",
new List<MarkupAttribute>())
};
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a9b456d04f99d9849a0200e688ddfb39
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
-75
View File
@@ -1,75 +0,0 @@
using NUnit.Framework;
namespace AibisDream.SystemEditor.Tests
{
public sealed class TextSpeedSettingsTests
{
[SetUp]
public void SetUp()
{
TextSpeedSettings.Reset();
}
[TearDown]
public void TearDown()
{
TextSpeedSettings.Reset();
}
[TestCase("0.5", TextSpeedSettings.SlowMultiplier, TextSpeedSettings.SlowValue)]
[TestCase("0.50", TextSpeedSettings.SlowMultiplier, TextSpeedSettings.SlowValue)]
[TestCase("1", TextSpeedSettings.DefaultMultiplier, TextSpeedSettings.DefaultValue)]
[TestCase("1.0", TextSpeedSettings.DefaultMultiplier, TextSpeedSettings.DefaultValue)]
[TestCase("3", TextSpeedSettings.FastMultiplier, TextSpeedSettings.FastValue)]
[TestCase("3.0", TextSpeedSettings.FastMultiplier, TextSpeedSettings.FastValue)]
public void TryNormalize_SupportedNumericValue_ReturnsCanonicalValue(
string rawValue,
float expectedMultiplier,
string expectedCanonicalValue)
{
var result = TextSpeedSettings.TryNormalize(
rawValue,
out var multiplier,
out var canonicalValue);
Assert.That(result, Is.True);
Assert.That(multiplier, Is.EqualTo(expectedMultiplier));
Assert.That(canonicalValue, Is.EqualTo(expectedCanonicalValue));
}
[TestCase(null)]
[TestCase("")]
[TestCase(" ")]
[TestCase("invalid")]
[TestCase("NaN")]
[TestCase("Infinity")]
[TestCase("-1")]
[TestCase("0")]
[TestCase("2")]
[TestCase("4")]
public void TryNormalize_InvalidOrUnsupportedValue_ReturnsDefault(
string rawValue)
{
var result = TextSpeedSettings.TryNormalize(
rawValue,
out var multiplier,
out var canonicalValue);
Assert.That(result, Is.False);
Assert.That(multiplier, Is.EqualTo(TextSpeedSettings.DefaultMultiplier));
Assert.That(canonicalValue, Is.EqualTo(TextSpeedSettings.DefaultValue));
}
[Test]
public void Reset_AfterApplyingFastValue_RestoresDefault()
{
Assert.That(TextSpeedSettings.ApplyStoredValue("3"), Is.True);
Assert.That(TextSpeedSettings.CurrentMultiplier, Is.EqualTo(TextSpeedSettings.FastMultiplier));
TextSpeedSettings.Reset();
Assert.That(TextSpeedSettings.CurrentMultiplier, Is.EqualTo(TextSpeedSettings.DefaultMultiplier));
Assert.That(TextSpeedSettings.CurrentCanonicalValue, Is.EqualTo(TextSpeedSettings.DefaultValue));
}
}
}
@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 665561f3805a96446b45ea1d50bfe520
guid: 1ccca9c8cbbc67e47be376cc9db7ce55
folderAsset: yes
DefaultImporter:
externalObjects: {}
@@ -0,0 +1,229 @@
using System;
using System.Collections.Generic;
using AibisDream.Framework;
using AibisDream.UI;
using TMPro;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace AibisDream.EditorTools
{
public static class TerminalTypographyMigration
{
public const string PrefabFolder = "Assets/GameContent/Feature_MainUI/Prefabs";
public const string PersistenceScenePath = "Assets/Scenes/Persistence.unity";
// Sources must be migrated before their consumers so existing added-component
// overrides can be replaced by the component inherited from the source prefab.
public static readonly IReadOnlyList<string> PrefabPaths = new[]
{
$"{PrefabFolder}/Common/Select Item.prefab",
$"{PrefabFolder}/Common/Slider Item.prefab",
$"{PrefabFolder}/Common/TerminalButton.prefab",
$"{PrefabFolder}/Common/TerminalChapterCard.prefab",
$"{PrefabFolder}/Common/TerminalHeader.prefab",
$"{PrefabFolder}/Common/TerminalMenuItem.prefab",
$"{PrefabFolder}/Common/TerminalOptionItem.prefab",
$"{PrefabFolder}/Common/TerminalRecordItem.prefab",
$"{PrefabFolder}/Common/TerminalSliderItem.prefab",
$"{PrefabFolder}/Common/TerminalChapterPanel.prefab",
$"{PrefabFolder}/TerminalChapterPanel.prefab",
$"{PrefabFolder}/TerminalLoadingPanel.prefab",
$"{PrefabFolder}/TerminalRecordPanel.prefab",
$"{PrefabFolder}/TerminalSettingPanel.prefab",
$"{PrefabFolder}/TerminalSettingPanelMini.prefab",
$"{PrefabFolder}/TerminalStartPanel.prefab"
};
[MenuItem("Tools/AIBIS Dream/Typography/Migrate Terminal Prefabs")]
public static void MigrateTerminalPrefabs()
{
int textCount = 0;
int addedCount = 0;
int removedOverrideCount = 0;
foreach (string path in PrefabPaths)
{
MigrationResult result = MigratePrefab(path);
textCount += result.TextCount;
addedCount += result.AddedCount;
removedOverrideCount += result.RemovedOverrideCount;
}
int sceneBindingCount = MigratePersistenceScene();
AssetDatabase.SaveAssets();
Debug.Log(
$"[TerminalTypographyMigration] Migrated {PrefabPaths.Count} prefabs and " +
$"{textCount} TMP texts. Added {addedCount} bindings; removed " +
$"{removedOverrideCount} redundant nested overrides; added " +
$"{sceneBindingCount} scene-owned bindings.");
}
public static void MigrateFromCommandLine()
{
try
{
MigrateTerminalPrefabs();
}
catch (Exception exception)
{
Debug.LogException(exception);
throw;
}
}
private static MigrationResult MigratePrefab(string path)
{
GameObject root = PrefabUtility.LoadPrefabContents(path);
try
{
TMP_Text[] texts = root.GetComponentsInChildren<TMP_Text>(true);
if (texts.Length == 0)
throw new InvalidOperationException($"Terminal prefab contains no TMP text: {path}");
int addedCount = 0;
int removedOverrideCount = 0;
foreach (TMP_Text text in texts)
{
int removedForText = RemoveRedundantNestedOverrides(text);
removedOverrideCount += removedForText;
LocalizedTypography[] bindings = text.GetComponents<LocalizedTypography>();
if (bindings.Length == 0)
{
// Saving the removed override makes the source component visible
// again when this consumer prefab is next loaded.
if (removedForText > 0)
continue;
LocalizedTypography binding = text.gameObject.AddComponent<LocalizedTypography>();
binding.ConfigureForEditor(TypographyRole.Terminal);
addedCount++;
}
else if (bindings.Length == 1)
{
ConfigureIfOwnedByCurrentPrefab(bindings[0]);
}
else
{
throw new InvalidOperationException(
$"TMP text has multiple typography bindings after cleanup: {path} / {text.name}");
}
}
PrefabUtility.SaveAsPrefabAsset(root, path);
return new MigrationResult(texts.Length, addedCount, removedOverrideCount);
}
finally
{
PrefabUtility.UnloadPrefabContents(root);
}
}
private static int MigratePersistenceScene()
{
Scene scene = SceneManager.GetSceneByPath(PersistenceScenePath);
bool openedForMigration = !scene.IsValid() || !scene.isLoaded;
if (openedForMigration)
scene = EditorSceneManager.OpenScene(PersistenceScenePath, OpenSceneMode.Additive);
try
{
int addedCount = 0;
var visited = new HashSet<TMP_Text>();
foreach (GameObject root in scene.GetRootGameObjects())
{
Component[] hosts = root.GetComponentsInChildren<Component>(true);
foreach (Component host in hosts)
{
if (host is not TerminalPanel && host is not InGameTerminalPanel)
continue;
foreach (TMP_Text text in host.GetComponentsInChildren<TMP_Text>(true))
{
if (!visited.Add(text))
continue;
LocalizedTypography[] bindings = text.GetComponents<LocalizedTypography>();
if (bindings.Length == 1)
continue;
if (bindings.Length > 1)
{
throw new InvalidOperationException(
$"Scene TMP text has multiple typography bindings: {text.name}");
}
if (PrefabUtility.IsPartOfPrefabInstance(text))
{
throw new InvalidOperationException(
$"Terminal prefab instance is missing its source binding: {text.name}");
}
LocalizedTypography binding = text.gameObject.AddComponent<LocalizedTypography>();
binding.ConfigureForEditor(TypographyRole.Terminal);
addedCount++;
}
}
}
if (addedCount > 0)
{
EditorSceneManager.MarkSceneDirty(scene);
EditorSceneManager.SaveScene(scene);
}
return addedCount;
}
finally
{
if (openedForMigration)
EditorSceneManager.CloseScene(scene, true);
}
}
private static int RemoveRedundantNestedOverrides(TMP_Text text)
{
GameObject sourceObject = PrefabUtility.GetCorrespondingObjectFromSource(text.gameObject);
if (sourceObject == null || sourceObject.GetComponent<LocalizedTypography>() == null)
return 0;
int removed = 0;
LocalizedTypography[] bindings = text.GetComponents<LocalizedTypography>();
foreach (LocalizedTypography binding in bindings)
{
if (!PrefabUtility.IsAddedComponentOverride(binding))
continue;
UnityEngine.Object.DestroyImmediate(binding, true);
removed++;
}
return removed;
}
private static void ConfigureIfOwnedByCurrentPrefab(LocalizedTypography binding)
{
if (PrefabUtility.IsPartOfPrefabInstance(binding) &&
PrefabUtility.GetCorrespondingObjectFromSource(binding) != null)
{
return;
}
binding.ConfigureForEditor(TypographyRole.Terminal);
}
private readonly struct MigrationResult
{
public MigrationResult(int textCount, int addedCount, int removedOverrideCount)
{
TextCount = textCount;
AddedCount = addedCount;
RemovedOverrideCount = removedOverrideCount;
}
public int TextCount { get; }
public int AddedCount { get; }
public int RemovedOverrideCount { get; }
}
}
}
@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 23aa0707ef5c4618b55c455f2a03a612
guid: 72a6797e130a7f643bd42bf648543ec2
MonoImporter:
externalObjects: {}
serializedVersion: 2
@@ -0,0 +1,169 @@
using System;
using System.Collections.Generic;
using AibisDream.Framework;
using TMPro;
using UnityEditor;
using UnityEditor.AddressableAssets;
using UnityEditor.AddressableAssets.Settings;
using UnityEditor.AddressableAssets.Settings.GroupSchemas;
using UnityEngine;
namespace AibisDream.EditorTools
{
public static class TypographyFirstRoundBuilder
{
private const string GroupName = "Shared_Typography";
private const string ProfileFolder = "Assets/GameContent/Common/Typography/Profiles";
private const string WenQuanYiPath = "Assets/Font/Assets/WenQuanYi Bitmap Song 14px SDF.asset";
private const string ChillBitmapPath = "Assets/Font/Assets/ChillBitmap_16px SDF.asset";
private const string ArkPixelPath = "Assets/Font/TTF/ark-pixel-16px-proportional-latin SDF.asset";
[MenuItem("Tools/AIBIS Dream/Typography/Rebuild First Round Assets")]
public static void BuildFirstRoundAssets()
{
EnsureFolder(ProfileFolder);
TMP_FontAsset wenQuanYi = LoadRequiredFont(WenQuanYiPath);
TMP_FontAsset chillBitmap = LoadRequiredFont(ChillBitmapPath);
TMP_FontAsset arkPixel = LoadRequiredFont(ArkPixelPath);
AddressableAssetSettings settings = AddressableAssetSettingsDefaultObject.Settings;
if (settings == null)
throw new InvalidOperationException("AddressableAssetSettings 不存在。");
AddressableAssetGroup group = GetOrCreateGroup(settings);
CreateOrUpdateProfile(
settings,
group,
TypographyProfileKind.ZhHans,
wenQuanYi,
new[] { chillBitmap });
CreateOrUpdateProfile(
settings,
group,
TypographyProfileKind.Japanese,
chillBitmap,
new[] { wenQuanYi });
CreateOrUpdateProfile(
settings,
group,
TypographyProfileKind.Latin,
arkPixel,
new[] { chillBitmap });
CreateOrUpdateProfile(
settings,
group,
TypographyProfileKind.Russian,
arkPixel,
new[] { chillBitmap });
TerminalTypographyMigration.MigrateTerminalPrefabs();
EditorUtility.SetDirty(settings);
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
Debug.Log("[TypographyFirstRoundBuilder] Profile、Addressables 和首轮试点绑定已更新。");
}
public static void BuildFromCommandLine()
{
try
{
BuildFirstRoundAssets();
}
catch (Exception exception)
{
Debug.LogException(exception);
throw;
}
}
private static TMP_FontAsset LoadRequiredFont(string path)
{
TMP_FontAsset font = AssetDatabase.LoadAssetAtPath<TMP_FontAsset>(path);
if (font == null)
throw new InvalidOperationException($"找不到字体资产:{path}");
return font;
}
private static AddressableAssetGroup GetOrCreateGroup(AddressableAssetSettings settings)
{
AddressableAssetGroup group = settings.FindGroup(GroupName);
if (group == null)
{
group = settings.CreateGroup(
GroupName,
false,
false,
true,
null,
typeof(BundledAssetGroupSchema),
typeof(ContentUpdateGroupSchema));
}
BundledAssetGroupSchema bundled = group.GetSchema<BundledAssetGroupSchema>();
if (bundled != null)
{
bundled.IncludeInBuild = true;
bundled.BundleMode = BundledAssetGroupSchema.BundlePackingMode.PackTogether;
EditorUtility.SetDirty(bundled);
}
ContentUpdateGroupSchema update = group.GetSchema<ContentUpdateGroupSchema>();
if (update != null)
{
update.StaticContent = false;
EditorUtility.SetDirty(update);
}
return group;
}
private static void CreateOrUpdateProfile(
AddressableAssetSettings settings,
AddressableAssetGroup group,
TypographyProfileKind kind,
TMP_FontAsset font,
IReadOnlyCollection<TMP_FontAsset> fallbacks)
{
string path = $"{ProfileFolder}/{kind}.asset";
TypographyProfile profile = AssetDatabase.LoadAssetAtPath<TypographyProfile>(path);
if (profile == null)
{
profile = ScriptableObject.CreateInstance<TypographyProfile>();
AssetDatabase.CreateAsset(profile, path);
}
var styles = new List<TypographyStyle>();
foreach (TypographyRole role in Enum.GetValues(typeof(TypographyRole)))
{
styles.Add(new TypographyStyle(
role,
font,
fallbackFonts: fallbacks,
fontSizeMultiplier: 1f));
}
profile.Configure(kind, styles);
EditorUtility.SetDirty(profile);
string guid = AssetDatabase.AssetPathToGUID(path);
AddressableAssetEntry entry = settings.CreateOrMoveEntry(guid, group, false, false);
entry.address = $"Typography/Profile/{kind}";
entry.SetLabel("Typography", true, true, false);
}
private static void EnsureFolder(string path)
{
string[] segments = path.Split('/');
string current = segments[0];
for (int i = 1; i < segments.Length; i++)
{
string next = $"{current}/{segments[i]}";
if (!AssetDatabase.IsValidFolder(next))
AssetDatabase.CreateFolder(current, segments[i]);
current = next;
}
}
}
}
@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: b95ba9d1e80e3dc4e83bc408569c4955
guid: a84a91ac052fc3444aa281c7d6a4f29e
MonoImporter:
externalObjects: {}
serializedVersion: 2
+12 -3
View File
@@ -22,8 +22,17 @@ public class VersionIncrementer : IPreprocessBuildWithReport
int build = int.Parse(versionParts[3]);
// 更新版本号
string date = DateTime.Now.ToString("yyyyMMdd-HHmmss");
PlayerSettings.bundleVersion = $"{major}.{minor}.{revision}.{build + 1}-{date}";
string incrementedVersion = $"{major}.{minor}.{revision}.{build + 1}";
bool isDevelopmentBuild = (report.summary.options & BuildOptions.Development) != 0;
if (isDevelopmentBuild)
{
string timestamp = DateTime.Now.ToString("yyyyMMdd-HHmmss");
incrementedVersion = $"{incrementedVersion}-{timestamp}-DEV";
}
PlayerSettings.bundleVersion = incrementedVersion;
Debug.Log($"[VersionIncrementer] bundleVersion: {incrementedVersion}");
if (report.summary.platform == BuildTarget.Android)
{
@@ -32,4 +41,4 @@ public class VersionIncrementer : IPreprocessBuildWithReport
Debug.Log($"[VersionIncrementer] Android versionCode: {versionCode} -> {versionCode + 1}");
}
}
}
}
Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More