Compare commits

..
Author SHA1 Message Date
dingyuntian 9cda02f02b Merge branch 'feature/本地化' of ssh://114.67.212.218:2222/aibis-dream/aibis-dream into feature/本地化 2026-07-29 20:36:30 +08:00
chenziqin 97ae87372d Merge branch '录屏test' into 'feature/本地化'
# Conflicts:
#   Assets/Yarn/FP/FP_Huoshan1/Stage6.yarn
2026-07-29 09:10:02 +00:00
Ovid 8671179ef4 commit 2026-07-28 21:30:55 +08:00
975 changed files with 10931 additions and 176500 deletions
-11
View File
@@ -67,17 +67,6 @@ 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: 2b0c2e120ca52edab004ec49752fe5ec
Hash: ae2a22805ec7ff55174c38995aa3e54d
m_OptimizeCatalogSize: 0
m_BuildRemoteCatalog: 0
m_BundleLocalCatalog: 0
@@ -71,16 +71,6 @@ 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
@@ -131,7 +121,6 @@ MonoBehaviour:
- Locale-pt-BR
- Locale-ru
- Locale-es
- Typography
m_SchemaTemplates: []
m_GroupTemplateObjects:
- {fileID: 11400000, guid: 0c158052680a3e94184c450d00291514, type: 2}
@@ -1,31 +0,0 @@
%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,31 +0,0 @@
%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,31 +0,0 @@
%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}
@@ -16,7 +16,13 @@ MonoBehaviour:
m_Data:
m_SerializedData: []
m_GUID: b848f030a719ba04c9f1e343f17ddc0c
m_SerializeEntries: []
m_SerializeEntries:
- m_GUID: 6d8ee72f781acf64abfe82ab659f5771
m_Address: 03_qq_symbol
m_ReadOnly: 1
m_SerializedLabels:
- Locale-zh-Hans
FlaggedDuringContentUpdateRestriction: 0
m_ReadOnly: 1
m_Settings: {fileID: 11400000, guid: 77169ce22e430f64fb36c771815a4a7b, type: 2}
m_SchemaSet:
@@ -1,25 +0,0 @@
%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}
@@ -40,33 +40,18 @@ MonoBehaviour:
m_ReadOnly: 1
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 539db7533ab287b45a774b3c5e2ccd9d
m_Address: "\u7ED3\u5C3E\u56FE\u6807"
- m_GUID: 032b1b5e09139ae45b6de51d35baae79
m_Address: discord-square-color-icon
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:
@@ -1,45 +0,0 @@
%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
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 29e8470e79377b4409a648ee18ce7682
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -1,16 +0,0 @@
%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
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 04b28f4d862df2440ac0be17bd1cd694
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -1,45 +0,0 @@
%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
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: ffaea4b400f30fd43bb702a14172406c
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -1,16 +0,0 @@
%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
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 42d59bddf89a08140adb832b309cf5a7
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -1,45 +0,0 @@
%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
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 779a71a2538b963459fd1ef9cc055b7c
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -1,16 +0,0 @@
%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
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 90fabd6fb037ad0489aaccead4298af4
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -1,45 +0,0 @@
%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
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: d3a6468425a79364fa21d96e369e8399
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -1,16 +0,0 @@
%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
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 2a43b55ded538f14f92548425cfb42e5
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -1,45 +0,0 @@
%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
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: d477ca02df8260c4ba977ebfe3a551fb
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -1,16 +0,0 @@
%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
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 7f3b2267eaa2569429428b05d408314e
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -1,45 +0,0 @@
%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
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 196a7a55916e25f4896599ddb2660f7a
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -1,16 +0,0 @@
%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
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: a40977b297f51304cb63f1a2787ea4a4
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -1,45 +0,0 @@
%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
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: a2d74ab6ace13ab4ba87121fa11aea2c
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -1,16 +0,0 @@
%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
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: b4ec695ab7e7a7848a6e4df4156f5091
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -1,45 +0,0 @@
%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
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 7219f667c0a45a642a5b943eb602dffc
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -1,16 +0,0 @@
%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
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 6f490fe007fca8342aceea06f8b0e054
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -1,45 +0,0 @@
%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
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 955397ef95649794a87b3c52b818f5c1
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -1,16 +0,0 @@
%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
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 5012207ff0e21364d9462964eb6374ef
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -1,45 +0,0 @@
%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
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: b39cbcc4de75ad6468842c36c7e5138d
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -1,16 +0,0 @@
%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
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 6bd7e9bcac1e24042b79a54f47f52106
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -1,30 +0,0 @@
%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}
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: b05cfdc6bbe312248b8be84a1ff01e32
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -1,30 +0,0 @@
%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}
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 0418ce3bee30b98409aa4d268d7d89d7
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -1,30 +0,0 @@
%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}
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 6e0c8dfd5e5e1c74b80cecc21df2d9cd
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -1,30 +0,0 @@
%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}
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: f0205c268777acb459dddbb4690d7c27
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -1,30 +0,0 @@
%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}
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: d029169cdb68ba848a0f0260aeddae55
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -1,49 +0,0 @@
%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}
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 1051221ba75fcea47b67648acedc5383
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: d3a91a90ebc2fd742bd10a66893940b8
guid: 638cd3fa1c2470d42aceb69214c01786
folderAsset: yes
DefaultImporter:
externalObjects: {}
@@ -0,0 +1,260 @@
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
@@ -0,0 +1,123 @@
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,5 +1,5 @@
fileFormatVersion: 2
guid: 72a6797e130a7f643bd42bf648543ec2
guid: d476e493adf4855468ce46409dae6661
MonoImporter:
externalObjects: {}
serializedVersion: 2
@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 1ccca9c8cbbc67e47be376cc9db7ce55
guid: 665561f3805a96446b45ea1d50bfe520
folderAsset: yes
DefaultImporter:
externalObjects: {}
@@ -0,0 +1,117 @@
#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,5 +1,5 @@
fileFormatVersion: 2
guid: a84a91ac052fc3444aa281c7d6a4f29e
guid: b95ba9d1e80e3dc4e83bc408569c4955
MonoImporter:
externalObjects: {}
serializedVersion: 2
@@ -0,0 +1,476 @@
#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
@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 895c6fa68b9b4ed4286362a258247f4a
guid: cbbba0c3621cf204483f453bad2d75a4
MonoImporter:
externalObjects: {}
serializedVersion: 2
@@ -1803,14 +1803,6 @@ 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");
@@ -2521,14 +2513,6 @@ 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)
{
@@ -0,0 +1,77 @@
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,5 +1,5 @@
fileFormatVersion: 2
guid: c9dd51810ef0cc94398175f3597a978e
guid: 23aa0707ef5c4618b55c455f2a03a612
MonoImporter:
externalObjects: {}
serializedVersion: 2
@@ -0,0 +1,577 @@
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);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d6b8797332ac4876b66e381ea3c98723
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,216 @@
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));
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5f75b14634314395a3a67721065f86bd
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,44 @@
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);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d5e819fca9154e979c37ac490184620b
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 SaveSnapshotContractTests
public sealed class PresentationSnapshotContractTests
{
[Test]
public void PresentationProviders_KeepStableIdsAndRestoreOrder()
public void PresentationProviders_UseStableIdsAndRestoreInDependencyOrder()
{
var showcase = new ShowcaseSnapshotProvider();
var day2Sleep = new Day2SleepPresentationSnapshotProvider();
@@ -24,7 +24,31 @@ namespace AibisDream.SystemEditor.Tests
}
[Test]
public void PresentationSections_RoundTripAndLegacySnapshotsRemainReadable()
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()
{
var source = new SaveSnapshot();
source.sections[SnapshotProviderIds.Showcase] = new ShowcaseSnapshotDto
@@ -33,40 +57,42 @@ namespace AibisDream.SystemEditor.Tests
picName = "D2S星图",
isBackgroundVisible = true
};
source.sections[SnapshotProviderIds.PlayTool] = new PlayToolSnapshotDto
source.sections[SnapshotProviderIds.Day2SleepPresentation] =
new Day2SleepPresentationSnapshotDto
{
isObjVisible = true,
objPicName = "证物",
objIsLocalized = true,
isFullScreenVisible = true,
fullScreenPicName = "病历",
fullScreenIsLocalized = true
mode = Day2SleepPresentationMode.LargeEffects.ToString(),
largeBlurAmount = 0.72f,
largeBlurSize = 0.015f,
largeScaleMultiplier = 1.1f,
largeAlpha = 0.4f
};
var restored = JsonConvert.DeserializeObject<SaveSnapshot>(
JsonConvert.SerializeObject(source));
var json = JsonConvert.SerializeObject(source);
var restored = JsonConvert.DeserializeObject<SaveSnapshot>(json);
var showcase = ((JObject)restored.sections[SnapshotProviderIds.Showcase])
.ToObject<ShowcaseSnapshotDto>();
var playTool = ((JObject)restored.sections[SnapshotProviderIds.PlayTool])
.ToObject<PlayToolSnapshotDto>();
var day2Sleep = ((JObject)restored.sections[SnapshotProviderIds.Day2SleepPresentation])
.ToObject<Day2SleepPresentationSnapshotDto>();
Assert.That(restored.schemaVersion, Is.EqualTo(SaveSnapshotSchema.CurrentVersion));
Assert.That(showcase.displayMode, Is.EqualTo("Large"));
Assert.That(showcase.picName, Is.EqualTo("D2S星图"));
Assert.That(playTool.objPicName, Is.EqualTo("证物"));
Assert.That(playTool.objIsLocalized, Is.True);
Assert.That(playTool.fullScreenPicName, Is.EqualTo("病历"));
Assert.That(playTool.fullScreenIsLocalized, Is.True);
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));
}
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>(
[Test]
public void OldSnapshotWithoutPresentationSections_DeserializesWithoutSynthesizingSections()
{
var restored = JsonConvert.DeserializeObject<SaveSnapshot>(
"{\"schemaVersion\":1,\"sections\":{}}");
Assert.That(legacy.schemaVersion, Is.EqualTo(1));
Assert.That(legacy.sections, Is.Empty);
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);
}
}
}
-8
View File
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: d6e75cb49ad84a8ebe0520a529e7a67e
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,50 +0,0 @@
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);
}
}
}
}
@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 23aa0707ef5c4618b55c455f2a03a612
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,152 +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 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;
}
}
}
}
@@ -1,131 +0,0 @@
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);
}
}
}
}
@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 7f556352cf7f71c4a98e967b54dffc3b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,94 +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();
}
[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));
}
}
}
@@ -1,182 +0,0 @@
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,75 +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 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>())
};
}
}
}
@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: a9b456d04f99d9849a0200e688ddfb39
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+75
View File
@@ -0,0 +1,75 @@
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,229 +0,0 @@
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,169 +0,0 @@
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;
}
}
}
}
+2 -11
View File
@@ -22,17 +22,8 @@ public class VersionIncrementer : IPreprocessBuildWithReport
int build = int.Parse(versionParts[3]);
// 更新版本号
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}");
string date = DateTime.Now.ToString("yyyyMMdd-HHmmss");
PlayerSettings.bundleVersion = $"{major}.{minor}.{revision}.{build + 1}-{date}";
if (report.summary.platform == BuildTarget.Android)
{
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

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