diff --git a/Assets/AddressableAssetsData/AddressableAssetSettings.asset b/Assets/AddressableAssetsData/AddressableAssetSettings.asset index 04056350e..a6039db1d 100644 --- a/Assets/AddressableAssetsData/AddressableAssetSettings.asset +++ b/Assets/AddressableAssetsData/AddressableAssetSettings.asset @@ -15,7 +15,7 @@ MonoBehaviour: m_DefaultGroup: 09546aca93a801441859af2a811fe53b m_currentHash: serializedVersion: 2 - Hash: e92b9b9776d0601449874b8c1b0ed1ae + Hash: ae2a22805ec7ff55174c38995aa3e54d m_OptimizeCatalogSize: 0 m_BuildRemoteCatalog: 0 m_BundleLocalCatalog: 0 @@ -68,6 +68,9 @@ MonoBehaviour: - {fileID: 11400000, guid: af94af36950b08d4f9a5037546bbe040, type: 2} - {fileID: 11400000, guid: 60fd2fd8c7094b242b01c63de65de9ea, type: 2} - {fileID: 11400000, guid: 7e4ab0de168458b43a21dc2f9e811ff5, type: 2} + - {fileID: 11400000, guid: 089fc86f714d6a94bafea3b117fbd639, type: 2} + - {fileID: 11400000, guid: 1c592abe924a856439461f3785ec53da, type: 2} + - {fileID: 11400000, guid: 26fd03652560e7f4abec092229fc38f2, type: 2} m_BuildSettings: m_CompileScriptsInVirtualMode: 0 m_CleanupStreamingAssetsAfterBuilds: 1 @@ -115,6 +118,9 @@ MonoBehaviour: - Locale-zh-Hans - SceneSO - Locale-ja-JP + - Locale-pt-BR + - Locale-ru + - Locale-es m_SchemaTemplates: [] m_GroupTemplateObjects: - {fileID: 11400000, guid: 0c158052680a3e94184c450d00291514, type: 2} diff --git a/Assets/AddressableAssetsData/AssetGroups/Localization-Locales.asset b/Assets/AddressableAssetsData/AssetGroups/Localization-Locales.asset index 7adacd092..57a1b8efd 100644 --- a/Assets/AddressableAssetsData/AssetGroups/Localization-Locales.asset +++ b/Assets/AddressableAssetsData/AssetGroups/Localization-Locales.asset @@ -35,6 +35,24 @@ MonoBehaviour: m_SerializedLabels: - Locale FlaggedDuringContentUpdateRestriction: 0 + - m_GUID: 9b1581a48f0ca11448dbddaf79c11252 + m_Address: Russian (ru) + m_ReadOnly: 1 + m_SerializedLabels: + - Locale + FlaggedDuringContentUpdateRestriction: 0 + - m_GUID: 3cadb0d5f8bf5e64599f52e139f7e3cc + m_Address: Spanish (es) + m_ReadOnly: 1 + m_SerializedLabels: + - Locale + FlaggedDuringContentUpdateRestriction: 0 + - m_GUID: d0c46f4777ac92249a43143a15892fd2 + m_Address: Portuguese (Brazil) (pt-BR) + m_ReadOnly: 1 + m_SerializedLabels: + - Locale + FlaggedDuringContentUpdateRestriction: 0 m_ReadOnly: 1 m_Settings: {fileID: 11400000, guid: 77169ce22e430f64fb36c771815a4a7b, type: 2} m_SchemaSet: diff --git a/Assets/AddressableAssetsData/AssetGroups/Localization-String-Tables-Portuguese (Brazil) (pt-BR).asset b/Assets/AddressableAssetsData/AssetGroups/Localization-String-Tables-Portuguese (Brazil) (pt-BR).asset new file mode 100644 index 000000000..11ff7620c --- /dev/null +++ b/Assets/AddressableAssetsData/AssetGroups/Localization-String-Tables-Portuguese (Brazil) (pt-BR).asset @@ -0,0 +1,31 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: bbb281ee3bf0b054c82ac2347e9e782c, type: 3} + m_Name: Localization-String-Tables-Portuguese (Brazil) (pt-BR) + m_EditorClassIdentifier: + m_GroupName: Localization-String-Tables-Portuguese (Brazil) (pt-BR) + m_Data: + m_SerializedData: [] + m_GUID: 67ced675275774640b77e0ded8750379 + m_SerializeEntries: + - m_GUID: 63300a11309a7b34bbf7a345b123cab8 + m_Address: ChapterInfo_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: b430e9b782d3119489b903dce4915542, type: 2} + - {fileID: 11400000, guid: 70748601b27158b47810538f6ed1c013, type: 2} diff --git a/Assets/StreamingAssets/Config/params.csv.meta b/Assets/AddressableAssetsData/AssetGroups/Localization-String-Tables-Portuguese (Brazil) (pt-BR).asset.meta similarity index 52% rename from Assets/StreamingAssets/Config/params.csv.meta rename to Assets/AddressableAssetsData/AssetGroups/Localization-String-Tables-Portuguese (Brazil) (pt-BR).asset.meta index dd48c8466..c2ec71056 100644 --- a/Assets/StreamingAssets/Config/params.csv.meta +++ b/Assets/AddressableAssetsData/AssetGroups/Localization-String-Tables-Portuguese (Brazil) (pt-BR).asset.meta @@ -1,7 +1,8 @@ fileFormatVersion: 2 -guid: e721272cd05718a4d8872d0f3e3f5b8c -DefaultImporter: +guid: 089fc86f714d6a94bafea3b117fbd639 +NativeFormatImporter: externalObjects: {} + mainObjectFileID: 11400000 userData: assetBundleName: assetBundleVariant: diff --git a/Assets/AddressableAssetsData/AssetGroups/Localization-String-Tables-Russian (ru).asset b/Assets/AddressableAssetsData/AssetGroups/Localization-String-Tables-Russian (ru).asset new file mode 100644 index 000000000..e9d766346 --- /dev/null +++ b/Assets/AddressableAssetsData/AssetGroups/Localization-String-Tables-Russian (ru).asset @@ -0,0 +1,31 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: bbb281ee3bf0b054c82ac2347e9e782c, type: 3} + m_Name: Localization-String-Tables-Russian (ru) + m_EditorClassIdentifier: + m_GroupName: Localization-String-Tables-Russian (ru) + m_Data: + m_SerializedData: [] + m_GUID: c1e47d7d0d831ac4b881f19aa2bf17e0 + m_SerializeEntries: + - m_GUID: c69caec2066296840887d7e0c2eff52e + m_Address: ChapterInfo_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: b251ae14d0b273843b5674d239daa31c, type: 2} + - {fileID: 11400000, guid: 808fc72235ececa47aa55960a94a9a9a, type: 2} diff --git a/Assets/AddressableAssetsData/AssetGroups/Localization-String-Tables-Russian (ru).asset.meta b/Assets/AddressableAssetsData/AssetGroups/Localization-String-Tables-Russian (ru).asset.meta new file mode 100644 index 000000000..0ed488165 --- /dev/null +++ b/Assets/AddressableAssetsData/AssetGroups/Localization-String-Tables-Russian (ru).asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 1c592abe924a856439461f3785ec53da +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/AddressableAssetsData/AssetGroups/Localization-String-Tables-Spanish (es).asset b/Assets/AddressableAssetsData/AssetGroups/Localization-String-Tables-Spanish (es).asset new file mode 100644 index 000000000..48bafd2a8 --- /dev/null +++ b/Assets/AddressableAssetsData/AssetGroups/Localization-String-Tables-Spanish (es).asset @@ -0,0 +1,31 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: bbb281ee3bf0b054c82ac2347e9e782c, type: 3} + m_Name: Localization-String-Tables-Spanish (es) + m_EditorClassIdentifier: + m_GroupName: Localization-String-Tables-Spanish (es) + m_Data: + m_SerializedData: [] + m_GUID: c8471b00dabdd224ab9c17d71d951cbc + m_SerializeEntries: + - m_GUID: 728457f9c2ef69c489530047d495eca5 + m_Address: ChapterInfo_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: 3ddf5f47a1b265049825b193e7e93fbd, type: 2} + - {fileID: 11400000, guid: 3e5e5c2400a39814994a9fcb8eaf49a6, type: 2} diff --git a/Assets/AddressableAssetsData/AssetGroups/Localization-String-Tables-Spanish (es).asset.meta b/Assets/AddressableAssetsData/AssetGroups/Localization-String-Tables-Spanish (es).asset.meta new file mode 100644 index 000000000..af75d0792 --- /dev/null +++ b/Assets/AddressableAssetsData/AssetGroups/Localization-String-Tables-Spanish (es).asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 26fd03652560e7f4abec092229fc38f2 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/AddressableAssetsData/AssetGroups/Schemas/Localization-String-Tables-Portuguese (Brazil) (pt-BR)_BundledAssetGroupSchema.asset b/Assets/AddressableAssetsData/AssetGroups/Schemas/Localization-String-Tables-Portuguese (Brazil) (pt-BR)_BundledAssetGroupSchema.asset new file mode 100644 index 000000000..304ca64d2 --- /dev/null +++ b/Assets/AddressableAssetsData/AssetGroups/Schemas/Localization-String-Tables-Portuguese (Brazil) (pt-BR)_BundledAssetGroupSchema.asset @@ -0,0 +1,45 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: e5d17a21594effb4e9591490b009e7aa, type: 3} + m_Name: Localization-String-Tables-Portuguese (Brazil) (pt-BR)_BundledAssetGroupSchema + m_EditorClassIdentifier: + m_Group: {fileID: 11400000, guid: 089fc86f714d6a94bafea3b117fbd639, 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 diff --git a/Assets/AddressableAssetsData/AssetGroups/Schemas/Localization-String-Tables-Portuguese (Brazil) (pt-BR)_BundledAssetGroupSchema.asset.meta b/Assets/AddressableAssetsData/AssetGroups/Schemas/Localization-String-Tables-Portuguese (Brazil) (pt-BR)_BundledAssetGroupSchema.asset.meta new file mode 100644 index 000000000..484e449d1 --- /dev/null +++ b/Assets/AddressableAssetsData/AssetGroups/Schemas/Localization-String-Tables-Portuguese (Brazil) (pt-BR)_BundledAssetGroupSchema.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b430e9b782d3119489b903dce4915542 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/AddressableAssetsData/AssetGroups/Schemas/Localization-String-Tables-Portuguese (Brazil) (pt-BR)_ContentUpdateGroupSchema.asset b/Assets/AddressableAssetsData/AssetGroups/Schemas/Localization-String-Tables-Portuguese (Brazil) (pt-BR)_ContentUpdateGroupSchema.asset new file mode 100644 index 000000000..9eda8227e --- /dev/null +++ b/Assets/AddressableAssetsData/AssetGroups/Schemas/Localization-String-Tables-Portuguese (Brazil) (pt-BR)_ContentUpdateGroupSchema.asset @@ -0,0 +1,16 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5834b5087d578d24c926ce20cd31e6d6, type: 3} + m_Name: Localization-String-Tables-Portuguese (Brazil) (pt-BR)_ContentUpdateGroupSchema + m_EditorClassIdentifier: + m_Group: {fileID: 11400000, guid: 089fc86f714d6a94bafea3b117fbd639, type: 2} + m_StaticContent: 0 diff --git a/Assets/AddressableAssetsData/AssetGroups/Schemas/Localization-String-Tables-Portuguese (Brazil) (pt-BR)_ContentUpdateGroupSchema.asset.meta b/Assets/AddressableAssetsData/AssetGroups/Schemas/Localization-String-Tables-Portuguese (Brazil) (pt-BR)_ContentUpdateGroupSchema.asset.meta new file mode 100644 index 000000000..3365724bd --- /dev/null +++ b/Assets/AddressableAssetsData/AssetGroups/Schemas/Localization-String-Tables-Portuguese (Brazil) (pt-BR)_ContentUpdateGroupSchema.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 70748601b27158b47810538f6ed1c013 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/AddressableAssetsData/AssetGroups/Schemas/Localization-String-Tables-Russian (ru)_BundledAssetGroupSchema.asset b/Assets/AddressableAssetsData/AssetGroups/Schemas/Localization-String-Tables-Russian (ru)_BundledAssetGroupSchema.asset new file mode 100644 index 000000000..4245288db --- /dev/null +++ b/Assets/AddressableAssetsData/AssetGroups/Schemas/Localization-String-Tables-Russian (ru)_BundledAssetGroupSchema.asset @@ -0,0 +1,45 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: e5d17a21594effb4e9591490b009e7aa, type: 3} + m_Name: Localization-String-Tables-Russian (ru)_BundledAssetGroupSchema + m_EditorClassIdentifier: + m_Group: {fileID: 11400000, guid: 1c592abe924a856439461f3785ec53da, 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 diff --git a/Assets/AddressableAssetsData/AssetGroups/Schemas/Localization-String-Tables-Russian (ru)_BundledAssetGroupSchema.asset.meta b/Assets/AddressableAssetsData/AssetGroups/Schemas/Localization-String-Tables-Russian (ru)_BundledAssetGroupSchema.asset.meta new file mode 100644 index 000000000..1438e4a66 --- /dev/null +++ b/Assets/AddressableAssetsData/AssetGroups/Schemas/Localization-String-Tables-Russian (ru)_BundledAssetGroupSchema.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b251ae14d0b273843b5674d239daa31c +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/AddressableAssetsData/AssetGroups/Schemas/Localization-String-Tables-Russian (ru)_ContentUpdateGroupSchema.asset b/Assets/AddressableAssetsData/AssetGroups/Schemas/Localization-String-Tables-Russian (ru)_ContentUpdateGroupSchema.asset new file mode 100644 index 000000000..e09ed0469 --- /dev/null +++ b/Assets/AddressableAssetsData/AssetGroups/Schemas/Localization-String-Tables-Russian (ru)_ContentUpdateGroupSchema.asset @@ -0,0 +1,16 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5834b5087d578d24c926ce20cd31e6d6, type: 3} + m_Name: Localization-String-Tables-Russian (ru)_ContentUpdateGroupSchema + m_EditorClassIdentifier: + m_Group: {fileID: 11400000, guid: 1c592abe924a856439461f3785ec53da, type: 2} + m_StaticContent: 0 diff --git a/Assets/AddressableAssetsData/AssetGroups/Schemas/Localization-String-Tables-Russian (ru)_ContentUpdateGroupSchema.asset.meta b/Assets/AddressableAssetsData/AssetGroups/Schemas/Localization-String-Tables-Russian (ru)_ContentUpdateGroupSchema.asset.meta new file mode 100644 index 000000000..4910de7bc --- /dev/null +++ b/Assets/AddressableAssetsData/AssetGroups/Schemas/Localization-String-Tables-Russian (ru)_ContentUpdateGroupSchema.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 808fc72235ececa47aa55960a94a9a9a +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/AddressableAssetsData/AssetGroups/Schemas/Localization-String-Tables-Spanish (es)_BundledAssetGroupSchema.asset b/Assets/AddressableAssetsData/AssetGroups/Schemas/Localization-String-Tables-Spanish (es)_BundledAssetGroupSchema.asset new file mode 100644 index 000000000..94eac2b41 --- /dev/null +++ b/Assets/AddressableAssetsData/AssetGroups/Schemas/Localization-String-Tables-Spanish (es)_BundledAssetGroupSchema.asset @@ -0,0 +1,45 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: e5d17a21594effb4e9591490b009e7aa, type: 3} + m_Name: Localization-String-Tables-Spanish (es)_BundledAssetGroupSchema + m_EditorClassIdentifier: + m_Group: {fileID: 11400000, guid: 26fd03652560e7f4abec092229fc38f2, 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 diff --git a/Assets/AddressableAssetsData/AssetGroups/Schemas/Localization-String-Tables-Spanish (es)_BundledAssetGroupSchema.asset.meta b/Assets/AddressableAssetsData/AssetGroups/Schemas/Localization-String-Tables-Spanish (es)_BundledAssetGroupSchema.asset.meta new file mode 100644 index 000000000..529d6ebde --- /dev/null +++ b/Assets/AddressableAssetsData/AssetGroups/Schemas/Localization-String-Tables-Spanish (es)_BundledAssetGroupSchema.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 3ddf5f47a1b265049825b193e7e93fbd +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/AddressableAssetsData/AssetGroups/Schemas/Localization-String-Tables-Spanish (es)_ContentUpdateGroupSchema.asset b/Assets/AddressableAssetsData/AssetGroups/Schemas/Localization-String-Tables-Spanish (es)_ContentUpdateGroupSchema.asset new file mode 100644 index 000000000..1fa89db33 --- /dev/null +++ b/Assets/AddressableAssetsData/AssetGroups/Schemas/Localization-String-Tables-Spanish (es)_ContentUpdateGroupSchema.asset @@ -0,0 +1,16 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5834b5087d578d24c926ce20cd31e6d6, type: 3} + m_Name: Localization-String-Tables-Spanish (es)_ContentUpdateGroupSchema + m_EditorClassIdentifier: + m_Group: {fileID: 11400000, guid: 26fd03652560e7f4abec092229fc38f2, type: 2} + m_StaticContent: 0 diff --git a/Assets/AddressableAssetsData/AssetGroups/Schemas/Localization-String-Tables-Spanish (es)_ContentUpdateGroupSchema.asset.meta b/Assets/AddressableAssetsData/AssetGroups/Schemas/Localization-String-Tables-Spanish (es)_ContentUpdateGroupSchema.asset.meta new file mode 100644 index 000000000..eed3bb436 --- /dev/null +++ b/Assets/AddressableAssetsData/AssetGroups/Schemas/Localization-String-Tables-Spanish (es)_ContentUpdateGroupSchema.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 3e5e5c2400a39814994a9fcb8eaf49a6 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Editor/Huoshan/ExpressionLocalizationTests.cs b/Assets/Editor/Huoshan/ExpressionLocalizationTests.cs new file mode 100644 index 000000000..3059607fb --- /dev/null +++ b/Assets/Editor/Huoshan/ExpressionLocalizationTests.cs @@ -0,0 +1,249 @@ +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"; + + [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")); + } + + [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 chineseTask = LocalizationKit.LocalizeParamAsync( + "l10n.hs.exp.log1.target", + chinese); + Task 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.Empty); + } + + [UnityTest] + public IEnumerator LocalizeParamAsync_ReturnsRawTextAndMissingKeyMarker() + { + Task 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 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( + "Assets/StreamingAssets/Config/params.csv"), Is.Null); + } + + [Test] + public void ExpressionCatalog_IsValidAndCaseSensitive() + { + ExpressionContentCatalog catalog = + AssetDatabase.LoadAssetAtPath(CatalogPath); + Assert.That(catalog, Is.Not.Null); + Assert.That(catalog.GetValidationErrors(), Is.Empty); + Assert.That(catalog.ActorLoopReference, Is.EqualTo("l10n.hs.exp.actor.loop")); + 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(); + try + { + SetPrivateField(catalog, "actorLoopReference", "raw"); + 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 { first, second }); + + string errors = string.Join("\n", catalog.GetValidationErrors()); + Assert.That(errors, Does.Contain("Actor Loop")); + 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 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, + "英日空条目应作为待翻译警告保留。"); + } + + 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); + } + } +} diff --git a/Assets/Scripts/Framework/Config/Param.cs.meta b/Assets/Editor/Huoshan/ExpressionLocalizationTests.cs.meta similarity index 83% rename from Assets/Scripts/Framework/Config/Param.cs.meta rename to Assets/Editor/Huoshan/ExpressionLocalizationTests.cs.meta index 3420c0a8f..b5f63475d 100644 --- a/Assets/Scripts/Framework/Config/Param.cs.meta +++ b/Assets/Editor/Huoshan/ExpressionLocalizationTests.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: c12e0d2c486f4ea6a5d4f70878b6e7c1 +guid: d6b8797332ac4876b66e381ea3c98723 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/Editor/Huoshan/ExpressionLocalizationValidator.cs b/Assets/Editor/Huoshan/ExpressionLocalizationValidator.cs new file mode 100644 index 000000000..511b2c175 --- /dev/null +++ b/Assets/Editor/Huoshan/ExpressionLocalizationValidator.cs @@ -0,0 +1,292 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using AibisDream.Framework; +using AibisDream.MiniGame.Language; +using AibisDream.Utility; +using UnityEditor; +using UnityEditor.Localization; +using UnityEngine; +using UnityEngine.Localization; +using UnityEngine.Localization.Tables; + +namespace AibisDream.EditorTests.Huoshan +{ + public enum ExpressionLocalizationIssueSeverity + { + Warning, + Error + } + + public readonly struct ExpressionLocalizationIssue + { + public ExpressionLocalizationIssue( + ExpressionLocalizationIssueSeverity severity, + string message) + { + Severity = severity; + Message = message; + } + + public ExpressionLocalizationIssueSeverity Severity { get; } + public string Message { get; } + } + + /// + /// 校验 Stage6 Express 命令、轮次 Catalog 与 Params String Table 的引用关系。 + /// 英日空值是待翻译警告,不阻断内容校验。 + /// + public static class ExpressionLocalizationValidator + { + private const string Stage6Path = "Assets/Yarn/FP/FP_Huoshan1/Stage6.yarn"; + private const string CatalogPath = + "Assets/GameContent/Feature_Huoshan/Expression/ExpressionContentCatalog.asset"; + + private static readonly Regex YarnCommandRegex = + new(@"^\s*<<(?[A-Za-z0-9_]+)(?.*?)>>", RegexOptions.Compiled); + + private static readonly Regex QuotedArgumentRegex = + new("\"(?(?:\\\\.|[^\"])*)\"", RegexOptions.Compiled); + + private static readonly Regex L10NRegex = + new(@"l10n\.(?[A-Za-z0-9._-]+)", RegexOptions.Compiled); + + private static readonly Regex ChineseRegex = + new(@"[\u3400-\u9FFF]", RegexOptions.Compiled); + + private static readonly Dictionary VisibleArgumentIndices = + new(StringComparer.Ordinal) + { + ["start_expression"] = new[] { 0, 1 }, + ["expression_lie_begin"] = new[] { 0 }, + ["expression_truth_lie_flicker"] = new[] { 0, 1 }, + ["expression_lie_morph_truth"] = new[] { 0, 1 }, + ["expression_truth_leak"] = new[] { 0 }, + ["expression_lie_shuffle"] = new[] { 0 }, + ["expression_actor_flash"] = new[] { 0 }, + ["expression_actor_flash_in"] = new[] { 0 }, + ["expression_flash_in"] = new[] { 0 }, + ["expression_actor_truth_flash"] = new[] { 0 }, + ["expression_truth_flash"] = new[] { 0 }, + ["expression_actor_word_hit"] = new[] { 0 }, + ["expression_word_hit"] = new[] { 0 }, + ["expression_actor_slot"] = new[] { 0, 1 }, + ["expression_actor_scroll"] = new[] { 0 }, + ["expression_truth_attack"] = new[] { 0 }, + ["expression_truth_destabilize"] = new[] { 0 }, + ["expression_truth_settle"] = new[] { 0 } + }; + + [MenuItem("Tools/AIBIS/Huoshan/Validate Express Localization")] + public static void ValidateFromMenu() + { + IReadOnlyList issues = Validate(); + int errorCount = issues.Count(issue => + issue.Severity == ExpressionLocalizationIssueSeverity.Error); + int warningCount = issues.Count - errorCount; + + foreach (ExpressionLocalizationIssue issue in issues) + { + if (issue.Severity == ExpressionLocalizationIssueSeverity.Error) + Debug.LogError($"[ExpressL10N] {issue.Message}"); + else + Debug.LogWarning($"[ExpressL10N] {issue.Message}"); + } + + if (errorCount == 0) + { + Debug.Log( + $"[ExpressL10N] 校验通过。errors=0, pending translations={warningCount}"); + } + } + + public static IReadOnlyList Validate() + { + var issues = new List(); + var referencedKeys = new HashSet(StringComparer.Ordinal); + + ValidateStage6(issues, referencedKeys); + ValidateCatalog(issues, referencedKeys); + ValidateParams(issues, referencedKeys); + return issues; + } + + private static void ValidateStage6( + List issues, + HashSet referencedKeys) + { + if (!File.Exists(Stage6Path)) + { + AddError(issues, $"找不到 Stage6 Yarn:{Stage6Path}"); + return; + } + + string[] lines = File.ReadAllLines(Stage6Path); + for (int lineIndex = 0; lineIndex < lines.Length; lineIndex++) + { + string line = lines[lineIndex]; + if (line.TrimStart().StartsWith("//", StringComparison.Ordinal)) + continue; + + foreach (Match keyMatch in L10NRegex.Matches(line)) + referencedKeys.Add(keyMatch.Groups["key"].Value); + + Match commandMatch = YarnCommandRegex.Match(line); + if (!commandMatch.Success) + continue; + + string command = commandMatch.Groups["command"].Value; + if (!VisibleArgumentIndices.TryGetValue(command, out int[] visibleIndices)) + continue; + + string[] arguments = QuotedArgumentRegex + .Matches(commandMatch.Groups["arguments"].Value) + .Cast() + .Select(match => match.Groups["value"].Value) + .ToArray(); + + foreach (int argumentIndex in visibleIndices) + { + if (argumentIndex >= arguments.Length) + continue; + + string value = arguments[argumentIndex]; + if (!LocalizationKit.IsLocalizedParam(value) && ChineseRegex.IsMatch(value)) + { + AddError( + issues, + $"{Stage6Path}:{lineIndex + 1} 的 {command} 玩家文字参数仍为中文硬编码:{value}"); + } + } + } + } + + private static void ValidateCatalog( + List issues, + HashSet referencedKeys) + { + ExpressionContentCatalog catalog = + AssetDatabase.LoadAssetAtPath(CatalogPath); + if (catalog == null) + { + AddError(issues, $"找不到轮次 Catalog:{CatalogPath}"); + return; + } + + foreach (string validationError in catalog.GetValidationErrors()) + AddError(issues, validationError); + + AddCatalogReference(catalog.ActorLoopReference, referencedKeys); + foreach (ExpressionRoundDefinition round in catalog.Rounds) + { + if (round == null) + continue; + AddCatalogReference(round.TargetReference, referencedKeys); + AddCatalogReference(round.TokenReference, referencedKeys); + } + } + + private static void ValidateParams( + List issues, + HashSet referencedKeys) + { + StringTableCollection collection = + LocalizationEditorSettings.GetStringTableCollection(ConstRef.ParamsTable); + if (collection == null) + { + AddError(issues, $"找不到 String Table Collection:{ConstRef.ParamsTable}"); + return; + } + + StringTable chinese = + collection.GetTable(new LocaleIdentifier("zh-Hans")) as StringTable; + StringTable english = + collection.GetTable(new LocaleIdentifier("en")) as StringTable; + StringTable japanese = + collection.GetTable(new LocaleIdentifier("ja-JP")) as StringTable; + + foreach (string key in referencedKeys.OrderBy(value => value, StringComparer.Ordinal)) + { + if (collection.SharedData.GetEntry(key) == null) + { + AddError(issues, $"Params Shared Data 缺少 Key:{key}"); + continue; + } + + StringTableEntry chineseEntry = chinese?.GetEntry(key); + if (string.IsNullOrWhiteSpace(chineseEntry?.LocalizedValue)) + { + AddError(issues, $"Params 中文值为空:{key}"); + continue; + } + + if (key.EndsWith(".tokens", StringComparison.Ordinal) || + string.Equals(key, "hs.exp.actor.loop", StringComparison.Ordinal)) + { + ValidatePipeList(issues, key, chineseEntry.LocalizedValue); + } + + AddPendingTranslation(issues, english, "en", key); + AddPendingTranslation(issues, japanese, "ja-JP", key); + } + } + + private static void ValidatePipeList( + List issues, + string key, + string value) + { + if (value.Contains('|')) + { + AddError(issues, $"Params/{key} 使用了全角分隔符“|”,必须使用半角“|”。"); + return; + } + + bool hasToken = value + .Split('|') + .Any(token => !string.IsNullOrWhiteSpace(token)); + if (!hasToken) + AddError(issues, $"Params/{key} 没有有效项。"); + } + + private static void AddPendingTranslation( + List issues, + StringTable table, + string localeCode, + string key) + { + StringTableEntry entry = table?.GetEntry(key); + if (entry == null) + { + AddError(issues, $"Params/{key} 缺少 {localeCode} 条目。"); + return; + } + + if (string.IsNullOrEmpty(entry.LocalizedValue)) + { + issues.Add(new ExpressionLocalizationIssue( + ExpressionLocalizationIssueSeverity.Warning, + $"待翻译:Params/{key}, Locale={localeCode}")); + } + } + + private static void AddCatalogReference( + string reference, + HashSet referencedKeys) + { + if (LocalizationKit.IsLocalizedParam(reference)) + referencedKeys.Add(LocalizationKit.GetL10NParamKey(reference)); + } + + private static void AddError( + List issues, + string message) + { + issues.Add(new ExpressionLocalizationIssue( + ExpressionLocalizationIssueSeverity.Error, + message)); + } + } +} diff --git a/Assets/Editor/Huoshan/ExpressionLocalizationValidator.cs.meta b/Assets/Editor/Huoshan/ExpressionLocalizationValidator.cs.meta new file mode 100644 index 000000000..9d63c8a1c --- /dev/null +++ b/Assets/Editor/Huoshan/ExpressionLocalizationValidator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5c602299e38f4bf4899abfbcbed84d77 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameContent/Feature_Huoshan/Expression/ExpressionContentCatalog.asset b/Assets/GameContent/Feature_Huoshan/Expression/ExpressionContentCatalog.asset new file mode 100644 index 000000000..174b2a156 --- /dev/null +++ b/Assets/GameContent/Feature_Huoshan/Expression/ExpressionContentCatalog.asset @@ -0,0 +1,28 @@ +%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: a86a72005fba4c99a59962fb9e92df83, type: 3} + m_Name: ExpressionContentCatalog + m_EditorClassIdentifier: + actorLoopReference: l10n.hs.exp.actor.loop + rounds: + - id: log1 + targetReference: l10n.hs.exp.log1.target + tokenReference: l10n.hs.exp.log1.tokens + nonTargetParticleCount: 16 + - id: log2 + targetReference: l10n.hs.exp.log2.target + tokenReference: l10n.hs.exp.log2.tokens + nonTargetParticleCount: 28 + - id: log3 + targetReference: l10n.hs.exp.log3.target + tokenReference: l10n.hs.exp.log3.tokens + nonTargetParticleCount: 38 diff --git a/Assets/GameContent/Feature_Huoshan/Expression/ExpressionContentCatalog.asset.meta b/Assets/GameContent/Feature_Huoshan/Expression/ExpressionContentCatalog.asset.meta new file mode 100644 index 000000000..0b42c70d1 --- /dev/null +++ b/Assets/GameContent/Feature_Huoshan/Expression/ExpressionContentCatalog.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b2d94952a18d4d178421691ab103f0fd +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameContent/Feature_Memory/Prefabs/PunchTapeForMem.prefab b/Assets/GameContent/Feature_Memory/Prefabs/PunchTapeForMem.prefab index 86eb6b864..8c12f2af3 100644 --- a/Assets/GameContent/Feature_Memory/Prefabs/PunchTapeForMem.prefab +++ b/Assets/GameContent/Feature_Memory/Prefabs/PunchTapeForMem.prefab @@ -197,7 +197,7 @@ RectTransform: m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 1} m_AnchorMax: {x: 0, y: 1} - m_AnchoredPosition: {x: 107.3, y: -4} + m_AnchoredPosition: {x: 107.3, y: -8} m_SizeDelta: {x: 106, y: 20} m_Pivot: {x: 0, y: 1} --- !u!222 &6236610923486505318 @@ -756,7 +756,7 @@ RectTransform: m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 1} m_AnchorMax: {x: 0, y: 1} - m_AnchoredPosition: {x: 107.29999, y: -25} + m_AnchoredPosition: {x: 107.29999, y: -22.6} m_SizeDelta: {x: 96, y: 20} m_Pivot: {x: 0, y: 1} --- !u!222 &6685312199246720920 diff --git a/Assets/GameContent/Feature_Memory/Prefabs/PunchTapeForPrinter.prefab b/Assets/GameContent/Feature_Memory/Prefabs/PunchTapeForPrinter.prefab index d3fed0711..8d9093f8f 100644 --- a/Assets/GameContent/Feature_Memory/Prefabs/PunchTapeForPrinter.prefab +++ b/Assets/GameContent/Feature_Memory/Prefabs/PunchTapeForPrinter.prefab @@ -280,7 +280,7 @@ RectTransform: m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 1} m_AnchorMax: {x: 0, y: 1} - m_AnchoredPosition: {x: 132.9, y: -5} + m_AnchoredPosition: {x: 132.9, y: -9} m_SizeDelta: {x: 132.5, y: 25} m_Pivot: {x: 0, y: 1} --- !u!222 &3142962418811844898 @@ -743,7 +743,7 @@ RectTransform: m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 1} m_AnchorMax: {x: 0, y: 1} - m_AnchoredPosition: {x: 132.90002, y: -31.25} + m_AnchoredPosition: {x: 132.90002, y: -29.9} m_SizeDelta: {x: 120, y: 25} m_Pivot: {x: 0, y: 1} --- !u!222 &2054489642493287172 diff --git a/Assets/Language/ChapterInfo/ChapterInfo.asset b/Assets/Language/ChapterInfo/ChapterInfo.asset index 38ef7b835..5dc963898 100644 --- a/Assets/Language/ChapterInfo/ChapterInfo.asset +++ b/Assets/Language/ChapterInfo/ChapterInfo.asset @@ -17,6 +17,9 @@ MonoBehaviour: - {fileID: 11400000, guid: 8906df9235e52fb41b11fe8776a77032, type: 2} - {fileID: 11400000, guid: bdf8ad79a9f707b45805e23c25ccdadb, type: 2} - {fileID: 11400000, guid: 9d8accfdeabc8034c986233920a17184, type: 2} + - {fileID: 11400000, guid: 63300a11309a7b34bbf7a345b123cab8, type: 2} + - {fileID: 11400000, guid: c69caec2066296840887d7e0c2eff52e, type: 2} + - {fileID: 11400000, guid: 728457f9c2ef69c489530047d495eca5, type: 2} m_Extensions: [] m_Group: String Table references: diff --git a/Assets/Language/ChapterInfo/ChapterInfo_es.asset b/Assets/Language/ChapterInfo/ChapterInfo_es.asset new file mode 100644 index 000000000..3eec10dae --- /dev/null +++ b/Assets/Language/ChapterInfo/ChapterInfo_es.asset @@ -0,0 +1,23 @@ +%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: e9620f8c34305754d8cc9a7e49e852d9, type: 3} + m_Name: ChapterInfo_es + m_EditorClassIdentifier: + m_LocaleId: + m_Code: es + m_SharedData: {fileID: 11400000, guid: 7ea8c68fdc624de4c86434963c06ec55, type: 2} + m_Metadata: + m_Items: [] + m_TableData: [] + references: + version: 2 + RefIds: [] diff --git a/Assets/Language/ChapterInfo/ChapterInfo_es.asset.meta b/Assets/Language/ChapterInfo/ChapterInfo_es.asset.meta new file mode 100644 index 000000000..c8c3b6e74 --- /dev/null +++ b/Assets/Language/ChapterInfo/ChapterInfo_es.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 728457f9c2ef69c489530047d495eca5 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Language/ChapterInfo/ChapterInfo_pt-BR.asset b/Assets/Language/ChapterInfo/ChapterInfo_pt-BR.asset new file mode 100644 index 000000000..3768452df --- /dev/null +++ b/Assets/Language/ChapterInfo/ChapterInfo_pt-BR.asset @@ -0,0 +1,23 @@ +%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: e9620f8c34305754d8cc9a7e49e852d9, type: 3} + m_Name: ChapterInfo_pt-BR + m_EditorClassIdentifier: + m_LocaleId: + m_Code: pt-BR + m_SharedData: {fileID: 11400000, guid: 7ea8c68fdc624de4c86434963c06ec55, type: 2} + m_Metadata: + m_Items: [] + m_TableData: [] + references: + version: 2 + RefIds: [] diff --git a/Assets/Language/ChapterInfo/ChapterInfo_pt-BR.asset.meta b/Assets/Language/ChapterInfo/ChapterInfo_pt-BR.asset.meta new file mode 100644 index 000000000..47854f9d2 --- /dev/null +++ b/Assets/Language/ChapterInfo/ChapterInfo_pt-BR.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 63300a11309a7b34bbf7a345b123cab8 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Language/ChapterInfo/ChapterInfo_ru.asset b/Assets/Language/ChapterInfo/ChapterInfo_ru.asset new file mode 100644 index 000000000..89512aae4 --- /dev/null +++ b/Assets/Language/ChapterInfo/ChapterInfo_ru.asset @@ -0,0 +1,23 @@ +%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: e9620f8c34305754d8cc9a7e49e852d9, type: 3} + m_Name: ChapterInfo_ru + m_EditorClassIdentifier: + m_LocaleId: + m_Code: ru + m_SharedData: {fileID: 11400000, guid: 7ea8c68fdc624de4c86434963c06ec55, type: 2} + m_Metadata: + m_Items: [] + m_TableData: [] + references: + version: 2 + RefIds: [] diff --git a/Assets/Language/ChapterInfo/ChapterInfo_ru.asset.meta b/Assets/Language/ChapterInfo/ChapterInfo_ru.asset.meta new file mode 100644 index 000000000..29f7fb121 --- /dev/null +++ b/Assets/Language/ChapterInfo/ChapterInfo_ru.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c69caec2066296840887d7e0c2eff52e +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Language/Params/Params Shared Data.asset b/Assets/Language/Params/Params Shared Data.asset index 63613a326..6698a0112 100644 --- a/Assets/Language/Params/Params Shared Data.asset +++ b/Assets/Language/Params/Params Shared Data.asset @@ -43,6 +43,118 @@ MonoBehaviour: m_Key: "\u903B\u8F91" m_Metadata: m_Items: [] + - m_Id: 200000000000001001 + m_Key: hs.exp.log1.target + m_Metadata: + m_Items: [] + - m_Id: 200000000000001002 + m_Key: hs.exp.log1.tokens + m_Metadata: + m_Items: [] + - m_Id: 200000000000001003 + m_Key: hs.exp.log2.target + m_Metadata: + m_Items: [] + - m_Id: 200000000000001004 + m_Key: hs.exp.log2.tokens + m_Metadata: + m_Items: [] + - m_Id: 200000000000001005 + m_Key: hs.exp.log3.target + m_Metadata: + m_Items: [] + - m_Id: 200000000000001006 + m_Key: hs.exp.log3.tokens + m_Metadata: + m_Items: [] + - m_Id: 200000000000001007 + m_Key: hs.exp.actor.loop + m_Metadata: + m_Items: [] + - m_Id: 200000000000001008 + m_Key: hs.exp.atk.why + m_Metadata: + m_Items: [] + - m_Id: 200000000000001009 + m_Key: hs.exp.atk.doubt + m_Metadata: + m_Items: [] + - m_Id: 200000000000001010 + m_Key: hs.exp.atk.last + m_Metadata: + m_Items: [] + - m_Id: 200000000000001011 + m_Key: hs.exp.atk.weather + m_Metadata: + m_Items: [] + - m_Id: 200000000000001012 + m_Key: hs.exp.atk.unsold + m_Metadata: + m_Items: [] + - m_Id: 200000000000001013 + m_Key: hs.exp.atk.explain + m_Metadata: + m_Items: [] + - m_Id: 200000000000001014 + m_Key: hs.exp.atk.refuse + m_Metadata: + m_Items: [] + - m_Id: 200000000000001015 + m_Key: hs.exp.atk.trust + m_Metadata: + m_Items: [] + - m_Id: 200000000000001016 + m_Key: hs.exp.atk.enough + m_Metadata: + m_Items: [] + - m_Id: 200000000000001017 + m_Key: hs.exp.atk.damn + m_Metadata: + m_Items: [] + - m_Id: 200000000000001018 + m_Key: hs.exp.atk.useless + m_Metadata: + m_Items: [] + - m_Id: 200000000000001019 + m_Key: hs.exp.atk.prove + m_Metadata: + m_Items: [] + - m_Id: 200000000000001020 + m_Key: hs.exp.atk.nothing + m_Metadata: + m_Items: [] + - m_Id: 200000000000001021 + m_Key: hs.exp.atk.catchup + m_Metadata: + m_Items: [] + - m_Id: 200000000000001022 + m_Key: hs.exp.atk.yingli + m_Metadata: + m_Items: [] + - m_Id: 200000000000001023 + m_Key: hs.exp.atk.courage + m_Metadata: + m_Items: [] + - m_Id: 200000000000001024 + m_Key: hs.exp.atk.say + m_Metadata: + m_Items: [] + - m_Id: 200000000000001025 + m_Key: hs.exp.atk.trash + m_Metadata: + m_Items: [] + - m_Id: 200000000000001026 + m_Key: hs.exp.atk.hopeless + m_Metadata: + m_Items: [] + - m_Id: 200000000000001027 + m_Key: hs.exp.atk.why_loop + m_Metadata: + m_Items: [] + - m_Id: 200000000000001028 + m_Key: hs.exp.final.win + m_Metadata: + m_Items: [] m_Metadata: m_Items: [] m_KeyGenerator: diff --git a/Assets/Language/Params/Params_en.asset b/Assets/Language/Params/Params_en.asset index 216cba05f..535805882 100644 --- a/Assets/Language/Params/Params_en.asset +++ b/Assets/Language/Params/Params_en.asset @@ -46,6 +46,118 @@ MonoBehaviour: m_Localized: Logic m_Metadata: m_Items: [] + - m_Id: 200000000000001001 + m_Localized: + m_Metadata: + m_Items: [] + - m_Id: 200000000000001002 + m_Localized: + m_Metadata: + m_Items: [] + - m_Id: 200000000000001003 + m_Localized: + m_Metadata: + m_Items: [] + - m_Id: 200000000000001004 + m_Localized: + m_Metadata: + m_Items: [] + - m_Id: 200000000000001005 + m_Localized: + m_Metadata: + m_Items: [] + - m_Id: 200000000000001006 + m_Localized: + m_Metadata: + m_Items: [] + - m_Id: 200000000000001007 + m_Localized: + m_Metadata: + m_Items: [] + - m_Id: 200000000000001008 + m_Localized: + m_Metadata: + m_Items: [] + - m_Id: 200000000000001009 + m_Localized: + m_Metadata: + m_Items: [] + - m_Id: 200000000000001010 + m_Localized: + m_Metadata: + m_Items: [] + - m_Id: 200000000000001011 + m_Localized: + m_Metadata: + m_Items: [] + - m_Id: 200000000000001012 + m_Localized: + m_Metadata: + m_Items: [] + - m_Id: 200000000000001013 + m_Localized: + m_Metadata: + m_Items: [] + - m_Id: 200000000000001014 + m_Localized: + m_Metadata: + m_Items: [] + - m_Id: 200000000000001015 + m_Localized: + m_Metadata: + m_Items: [] + - m_Id: 200000000000001016 + m_Localized: + m_Metadata: + m_Items: [] + - m_Id: 200000000000001017 + m_Localized: + m_Metadata: + m_Items: [] + - m_Id: 200000000000001018 + m_Localized: + m_Metadata: + m_Items: [] + - m_Id: 200000000000001019 + m_Localized: + m_Metadata: + m_Items: [] + - m_Id: 200000000000001020 + m_Localized: + m_Metadata: + m_Items: [] + - m_Id: 200000000000001021 + m_Localized: + m_Metadata: + m_Items: [] + - m_Id: 200000000000001022 + m_Localized: + m_Metadata: + m_Items: [] + - m_Id: 200000000000001023 + m_Localized: + m_Metadata: + m_Items: [] + - m_Id: 200000000000001024 + m_Localized: + m_Metadata: + m_Items: [] + - m_Id: 200000000000001025 + m_Localized: + m_Metadata: + m_Items: [] + - m_Id: 200000000000001026 + m_Localized: + m_Metadata: + m_Items: [] + - m_Id: 200000000000001027 + m_Localized: + m_Metadata: + m_Items: [] + - m_Id: 200000000000001028 + m_Localized: + m_Metadata: + m_Items: [] references: version: 2 RefIds: [] diff --git a/Assets/Language/Params/Params_es.asset b/Assets/Language/Params/Params_es.asset new file mode 100644 index 000000000..caedbf494 --- /dev/null +++ b/Assets/Language/Params/Params_es.asset @@ -0,0 +1,23 @@ +%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: e9620f8c34305754d8cc9a7e49e852d9, type: 3} + m_Name: Params_es + m_EditorClassIdentifier: + m_LocaleId: + m_Code: es + m_SharedData: {fileID: 11400000, guid: 4a2d391471bd9cd439150c52659e9846, type: 2} + m_Metadata: + m_Items: [] + m_TableData: [] + references: + version: 2 + RefIds: [] diff --git a/Assets/Language/Params/Params_es.asset.meta b/Assets/Language/Params/Params_es.asset.meta new file mode 100644 index 000000000..c30129b64 --- /dev/null +++ b/Assets/Language/Params/Params_es.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 1edf9254b97981643bdecd6d22ed1150 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Language/Params/Params_ja-JP.asset b/Assets/Language/Params/Params_ja-JP.asset index 08a89565e..ead8fda35 100644 --- a/Assets/Language/Params/Params_ja-JP.asset +++ b/Assets/Language/Params/Params_ja-JP.asset @@ -46,6 +46,118 @@ MonoBehaviour: m_Localized: "\u8AD6\u7406" m_Metadata: m_Items: [] + - m_Id: 200000000000001001 + m_Localized: + m_Metadata: + m_Items: [] + - m_Id: 200000000000001002 + m_Localized: + m_Metadata: + m_Items: [] + - m_Id: 200000000000001003 + m_Localized: + m_Metadata: + m_Items: [] + - m_Id: 200000000000001004 + m_Localized: + m_Metadata: + m_Items: [] + - m_Id: 200000000000001005 + m_Localized: + m_Metadata: + m_Items: [] + - m_Id: 200000000000001006 + m_Localized: + m_Metadata: + m_Items: [] + - m_Id: 200000000000001007 + m_Localized: + m_Metadata: + m_Items: [] + - m_Id: 200000000000001008 + m_Localized: + m_Metadata: + m_Items: [] + - m_Id: 200000000000001009 + m_Localized: + m_Metadata: + m_Items: [] + - m_Id: 200000000000001010 + m_Localized: + m_Metadata: + m_Items: [] + - m_Id: 200000000000001011 + m_Localized: + m_Metadata: + m_Items: [] + - m_Id: 200000000000001012 + m_Localized: + m_Metadata: + m_Items: [] + - m_Id: 200000000000001013 + m_Localized: + m_Metadata: + m_Items: [] + - m_Id: 200000000000001014 + m_Localized: + m_Metadata: + m_Items: [] + - m_Id: 200000000000001015 + m_Localized: + m_Metadata: + m_Items: [] + - m_Id: 200000000000001016 + m_Localized: + m_Metadata: + m_Items: [] + - m_Id: 200000000000001017 + m_Localized: + m_Metadata: + m_Items: [] + - m_Id: 200000000000001018 + m_Localized: + m_Metadata: + m_Items: [] + - m_Id: 200000000000001019 + m_Localized: + m_Metadata: + m_Items: [] + - m_Id: 200000000000001020 + m_Localized: + m_Metadata: + m_Items: [] + - m_Id: 200000000000001021 + m_Localized: + m_Metadata: + m_Items: [] + - m_Id: 200000000000001022 + m_Localized: + m_Metadata: + m_Items: [] + - m_Id: 200000000000001023 + m_Localized: + m_Metadata: + m_Items: [] + - m_Id: 200000000000001024 + m_Localized: + m_Metadata: + m_Items: [] + - m_Id: 200000000000001025 + m_Localized: + m_Metadata: + m_Items: [] + - m_Id: 200000000000001026 + m_Localized: + m_Metadata: + m_Items: [] + - m_Id: 200000000000001027 + m_Localized: + m_Metadata: + m_Items: [] + - m_Id: 200000000000001028 + m_Localized: + m_Metadata: + m_Items: [] references: version: 2 RefIds: [] diff --git a/Assets/Language/Params/Params_pt-BR.asset b/Assets/Language/Params/Params_pt-BR.asset new file mode 100644 index 000000000..c0628fea0 --- /dev/null +++ b/Assets/Language/Params/Params_pt-BR.asset @@ -0,0 +1,23 @@ +%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: e9620f8c34305754d8cc9a7e49e852d9, type: 3} + m_Name: Params_pt-BR + m_EditorClassIdentifier: + m_LocaleId: + m_Code: pt-BR + m_SharedData: {fileID: 11400000, guid: 4a2d391471bd9cd439150c52659e9846, type: 2} + m_Metadata: + m_Items: [] + m_TableData: [] + references: + version: 2 + RefIds: [] diff --git a/Assets/Language/Params/Params_pt-BR.asset.meta b/Assets/Language/Params/Params_pt-BR.asset.meta new file mode 100644 index 000000000..64ec76054 --- /dev/null +++ b/Assets/Language/Params/Params_pt-BR.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 6ad3c9298dbc3b642b1251ffc306eaa6 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Language/Params/Params_ru.asset b/Assets/Language/Params/Params_ru.asset new file mode 100644 index 000000000..aa0e7a050 --- /dev/null +++ b/Assets/Language/Params/Params_ru.asset @@ -0,0 +1,23 @@ +%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: e9620f8c34305754d8cc9a7e49e852d9, type: 3} + m_Name: Params_ru + m_EditorClassIdentifier: + m_LocaleId: + m_Code: ru + m_SharedData: {fileID: 11400000, guid: 4a2d391471bd9cd439150c52659e9846, type: 2} + m_Metadata: + m_Items: [] + m_TableData: [] + references: + version: 2 + RefIds: [] diff --git a/Assets/Language/Params/Params_ru.asset.meta b/Assets/Language/Params/Params_ru.asset.meta new file mode 100644 index 000000000..9fa0dc533 --- /dev/null +++ b/Assets/Language/Params/Params_ru.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b91f4281442d2b54f92fbecb5bd56426 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Language/Params/Params_zh-Hans.asset b/Assets/Language/Params/Params_zh-Hans.asset index e8c3963bd..6f3544b82 100644 --- a/Assets/Language/Params/Params_zh-Hans.asset +++ b/Assets/Language/Params/Params_zh-Hans.asset @@ -46,6 +46,118 @@ MonoBehaviour: m_Localized: "\u903B\u8F91" m_Metadata: m_Items: [] + - m_Id: 200000000000001001 + m_Localized: "\u6211\u5C31\u662F\u4E2A\u7B11\u8BDD" + m_Metadata: + m_Items: [] + - m_Id: 200000000000001002 + m_Localized: "\u54C8\u54C8|\u563F\u563F|\u5475\u5475" + m_Metadata: + m_Items: [] + - m_Id: 200000000000001003 + m_Localized: "\u4E0D\u8981\u79BB\u5F00\u6211" + m_Metadata: + m_Items: [] + - m_Id: 200000000000001004 + m_Localized: "\u54C8\u54C8\u54C8|\u563F\u563F\u563F|\u5475\u5475\u5475|\u7B11\u6B7B\u4E86|\u597D\u597D\u7B11" + m_Metadata: + m_Items: [] + - m_Id: 200000000000001005 + m_Localized: "\u4E3A\u4EC0\u4E48\u4E0D\u76F8\u4FE1\u6211" + m_Metadata: + m_Items: [] + - m_Id: 200000000000001006 + m_Localized: "\u54C8\u54C8\u54C8\u54C8|\u7B11\u6B7B\u6211\u4E86|\u592A\u597D\u7B11\u4E86|\u7EF7\u4E0D\u4F4F\u4E86|\u7B11\u4E0D\u6D3B\u4E86|\u771F\u7684\u592A\u641E\u7B11" + m_Metadata: + m_Items: [] + - m_Id: 200000000000001007 + m_Localized: "\u6CA1\u95EE\u9898|\u5187\u95EE\u9898|\u5E3D\u95EE\u9898" + m_Metadata: + m_Items: [] + - m_Id: 200000000000001008 + m_Localized: "\u4E3A\u4EC0\u4E48" + m_Metadata: + m_Items: [] + - m_Id: 200000000000001009 + m_Localized: "\u4E0D\u76F8\u4FE1\u6211" + m_Metadata: + m_Items: [] + - m_Id: 200000000000001010 + m_Localized: "\u6211\u660E\u767D\u6211\u57AB\u5E95" + m_Metadata: + m_Items: [] + - m_Id: 200000000000001011 + m_Localized: "\u5929\u6C14\u771F\u7684\u4E0D\u884C" + m_Metadata: + m_Items: [] + - m_Id: 200000000000001012 + m_Localized: "\u5356\u4E0D\u51FA\u53BB" + m_Metadata: + m_Items: [] + - m_Id: 200000000000001013 + m_Localized: "\u52AA\u529B\u89E3\u91CA" + m_Metadata: + m_Items: [] + - m_Id: 200000000000001014 + m_Localized: "\u4E0D\u613F\u610F\u76F8\u4FE1\u6211" + m_Metadata: + m_Items: [] + - m_Id: 200000000000001015 + m_Localized: "\u4E00\u70B9\u4FE1\u4EFB" + m_Metadata: + m_Items: [] + - m_Id: 200000000000001016 + m_Localized: "\u53D7\u591F\u4E86" + m_Metadata: + m_Items: [] + - m_Id: 200000000000001017 + m_Localized: "\u771F\u8BE5\u6B7B" + m_Metadata: + m_Items: [] + - m_Id: 200000000000001018 + m_Localized: "\u771F\u6CA1\u7528" + m_Metadata: + m_Items: [] + - m_Id: 200000000000001019 + m_Localized: "\u8BC1\u660E\u81EA\u5DF1" + m_Metadata: + m_Items: [] + - m_Id: 200000000000001020 + m_Localized: "\u4EC0\u4E48\u90FD\u505A\u4E0D\u5230" + m_Metadata: + m_Items: [] + - m_Id: 200000000000001021 + m_Localized: "\u8FFD\u4E0A\u4F60" + m_Metadata: + m_Items: [] + - m_Id: 200000000000001022 + m_Localized: "\u5E2E\u5230\u82F1\u7406" + m_Metadata: + m_Items: [] + - m_Id: 200000000000001023 + m_Localized: "\u6CA1\u6709\u52C7\u6C14" + m_Metadata: + m_Items: [] + - m_Id: 200000000000001024 + m_Localized: "\u8BF4\u51FA\u6765" + m_Metadata: + m_Items: [] + - m_Id: 200000000000001025 + m_Localized: "\u5E9F\u7269" + m_Metadata: + m_Items: [] + - m_Id: 200000000000001026 + m_Localized: "\u70C2\u6CE5\u6276\u4E0D\u4E0A\u5899" + m_Metadata: + m_Items: [] + - m_Id: 200000000000001027 + m_Localized: "\u4E3A\u4EC0\u4E48\u4E3A\u4EC0\u4E48" + m_Metadata: + m_Items: [] + - m_Id: 200000000000001028 + m_Localized: "\u6211\u60F3\u8D62\u4E00\u6B21" + m_Metadata: + m_Items: [] references: version: 2 RefIds: [] diff --git a/Assets/Language/UIText/UIText_en.asset b/Assets/Language/UIText/UIText_en.asset index db990ed77..647465939 100644 --- a/Assets/Language/UIText/UIText_en.asset +++ b/Assets/Language/UIText/UIText_en.asset @@ -258,19 +258,19 @@ MonoBehaviour: m_Metadata: m_Items: [] - m_Id: 200000000000000001 - m_Localized: "\u76EE\u6807\uFF1A\u5265\u79BB\u7EA2\u8272\u7684\u5E72\u6270\u8BED\u8A00\uFF0C\u805A\u96C6\u84DD\u8272\u7684\u771F\u5B9E\u8868\u8FBE" + m_Localized: m_Metadata: m_Items: [] - m_Id: 200000000000000002 - m_Localized: "\u8F93\u51FA" + m_Localized: m_Metadata: m_Items: [] - m_Id: 200000000000000003 - m_Localized: "{0}% \u8BED\u8A00\u6574\u5408\u5B8C\u6210\u5EA6" + m_Localized: m_Metadata: m_Items: [] - m_Id: 200000000000000004 - m_Localized: "{0} \u5904\u5F02\u5E38\u601D\u7EEA\u4ECD\u5728\u5E72\u6270\u8868\u8FBE" + m_Localized: m_Metadata: m_Items: [] references: diff --git a/Assets/Language/UIText/UIText_es.asset b/Assets/Language/UIText/UIText_es.asset new file mode 100644 index 000000000..2908ae0c0 --- /dev/null +++ b/Assets/Language/UIText/UIText_es.asset @@ -0,0 +1,23 @@ +%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: e9620f8c34305754d8cc9a7e49e852d9, type: 3} + m_Name: UIText_es + m_EditorClassIdentifier: + m_LocaleId: + m_Code: es + m_SharedData: {fileID: 11400000, guid: 994c69bf4b2403043bc9562f2d24ee56, type: 2} + m_Metadata: + m_Items: [] + m_TableData: [] + references: + version: 2 + RefIds: [] diff --git a/Assets/Language/UIText/UIText_es.asset.meta b/Assets/Language/UIText/UIText_es.asset.meta new file mode 100644 index 000000000..22e3e9853 --- /dev/null +++ b/Assets/Language/UIText/UIText_es.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 380c27123212d66468aa1f3ed3bf72ea +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Language/UIText/UIText_ja-JP.asset b/Assets/Language/UIText/UIText_ja-JP.asset index 147bce71f..70b897290 100644 --- a/Assets/Language/UIText/UIText_ja-JP.asset +++ b/Assets/Language/UIText/UIText_ja-JP.asset @@ -247,19 +247,19 @@ MonoBehaviour: m_Metadata: m_Items: [] - m_Id: 200000000000000001 - m_Localized: "\u76EE\u6807\uFF1A\u5265\u79BB\u7EA2\u8272\u7684\u5E72\u6270\u8BED\u8A00\uFF0C\u805A\u96C6\u84DD\u8272\u7684\u771F\u5B9E\u8868\u8FBE" + m_Localized: m_Metadata: m_Items: [] - m_Id: 200000000000000002 - m_Localized: "\u8F93\u51FA" + m_Localized: m_Metadata: m_Items: [] - m_Id: 200000000000000003 - m_Localized: "{0}% \u8BED\u8A00\u6574\u5408\u5B8C\u6210\u5EA6" + m_Localized: m_Metadata: m_Items: [] - m_Id: 200000000000000004 - m_Localized: "{0} \u5904\u5F02\u5E38\u601D\u7EEA\u4ECD\u5728\u5E72\u6270\u8868\u8FBE" + m_Localized: m_Metadata: m_Items: [] references: diff --git a/Assets/Language/UIText/UIText_pt-BR.asset b/Assets/Language/UIText/UIText_pt-BR.asset new file mode 100644 index 000000000..0876510d6 --- /dev/null +++ b/Assets/Language/UIText/UIText_pt-BR.asset @@ -0,0 +1,23 @@ +%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: e9620f8c34305754d8cc9a7e49e852d9, type: 3} + m_Name: UIText_pt-BR + m_EditorClassIdentifier: + m_LocaleId: + m_Code: pt-BR + m_SharedData: {fileID: 11400000, guid: 994c69bf4b2403043bc9562f2d24ee56, type: 2} + m_Metadata: + m_Items: [] + m_TableData: [] + references: + version: 2 + RefIds: [] diff --git a/Assets/Language/UIText/UIText_pt-BR.asset.meta b/Assets/Language/UIText/UIText_pt-BR.asset.meta new file mode 100644 index 000000000..32d4a2d7b --- /dev/null +++ b/Assets/Language/UIText/UIText_pt-BR.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7d496f91e240bb143b62a6239c817d09 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Language/UIText/UIText_ru.asset b/Assets/Language/UIText/UIText_ru.asset new file mode 100644 index 000000000..48581a69d --- /dev/null +++ b/Assets/Language/UIText/UIText_ru.asset @@ -0,0 +1,23 @@ +%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: e9620f8c34305754d8cc9a7e49e852d9, type: 3} + m_Name: UIText_ru + m_EditorClassIdentifier: + m_LocaleId: + m_Code: ru + m_SharedData: {fileID: 11400000, guid: 994c69bf4b2403043bc9562f2d24ee56, type: 2} + m_Metadata: + m_Items: [] + m_TableData: [] + references: + version: 2 + RefIds: [] diff --git a/Assets/Language/UIText/UIText_ru.asset.meta b/Assets/Language/UIText/UIText_ru.asset.meta new file mode 100644 index 000000000..6c8b5b353 --- /dev/null +++ b/Assets/Language/UIText/UIText_ru.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: f679069e455200841a92c27186383940 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scenes/HuoShanFixScene.unity b/Assets/Scenes/HuoShanFixScene.unity index ce31dc270..0f71c9d1c 100644 --- a/Assets/Scenes/HuoShanFixScene.unity +++ b/Assets/Scenes/HuoShanFixScene.unity @@ -16127,6 +16127,7 @@ MonoBehaviour: expressionView: {fileID: 5162511683866016917} logReleasePresentation: {fileID: 2138889543} screenPresentation: {fileID: 2138889544} + contentCatalog: {fileID: 11400000, guid: b2d94952a18d4d178421691ab103f0fd, type: 2} defaultAnxietyPhrases: - "\u6211\u597D\u6015" - "\u600E\u4E48\u529E" diff --git a/Assets/Scripts/AibisDream.Menus/AibisAssetMenus.cs b/Assets/Scripts/AibisDream.Menus/AibisAssetMenus.cs index 044cf2672..b35fbb864 100644 --- a/Assets/Scripts/AibisDream.Menus/AibisAssetMenus.cs +++ b/Assets/Scripts/AibisDream.Menus/AibisAssetMenus.cs @@ -17,6 +17,7 @@ namespace AibisDream public const string FixWhackMoleData = Root + "/维修·打地鼠/关卡数据"; public const string HuoShanEmotionWave = Root + "/火山/情绪波配置"; public const string HuoShanWaveform = Root + "/火山/波形配置"; + public const string HuoShanExpressionContent = Root + "/火山/表达内容配置"; public const string BlockPuzzleValidator = Root + "/方块拼图/验证器"; public const string MemoryPunchTapeCatalog = Root + "/记忆/打孔带目录"; } diff --git a/Assets/Scripts/AssetRefs/ConstRef.cs b/Assets/Scripts/AssetRefs/ConstRef.cs index b3a306507..e5f75494e 100644 --- a/Assets/Scripts/AssetRefs/ConstRef.cs +++ b/Assets/Scripts/AssetRefs/ConstRef.cs @@ -52,6 +52,7 @@ namespace AibisDream.Utility public const string UITextTable = "UIText"; public const string ChapterInfoTable = "ChapterInfo"; + public const string ParamsTable = "Params"; #endregion diff --git a/Assets/Scripts/Dialog System/AibisLineProvider.cs b/Assets/Scripts/Dialog System/AibisLineProvider.cs index 825e9baef..9c6041335 100644 --- a/Assets/Scripts/Dialog System/AibisLineProvider.cs +++ b/Assets/Scripts/Dialog System/AibisLineProvider.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Threading; +using System.Threading.Tasks; using AibisDream.Framework; using AibisDream.Utility; using UnityEngine; @@ -79,10 +80,16 @@ namespace AibisDream return LocalizedLine.InvalidLine; } - // 对 substitutions 做本地化解析(保留旧 LocalisedLineProvider 的能力) - for (int i = 0; i < line.Substitutions.Length; i++) + // 对 substitutions 并行做本地化解析(保留旧 LocalisedLineProvider 的能力)。 + if (line.Substitutions.Length > 0) { - line.Substitutions[i] = LocalizationKit.LocalizeParam(line.Substitutions[i]); + var localizationTasks = new Task[line.Substitutions.Length]; + for (int i = 0; i < line.Substitutions.Length; i++) + localizationTasks[i] = LocalizationKit.LocalizeParamAsync(line.Substitutions[i]); + + string[] localizedSubstitutions = await Task.WhenAll(localizationTasks); + for (int i = 0; i < localizedSubstitutions.Length; i++) + line.Substitutions[i] = localizedSubstitutions[i]; } // 先展开项目级语义标记,再保留旧 LocalisedLineProvider 的文本转义处理。 diff --git a/Assets/Scripts/FixSystem/PunchTape/PunchTapeItem.cs b/Assets/Scripts/FixSystem/PunchTape/PunchTapeItem.cs index 933555f62..6cf245206 100644 --- a/Assets/Scripts/FixSystem/PunchTape/PunchTapeItem.cs +++ b/Assets/Scripts/FixSystem/PunchTape/PunchTapeItem.cs @@ -25,6 +25,7 @@ namespace AibisDream.FixSystem private PunchTape punchTapeData; private PunchTapeDefinition punchTapeDefinition; + private int localizationRefreshVersion; private void OnEnable() { @@ -34,6 +35,7 @@ namespace AibisDream.FixSystem private void OnDisable() { LocalizationSettings.SelectedLocaleChanged -= HandleSelectedLocaleChanged; + localizationRefreshVersion++; } public void Setup(PunchTape punchTape, PunchTapeDefinition definition) @@ -57,8 +59,7 @@ namespace AibisDream.FixSystem if (nameText != null) { - RefreshLocalizedName(LocalizationKit.GetLocaleKind( - LocalizationSettings.SelectedLocale?.Identifier.Code)); + RefreshLocalizedName(LocalizationSettings.SelectedLocale); } memoryIndexLabelText?.RefreshString(); @@ -73,17 +74,26 @@ namespace AibisDream.FixSystem private void HandleSelectedLocaleChanged(Locale locale) { - RefreshLocalizedName(LocalizationKit.GetLocaleKind(locale?.Identifier.Code)); + RefreshLocalizedName(locale); } - private void RefreshLocalizedName(LocaleKind localeKind) + private async void RefreshLocalizedName(Locale locale) { if (nameText == null || punchTapeDefinition == null) return; + int version = ++localizationRefreshVersion; + string localizedName = await LocalizationKit.LocalizeParamAsync( + punchTapeDefinition.DisplayNameParam, + locale); + if (version != localizationRefreshVersion || + nameText == null || + punchTapeDefinition == null) + return; + nameText.StringReference.Arguments = new object[] { - LocalizationKit.LocalizeParam(punchTapeDefinition.DisplayNameParam, localeKind) + localizedName }; nameText.RefreshString(); } diff --git a/Assets/Scripts/Framework/Config/ConfigUtil.cs b/Assets/Scripts/Framework/Config/ConfigUtil.cs index 50372eeea..2bc332505 100644 --- a/Assets/Scripts/Framework/Config/ConfigUtil.cs +++ b/Assets/Scripts/Framework/Config/ConfigUtil.cs @@ -13,10 +13,8 @@ namespace AibisDream.Kit public class ConfigUtil : SingletonBase { private const string CharacterConfigPath = "/Config/character.csv"; - private const string ParamConfigPath = "/Config/params.csv"; private Dictionary _characters; - private Dictionary _params; /// /// 用于单例模式 @@ -41,7 +39,6 @@ namespace AibisDream.Kit public override void OnSingletonInit() { InitCharacterConfig(); - InitParamConfig(); } private void InitCharacterConfig() @@ -52,15 +49,6 @@ namespace AibisDream.Kit .ToDictionary(item => item.Key, item => item); } - private void InitParamConfig() - { - var paramList = CsvUtil.ReadAsBean(Application.streamingAssetsPath + ParamConfigPath); - _params = paramList - .Where(item => !string.IsNullOrWhiteSpace(item.Key)) - .ToDictionary(item => item.Key, item => item); - } - - /// /// 按key获取角色预设 /// @@ -72,17 +60,6 @@ namespace AibisDream.Kit return _characters.TryGetValue(key, out character); } - /// - /// 按key获取参数本地化配置 - /// - /// key - /// 参数配置 - /// 是否能获取 - public bool TryGetParam(string key, out Param param) - { - return _params.TryGetValue(key, out param); - } - /// /// 按类型读取Json配置 /// @@ -176,4 +153,4 @@ namespace AibisDream.Kit return fileNames; } } -} \ No newline at end of file +} diff --git a/Assets/Scripts/Framework/Config/Param.cs b/Assets/Scripts/Framework/Config/Param.cs deleted file mode 100644 index 021cfca82..000000000 --- a/Assets/Scripts/Framework/Config/Param.cs +++ /dev/null @@ -1,27 +0,0 @@ -using AibisDream.Framework; - -namespace AibisDream.Kit -{ - public class Param - { - public string Key { get; set; } - public string Cn { get; set; } - public string En { get; set; } - public string Ja { get; set; } - - public string LocalizeByKind(LocaleKind kind) - { - switch (kind) - { - case LocaleKind.En: - return string.IsNullOrWhiteSpace(En) ? Key : En; - case LocaleKind.Ja: - if (!string.IsNullOrWhiteSpace(Ja)) return Ja; - return string.IsNullOrWhiteSpace(Cn) ? Key : Cn; - case LocaleKind.Cn: - default: - return string.IsNullOrWhiteSpace(Cn) ? Key : Cn; - } - } - } -} diff --git a/Assets/Scripts/Framework/OtherKit/LocalizationKit.cs b/Assets/Scripts/Framework/OtherKit/LocalizationKit.cs index 76ad11c66..789e3e46a 100644 --- a/Assets/Scripts/Framework/OtherKit/LocalizationKit.cs +++ b/Assets/Scripts/Framework/OtherKit/LocalizationKit.cs @@ -1,5 +1,7 @@ using System.Collections; -using AibisDream.Kit; +using System; +using System.Threading.Tasks; +using AibisDream.Utility; using UnityEngine; using UnityEngine.Localization; using UnityEngine.Localization.Settings; @@ -127,39 +129,85 @@ namespace AibisDream.Framework } /// - /// 本地化参数 + /// 判断字符串是否为 l10n 参数引用。 /// - /// 参数名 - /// - public static string LocalizeParam(string param) + public static bool IsLocalizedParam(string value) { - return LocalizeParam(param, CurrentLocaleKind); + return !string.IsNullOrEmpty(value) && + value.StartsWith(LocalizationPrefix, StringComparison.Ordinal); } /// - /// 按指定语言列解析本地化参数,供需要响应 Locale 切换的非 Yarn UI 使用。 + /// 从 Params String Table 异步解析 l10n 参数。非 l10n 参数原样返回。 + /// 当前 Locale 条目为空时保留空值,不使用任何 Locale 回退。 /// - public static string LocalizeParam(string param, LocaleKind localeKind) + public static async Task LocalizeParamAsync(string param, Locale locale = null) { - // 非本地化参数直接返回 - if (string.IsNullOrEmpty(param)) return param; - if (!param.StartsWith(LocalizationPrefix)) return param; + if (!IsLocalizedParam(param)) + return param; - var localizationKey = param[LocalizationPrefix.Length..]; - if (ConfigUtil.Instance.TryGetParam(localizationKey, out var value)) + string localizationKey = GetL10NParamKey(param); + if (string.IsNullOrEmpty(localizationKey)) { - return value.LocalizeByKind(localeKind); + Debug.LogError("[LocalizationKit] l10n 参数缺少 Entry Key。"); + return "⟦empty⟧"; } - // 如果找不到,则使用原始值 - Debug.LogWarning($"Variable Key '{localizationKey}' not found in params csv."); - return localizationKey; + try + { + await LocalizationSettings.InitializationOperation.Task; + Locale resolvedLocale = locale != null ? locale : LocalizationSettings.SelectedLocale; + var operation = LocalizationSettings.StringDatabase.GetTableEntryAsync( + ConstRef.ParamsTable, + localizationKey, + resolvedLocale, + FallbackBehavior.DontUseFallback); + var result = await operation.Task; + if (result.Entry != null) + return result.Entry.LocalizedValue ?? string.Empty; + + string localeCode = resolvedLocale != null + ? resolvedLocale.Identifier.Code + : ""; + Debug.LogError( + $"[LocalizationKit] Params 缺少条目:Table={ConstRef.ParamsTable}, " + + $"Key={localizationKey}, Locale={localeCode}。"); + return $"⟦{localizationKey}⟧"; + } + catch (Exception exception) + { + string localeCode = locale != null + ? locale.Identifier.Code + : LocalizationSettings.SelectedLocale?.Identifier.Code ?? ""; + Debug.LogError( + $"[LocalizationKit] Params 查询失败:Table={ConstRef.ParamsTable}, " + + $"Key={localizationKey}, Locale={localeCode}。\n{exception}"); + return $"⟦{localizationKey}⟧"; + } + } + + /// + /// 协程版 l10n 参数解析,供 Yarn Command 和 MonoBehaviour 使用。 + /// + public static IEnumerator LocalizeParam( + string param, + Locale locale, + Action completed) + { + Task task = LocalizeParamAsync(param, locale); + while (!task.IsCompleted) + yield return null; + + string result = task.Status == TaskStatus.RanToCompletion + ? task.Result + : $"⟦{GetL10NParamKey(param)}⟧"; + completed?.Invoke(result); } public static string GetL10NParamKey(string key) { // 本地化参数则去除前缀,非本地化参数直接返回 - return key.StartsWith(LocalizationPrefix) ? key[LocalizationPrefix.Length..] : key; + return IsLocalizedParam(key) ? key[LocalizationPrefix.Length..] : key; } /// diff --git a/Assets/Scripts/MiniGame/HuoShan/Language/ExpressionContentCatalog.cs b/Assets/Scripts/MiniGame/HuoShan/Language/ExpressionContentCatalog.cs new file mode 100644 index 000000000..aa6f9f8d5 --- /dev/null +++ b/Assets/Scripts/MiniGame/HuoShan/Language/ExpressionContentCatalog.cs @@ -0,0 +1,130 @@ +using System; +using System.Collections.Generic; +using AibisDream.Framework; +using UnityEngine; + +namespace AibisDream.MiniGame.Language +{ + [Serializable] + public sealed class ExpressionRoundDefinition + { + [SerializeField] private string id; + [SerializeField] private string targetReference; + [SerializeField] private string tokenReference; + [SerializeField, Min(0)] private int nonTargetParticleCount; + + public string Id => id; + public string TargetReference => targetReference; + public string TokenReference => tokenReference; + public int NonTargetParticleCount => nonTargetParticleCount; + + public bool TryValidate(out string error) + { + if (string.IsNullOrWhiteSpace(id)) + { + error = "轮次 ID 不能为空。"; + return false; + } + + if (!LocalizationKit.IsLocalizedParam(targetReference)) + { + error = $"轮次 '{id}' 的目标文字必须配置为 l10n.* 引用。"; + return false; + } + + if (!LocalizationKit.IsLocalizedParam(tokenReference)) + { + error = $"轮次 '{id}' 的干扰 token 必须配置为 l10n.* 引用。"; + return false; + } + + if (nonTargetParticleCount < 0) + { + error = $"轮次 '{id}' 的非目标粒子数量不能为负数。"; + return false; + } + + error = null; + return true; + } + } + + [CreateAssetMenu( + fileName = "ExpressionContentCatalog", + menuName = AibisAssetMenus.HuoShanExpressionContent)] + public sealed class ExpressionContentCatalog : ScriptableObject + { + [SerializeField] private string actorLoopReference; + [SerializeField] private List rounds = new(); + + private Dictionary roundById; + + public string ActorLoopReference => actorLoopReference; + public IReadOnlyList Rounds => rounds; + + public bool TryGetRound(string roundId, out ExpressionRoundDefinition definition) + { + EnsureLookup(); + if (string.IsNullOrWhiteSpace(roundId)) + { + definition = null; + return false; + } + + return roundById.TryGetValue(roundId, out definition); + } + + public List GetValidationErrors() + { + var errors = new List(); + if (!LocalizationKit.IsLocalizedParam(actorLoopReference)) + errors.Add("Actor Loop 必须配置为 l10n.* 引用。"); + + var ids = new HashSet(StringComparer.Ordinal); + if (rounds == null || rounds.Count == 0) + { + errors.Add("Catalog 中没有表达轮次。"); + return errors; + } + + for (int i = 0; i < rounds.Count; i++) + { + ExpressionRoundDefinition round = rounds[i]; + if (round == null) + { + errors.Add($"rounds[{i}] 为空。"); + continue; + } + + if (!round.TryValidate(out string error)) + errors.Add(error); + if (!string.IsNullOrWhiteSpace(round.Id) && !ids.Add(round.Id)) + errors.Add($"存在重复轮次 ID:'{round.Id}'。"); + } + + return errors; + } + + private void EnsureLookup() + { + if (roundById != null) + return; + + roundById = new Dictionary(StringComparer.Ordinal); + if (rounds == null) + return; + + foreach (ExpressionRoundDefinition round in rounds) + { + if (round == null || string.IsNullOrWhiteSpace(round.Id) || roundById.ContainsKey(round.Id)) + continue; + roundById.Add(round.Id, round); + } + } + + private void OnValidate() + { + roundById = null; + } + } +} diff --git a/Assets/Scripts/MiniGame/HuoShan/Language/ExpressionContentCatalog.cs.meta b/Assets/Scripts/MiniGame/HuoShan/Language/ExpressionContentCatalog.cs.meta new file mode 100644 index 000000000..afcdaa8e8 --- /dev/null +++ b/Assets/Scripts/MiniGame/HuoShan/Language/ExpressionContentCatalog.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a86a72005fba4c99a59962fb9e92df83 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/MiniGame/HuoShan/Language/ExpressionManager.cs b/Assets/Scripts/MiniGame/HuoShan/Language/ExpressionManager.cs index 6bdd898f3..8a3322ce0 100644 --- a/Assets/Scripts/MiniGame/HuoShan/Language/ExpressionManager.cs +++ b/Assets/Scripts/MiniGame/HuoShan/Language/ExpressionManager.cs @@ -4,6 +4,7 @@ using UnityEngine; using AibisDream.FixSystem; using AibisDream.MiniGame.Language; using System.Collections.Generic; +using UnityEngine.Localization; namespace AibisDream { @@ -17,6 +18,7 @@ namespace AibisDream [SerializeField] private GameObject expressionView; [SerializeField] private LogReleasePresentationController logReleasePresentation; [SerializeField] private ExpressionScreenPresentationController screenPresentation; + [SerializeField] private ExpressionContentCatalog contentCatalog; [Header("默认配置")] [SerializeField] private List defaultAnxietyPhrases = new List @@ -54,6 +56,18 @@ namespace AibisDream [SerializeField, Range(0.3f, 1f)] private float screenStressCap = 0.85f; public LogReleasePresentationController LogReleasePresentation => logReleasePresentation; + public ExpressionContentCatalog ContentCatalog => contentCatalog; + public Locale ActiveExpressionLocale { get; private set; } + + public Locale GetExpressionLocale(Locale fallback) + { + return ActiveExpressionLocale != null ? ActiveExpressionLocale : fallback; + } + + public void SetActiveExpressionLocale(Locale locale) + { + ActiveExpressionLocale = locale; + } /// 当前屏幕 Glitch 档位(0–1),由 expression_screen_glitch 持续保持。 public float ScreenGlitchLevel => screenGlitchIntensity; @@ -274,6 +288,7 @@ namespace AibisDream /// public void CloseView() { + ActiveExpressionLocale = null; screenPresentation?.ResetImmediate(); ResetScreenGlitchState(); ApplyScreenGlitchProperties(); @@ -941,6 +956,7 @@ namespace AibisDream private void OnDisable() { + ActiveExpressionLocale = null; ResetScreenGlitchState(); ApplyScreenGlitchProperties(); diff --git a/Assets/Scripts/MiniGame/HuoShan/Language/ExpressionTextTokenizer.cs b/Assets/Scripts/MiniGame/HuoShan/Language/ExpressionTextTokenizer.cs new file mode 100644 index 000000000..e01958d45 --- /dev/null +++ b/Assets/Scripts/MiniGame/HuoShan/Language/ExpressionTextTokenizer.cs @@ -0,0 +1,108 @@ +using System.Collections.Generic; +using System.Globalization; + +namespace AibisDream.MiniGame.Language +{ + /// + /// 将玩家可见文字按 Unicode 文本元素拆分。空白保留在布局中,但不生成粒子。 + /// 随机粒子字符池不使用本类,维持现有行为。 + /// + public static class ExpressionTextTokenizer + { + public readonly struct Layout + { + public Layout(List visibleElements, List offsets) + { + VisibleElements = visibleElements; + Offsets = offsets; + } + + public IReadOnlyList VisibleElements { get; } + public IReadOnlyList Offsets { get; } + public int Count => VisibleElements?.Count ?? 0; + } + + public static List GetVisibleElements(string text) + { + var result = new List(); + if (string.IsNullOrEmpty(text)) + return result; + + TextElementEnumerator enumerator = StringInfo.GetTextElementEnumerator(text); + while (enumerator.MoveNext()) + { + string element = enumerator.GetTextElement(); + if (!string.IsNullOrWhiteSpace(element)) + result.Add(element); + } + + return result; + } + + public static Layout BuildLayout( + string text, + float characterSpacing, + float whitespaceSpacingMultiplier) + { + var visible = new List(); + var positions = new List(); + if (string.IsNullOrEmpty(text)) + return new Layout(visible, positions); + + float spacing = characterSpacing > 0f ? characterSpacing : 1f; + float whitespaceAdvance = spacing * whitespaceSpacingMultiplier; + float cursor = 0f; + TextElementEnumerator enumerator = StringInfo.GetTextElementEnumerator(text); + while (enumerator.MoveNext()) + { + string element = enumerator.GetTextElement(); + if (string.IsNullOrWhiteSpace(element)) + { + if (visible.Count > 0) + cursor += whitespaceAdvance; + continue; + } + + visible.Add(element); + positions.Add(cursor); + cursor += spacing; + } + + if (positions.Count > 0) + { + float center = (positions[0] + positions[positions.Count - 1]) * 0.5f; + for (int i = 0; i < positions.Count; i++) + positions[i] -= center; + } + + return new Layout(visible, positions); + } + + public static int FindVisibleSequence( + IReadOnlyList source, + IReadOnlyList fragment) + { + if (source == null || fragment == null || fragment.Count == 0 || fragment.Count > source.Count) + return -1; + + int lastStart = source.Count - fragment.Count; + for (int start = 0; start <= lastStart; start++) + { + bool matches = true; + for (int i = 0; i < fragment.Count; i++) + { + if (!string.Equals(source[start + i], fragment[i], System.StringComparison.Ordinal)) + { + matches = false; + break; + } + } + + if (matches) + return start; + } + + return -1; + } + } +} diff --git a/Assets/Scripts/MiniGame/HuoShan/Language/ExpressionTextTokenizer.cs.meta b/Assets/Scripts/MiniGame/HuoShan/Language/ExpressionTextTokenizer.cs.meta new file mode 100644 index 000000000..ed272d648 --- /dev/null +++ b/Assets/Scripts/MiniGame/HuoShan/Language/ExpressionTextTokenizer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f8ba4ca26b9b46649c0da7161dc9c756 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/MiniGame/HuoShan/Language/LanguageParticleManager.cs b/Assets/Scripts/MiniGame/HuoShan/Language/LanguageParticleManager.cs index 4939637b4..b3d8ed0f0 100644 --- a/Assets/Scripts/MiniGame/HuoShan/Language/LanguageParticleManager.cs +++ b/Assets/Scripts/MiniGame/HuoShan/Language/LanguageParticleManager.cs @@ -93,6 +93,7 @@ namespace AibisDream.MiniGame.Language [SerializeField] private float floatingTextFontSize = 3f; // 背景浮动文字大小 [SerializeField] private bool targetParticleBold = true; // 目标粒子是否加粗 [SerializeField] private bool nonTargetParticleBold = false; // 非目标粒子是否加粗 + [SerializeField, Range(0.1f, 2f)] private float whitespaceSpacingMultiplier = 0.6f; [Header("非目标粒子干扰效果")] [SerializeField, Range(0f, 0.1f)] private float nonTargetShakeIntensity = 0.02f; // 非目标粒子抖动强度 @@ -354,10 +355,18 @@ namespace AibisDream.MiniGame.Language CaptureDefaultCandidateCountFromInspector(); string useSentence = string.IsNullOrEmpty(sentence) ? "这是一个测试示例" : sentence; + int visibleTargetCount = ExpressionTextTokenizer.GetVisibleElements(useSentence).Count; + if (visibleTargetCount == 0) + { + Debug.LogError("[LanguageParticleManager] 目标句没有可显示的 Unicode 文本元素;初始化已中止。"); + return; + } if (nonTargetParticleTotal >= 0) { - candidateCount = Mathf.Max(useSentence.Length + nonTargetParticleTotal, useSentence.Length); + candidateCount = Mathf.Max( + visibleTargetCount + nonTargetParticleTotal, + visibleTargetCount); } else { @@ -409,7 +418,7 @@ namespace AibisDream.MiniGame.Language TextParticle.SetAnxietyPhrases(anxietyPhrases); // 根据目标句子更新目标粒子数量 - redParticleCount = Mathf.Min(targetSentence.Length, candidateCount); + redParticleCount = Mathf.Min(visibleTargetCount, candidateCount); // 重新初始化游戏状态并播放入场动画 ResetGame(true); @@ -1247,7 +1256,11 @@ namespace AibisDream.MiniGame.Language SetStatusUIVisibility(true, 0f); orderedRedParticles = GetOrderedRedParticles(); - int displayLength = Mathf.Min(orderedRedParticles.Count, targetSentence.Length); + ExpressionTextTokenizer.Layout layout = ExpressionTextTokenizer.BuildLayout( + targetSentence, + 0.9f, + whitespaceSpacingMultiplier); + int displayLength = Mathf.Min(orderedRedParticles.Count, layout.Count); if (displayLength < orderedRedParticles.Count) { @@ -1256,20 +1269,17 @@ namespace AibisDream.MiniGame.Language for (int i = 0; i < displayLength; i++) { - finalTargetCharacters.Add(targetSentence[i].ToString()); + finalTargetCharacters.Add(layout.VisibleElements[i]); } Vector3 displayCenter = worldCanvas != null ? worldCanvas.transform.position : Vector3.zero; if (finalTargetCharacters.Count > 0) { - float spacing = 0.9f; - float totalWidth = spacing * Mathf.Max(0, finalTargetCharacters.Count - 1); - float startX = -totalWidth / 2f; finalTargetPositions.Clear(); Bounds clip = GetCompletionClipBounds(); for (int i = 0; i < finalTargetCharacters.Count; i++) { - Vector3 pos = displayCenter + new Vector3(startX + i * spacing, 0f, 0f); + Vector3 pos = displayCenter + new Vector3(layout.Offsets[i], 0f, 0f); finalTargetPositions.Add(ClampPositionToBounds(pos, clip)); } } @@ -2151,6 +2161,7 @@ namespace AibisDream.MiniGame.Language List particles = GetPresentationPhraseParticles(); if (!ValidatePresentationPhrase(text, particles, "真话失稳")) yield break; + List characters = ExpressionTextTokenizer.GetVisibleElements(text); Color unstableColor = truthAttackColor; if (!string.IsNullOrWhiteSpace(colorHex)) @@ -2169,8 +2180,8 @@ namespace AibisDream.MiniGame.Language float totalDuration = Mathf.Max(0.12f, duration); float resolvedShakeAmplitude = Mathf.Max(0f, shakeAmplitude); float resolvedShakeSpeed = Mathf.Max(0f, shakeSpeed); - List destinations = CalculatePresentationPhrasePositions(text.Length); - UpdateFinalPresentationPhrase(text, destinations); + List destinations = CalculatePresentationPhrasePositions(text); + UpdateFinalPresentationPhrase(characters, destinations); for (int i = 0; i < particles.Count; i++) { @@ -2197,13 +2208,13 @@ namespace AibisDream.MiniGame.Language particle.resolvedColorProgress = 1f; particle.calmProgress = 1f; - if (i >= text.Length) + if (i >= characters.Count) { particle.alpha = 0f; continue; } - particle.ForceSetCharacter(text[i].ToString()); + particle.ForceSetCharacter(characters[i]); particle.alpha = 1f; particle.SetFocusInterference( unstableColor, @@ -2221,7 +2232,7 @@ namespace AibisDream.MiniGame.Language yield return new WaitForSeconds(totalDuration); - for (int i = 0; i < text.Length && i < particles.Count; i++) + for (int i = 0; i < characters.Count && i < particles.Count; i++) { CandidateParticle particle = particles[i]; if (particle == null) @@ -2239,6 +2250,7 @@ namespace AibisDream.MiniGame.Language List particles = GetPresentationPhraseParticles(); if (!ValidatePresentationPhrase(text, particles, "真话攻击")) yield break; + List characters = ExpressionTextTokenizer.GetVisibleElements(text); PreparePresentationPhraseControl(); @@ -2247,8 +2259,8 @@ namespace AibisDream.MiniGame.Language float travelDuration = totalDuration * 0.68f; float settleDuration = Mathf.Max(0.04f, totalDuration - travelDuration); int directionSign = truthAttackSequenceIndex++ % 2 == 0 ? -1 : 1; - List destinations = CalculatePresentationPhrasePositions(text.Length); - UpdateFinalPresentationPhrase(text, destinations); + List destinations = CalculatePresentationPhrasePositions(text); + UpdateFinalPresentationPhrase(characters, destinations); for (int i = 0; i < particles.Count; i++) { @@ -2276,7 +2288,7 @@ namespace AibisDream.MiniGame.Language particle.resolvedColorProgress = 1f; particle.calmProgress = 1f; - if (i >= text.Length) + if (i >= characters.Count) { DOTween.To( () => particle.alpha, @@ -2288,7 +2300,7 @@ namespace AibisDream.MiniGame.Language continue; } - particle.ForceSetCharacter(text[i].ToString()); + particle.ForceSetCharacter(characters[i]); particle.alpha = 1f; particle.focusInterferenceProgress = 1f; @@ -2325,7 +2337,7 @@ namespace AibisDream.MiniGame.Language yield return new WaitForSeconds(totalDuration); - for (int i = 0; i < text.Length && i < particles.Count; i++) + for (int i = 0; i < characters.Count && i < particles.Count; i++) { CandidateParticle particle = particles[i]; if (particle == null) @@ -2344,6 +2356,7 @@ namespace AibisDream.MiniGame.Language List particles = GetPresentationPhraseParticles(); if (!ValidatePresentationPhrase(text, particles, "真话稳定")) yield break; + List characters = ExpressionTextTokenizer.GetVisibleElements(text); PreparePresentationPhraseControl(); @@ -2384,8 +2397,8 @@ namespace AibisDream.MiniGame.Language yield return new WaitForSeconds(breakDuration); - List destinations = CalculatePresentationPhrasePositions(text.Length); - UpdateFinalPresentationPhrase(text, destinations); + List destinations = CalculatePresentationPhrasePositions(text); + UpdateFinalPresentationPhrase(characters, destinations); for (int i = 0; i < particles.Count; i++) { CandidateParticle particle = particles[i]; @@ -2410,14 +2423,14 @@ namespace AibisDream.MiniGame.Language particle.resolvedColorProgress = 1f; particle.calmProgress = 1f; - if (i >= text.Length) + if (i >= characters.Count) { particle.alpha = 0f; particle.ClearFocusInterference(); continue; } - particle.ForceSetCharacter(text[i].ToString()); + particle.ForceSetCharacter(characters[i]); particle.SetFocusInterference(truthAttackColor, 0f, 0f); particle.focusInterferenceProgress = 1f; particle.alpha = 0.15f; @@ -2453,8 +2466,8 @@ namespace AibisDream.MiniGame.Language if (particle == null) continue; particle.ClearFocusInterference(); - particle.alpha = i < text.Length ? 1f : 0f; - if (i < text.Length) + particle.alpha = i < characters.Count ? 1f : 0f; + if (i < characters.Count) { particle.transform.position = destinations[i]; particle.transform.localScale = Vector3.one * focusZoomScale; @@ -2494,10 +2507,17 @@ namespace AibisDream.MiniGame.Language return false; } - if (text.Length > particles.Count) + int requiredParticles = ExpressionTextTokenizer.GetVisibleElements(text).Count; + if (requiredParticles == 0) + { + Debug.LogWarning($"[LanguageParticleManager] {context}没有可显示的文本元素;已安全跳过。"); + return false; + } + + if (requiredParticles > particles.Count) { Debug.LogWarning( - $"[LanguageParticleManager] {context}“{text}”需要 {text.Length} 个目标粒子," + + $"[LanguageParticleManager] {context}“{text}”需要 {requiredParticles} 个目标粒子," + $"当前只有 {particles.Count} 个;为避免截断已跳过。"); return false; } @@ -2521,27 +2541,32 @@ namespace AibisDream.MiniGame.Language connectionRenderer.TargetAlphaScale = 0f; } - private List CalculatePresentationPhrasePositions(int characterCount) + private List CalculatePresentationPhrasePositions(string text) { - var positions = new List(Mathf.Max(0, characterCount)); - Vector3 center = worldCanvas != null ? worldCanvas.transform.position : transform.position; float spacing = Mathf.Max(0.1f, truthAttackCharacterSpacing); - float startX = -spacing * Mathf.Max(0, characterCount - 1) * 0.5f; + ExpressionTextTokenizer.Layout layout = ExpressionTextTokenizer.BuildLayout( + text, + spacing, + whitespaceSpacingMultiplier); + var positions = new List(layout.Count); + Vector3 center = worldCanvas != null ? worldCanvas.transform.position : transform.position; Bounds clip = GetCompletionClipBounds(); - for (int i = 0; i < characterCount; i++) + for (int i = 0; i < layout.Count; i++) { - Vector3 position = center + new Vector3(startX + i * spacing, 0f, 0f); + Vector3 position = center + new Vector3(layout.Offsets[i], 0f, 0f); positions.Add(ClampPositionToBounds(position, clip)); } return positions; } - private void UpdateFinalPresentationPhrase(string text, List positions) + private void UpdateFinalPresentationPhrase( + IReadOnlyList characters, + List positions) { finalTargetCharacters.Clear(); finalTargetPositions.Clear(); - for (int i = 0; i < text.Length; i++) - finalTargetCharacters.Add(text[i].ToString()); + for (int i = 0; i < characters.Count; i++) + finalTargetCharacters.Add(characters[i]); finalTargetPositions.AddRange(positions); } @@ -2579,7 +2604,9 @@ namespace AibisDream.MiniGame.Language if (string.IsNullOrEmpty(fragment) || orderedRedParticles.Count == 0) yield break; - int startIndex = targetSentence.IndexOf(fragment, System.StringComparison.Ordinal); + List targetElements = ExpressionTextTokenizer.GetVisibleElements(targetSentence); + List fragmentElements = ExpressionTextTokenizer.GetVisibleElements(fragment); + int startIndex = ExpressionTextTokenizer.FindVisibleSequence(targetElements, fragmentElements); if (startIndex < 0) { Debug.LogWarning($"[LanguageParticleManager] 真话泄漏片段“{fragment}”不在目标句“{targetSentence}”中。"); @@ -2587,12 +2614,12 @@ namespace AibisDream.MiniGame.Language } var leaked = new List(); - int endExclusive = Mathf.Min(startIndex + fragment.Length, orderedRedParticles.Count); + int endExclusive = Mathf.Min(startIndex + fragmentElements.Count, orderedRedParticles.Count); for (int i = startIndex; i < endExclusive; i++) { CandidateParticle particle = orderedRedParticles[i]; if (particle == null) continue; - particle.ForceSetCharacter(targetSentence[i].ToString()); + particle.ForceSetCharacter(targetElements[i]); particle.isStatic = true; particle.alpha = 1f; leaked.Add(particle); diff --git a/Assets/Scripts/MiniGame/HuoShan/Language/LanguageYarnCommand.cs b/Assets/Scripts/MiniGame/HuoShan/Language/LanguageYarnCommand.cs index c393ecaaf..8487124fb 100644 --- a/Assets/Scripts/MiniGame/HuoShan/Language/LanguageYarnCommand.cs +++ b/Assets/Scripts/MiniGame/HuoShan/Language/LanguageYarnCommand.cs @@ -1,7 +1,11 @@ using System.Collections; using System.Collections.Generic; +using System.Threading.Tasks; using AibisDream.FixSystem; using AibisDream; +using AibisDream.Framework; +using UnityEngine.Localization; +using UnityEngine.Localization.Settings; using Yarn.Unity; namespace AibisDream.MiniGame.Language @@ -13,6 +17,66 @@ namespace AibisDream.MiniGame.Language { private static AibisDream.ExpressionManager ExpressionManager => FixSystemCenter.SystemDic.Get(); + private static Locale GetCommandLocale() + { + Locale current = LocalizationSettings.SelectedLocale; + return ExpressionManager != null + ? ExpressionManager.GetExpressionLocale(current) + : current; + } + + private static IEnumerator ResolveTextReferences( + Locale locale, + System.Action completed, + params string[] references) + { + var tasks = new Task[references.Length]; + for (int i = 0; i < references.Length; i++) + tasks[i] = LocalizationKit.LocalizeParamAsync(references[i], locale); + + Task all = Task.WhenAll(tasks); + while (!all.IsCompleted) + yield return null; + + completed?.Invoke(all.Status == TaskStatus.RanToCompletion + ? all.Result + : System.Array.Empty()); + } + + private static bool ValidateRequiredText(string value, string command, string parameter) + { + if (!string.IsNullOrWhiteSpace(value)) + return true; + + UnityEngine.Debug.LogError( + $"[LanguageYarnCommand] {command} 的玩家可见参数 {parameter} 解析为空;命令已安全结束。"); + return false; + } + + public static bool TryParseExpressionTokens(string value, out List tokens) + { + tokens = new List(); + if (string.IsNullOrWhiteSpace(value)) + return false; + + if (value.Contains("|")) + { + UnityEngine.Debug.LogError( + "[LanguageYarnCommand] 干扰 token 使用了全角分隔符“|”;请改用半角“|”。"); + return false; + } + + string[] parts = value.Split('|'); + foreach (string part in parts) + { + string token = part.Trim(); + if (!string.IsNullOrEmpty(token)) + tokens.Add(token); + } + + return tokens.Count > 0; + } + private static bool TryGetPresentation(out LogReleasePresentationController presentation) { presentation = ExpressionManager != null @@ -30,8 +94,8 @@ namespace AibisDream.MiniGame.Language /// 启动表达粒子系统(在打开视图后调用),等粒子字入场完成即可返回(可交互);panel 动效并行不阻塞。 /// 流程:若有上一轮则先淡出字与连线 → 再淡入火山释放 log → 再启动新一轮并等粒子就绪。 /// 注意:Yarn Spinner 2.x 对同名 重载支持不可靠,只保留一个注册入口,用可选参数区分 3/4 参调用。 - /// <> - /// <> + /// <> + /// <> /// (第四项为「非目标」候选粒子总数,候选池 = 目标句字数 + 该值;不写第四项则用 Inspector 的 Candidate Count) /// /// 干扰短句(笑话等),用 | 分隔,用于非目标粒子字符池,如:"哈哈|嘿嘿|呵呵" @@ -60,21 +124,103 @@ namespace AibisDream.MiniGame.Language yield break; } - string[] phrases = null; - if (!string.IsNullOrEmpty(anxietyPhrasesStr)) + Locale locale = LocalizationSettings.SelectedLocale; + string[] localized = null; + yield return ResolveTextReferences( + locale, + values => localized = values, + anxietyPhrasesStr, + targetSentence); + if (localized == null || localized.Length != 2 || + !ValidateRequiredText(localized[0], "start_expression", nameof(anxietyPhrasesStr)) || + !ValidateRequiredText(localized[1], "start_expression", nameof(targetSentence))) + yield break; + + if (!TryParseExpressionTokens(localized[0], out List phrases)) { - phrases = anxietyPhrasesStr.Split('|'); - for (int i = 0; i < phrases.Length; i++) - { - phrases[i] = phrases[i].Trim(); - } + UnityEngine.Debug.LogError( + "[LanguageYarnCommand] start_expression 没有有效的干扰 token;命令已安全结束。"); + yield break; } // 先清理上一轮演出,再淡出旧文字/连线并恢复 Log 操作界面。 yield return ExpressionManager.PreparePresentationForNextRound(); yield return ExpressionManager.FadeOutParticlesBeforeNewRound(0f); yield return ExpressionManager.EnsureReleaseLogFadedIn(1f); - ExpressionManager.StartSystem(phrases, targetSentence, completionNode, nonTargetParticleTotal); + ExpressionManager.SetActiveExpressionLocale(locale); + ExpressionManager.StartSystem( + phrases, + localized[1], + completionNode, + nonTargetParticleTotal); + yield return ExpressionManager.WaitUntilExpressionFlowReady(); + } + + /// + /// 使用 ExpressionContentCatalog 中的轮次配置启动表达系统。 + /// <> + /// + [YarnCommand("start_expression_round")] + public static IEnumerator StartExpressionRound(string roundId, string completionNode = "") + { + if (ExpressionManager == null) + { + UnityEngine.Debug.LogError("ExpressionManager 未找到!请确保场景中有 ExpressionManager 组件。"); + yield break; + } + + ExpressionContentCatalog catalog = ExpressionManager.ContentCatalog; + if (catalog == null) + { + UnityEngine.Debug.LogError( + "[LanguageYarnCommand] ExpressionContentCatalog 未配置;无法启动表达轮次。"); + yield break; + } + + List catalogErrors = catalog.GetValidationErrors(); + if (catalogErrors.Count > 0) + { + UnityEngine.Debug.LogError( + "[LanguageYarnCommand] ExpressionContentCatalog 数据无效;无法启动表达轮次。\n" + + string.Join("\n", catalogErrors)); + yield break; + } + + if (!catalog.TryGetRound(roundId, out ExpressionRoundDefinition round)) + { + UnityEngine.Debug.LogError( + $"[LanguageYarnCommand] ExpressionContentCatalog 中不存在轮次 '{roundId}'。"); + yield break; + } + + Locale locale = LocalizationSettings.SelectedLocale; + string[] localized = null; + yield return ResolveTextReferences( + locale, + values => localized = values, + round.TokenReference, + round.TargetReference); + if (localized == null || localized.Length != 2 || + !ValidateRequiredText(localized[0], "start_expression_round", nameof(round.TokenReference)) || + !ValidateRequiredText(localized[1], "start_expression_round", nameof(round.TargetReference))) + yield break; + + if (!TryParseExpressionTokens(localized[0], out List phrases)) + { + UnityEngine.Debug.LogError( + $"[LanguageYarnCommand] 表达轮次 '{roundId}' 没有有效的干扰 token。"); + yield break; + } + + yield return ExpressionManager.PreparePresentationForNextRound(); + yield return ExpressionManager.FadeOutParticlesBeforeNewRound(0f); + yield return ExpressionManager.EnsureReleaseLogFadedIn(1f); + ExpressionManager.SetActiveExpressionLocale(locale); + ExpressionManager.StartSystem( + phrases, + localized[1], + completionNode ?? string.Empty, + round.NonTargetParticleCount); yield return ExpressionManager.WaitUntilExpressionFlowReady(); } @@ -241,8 +387,17 @@ namespace AibisDream.MiniGame.Language UnityEngine.Debug.LogError("ExpressionManager 未找到!"); yield break; } + string[] localized = null; + yield return ResolveTextReferences( + GetCommandLocale(), + values => localized = values, + keyword); + if (localized == null || localized.Length != 1 || + !ValidateRequiredText(localized[0], "expression_lie_begin", nameof(keyword))) + yield break; + yield return ExpressionManager.StartCoroutine( - ExpressionManager.BeginExpressionLie(keyword, preset)); + ExpressionManager.BeginExpressionLie(localized[0], preset)); } [YarnCommand("expression_lie_break")] @@ -385,24 +540,37 @@ namespace AibisDream.MiniGame.Language /// /// 结尾真假快速闪切(非阻塞,节奏由同长度 wait 控制):真心话与谎话交替独占屏幕, /// 两者的屏幕底色(电视头背景)不同,越到后面切得越快,结束定格在真话帧。 - /// <> + /// <> /// [YarnCommand("expression_truth_lie_flicker")] - public static void ExpressionTruthLieFlicker( + public static IEnumerator ExpressionTruthLieFlicker( string liePhrases, string truthPhrase, float duration, float interval = 0.24f, float fontSize = 12f) { - if (TryGetPresentation(out LogReleasePresentationController presentation)) - presentation.StartTruthLieFlicker(liePhrases, truthPhrase, duration, interval, fontSize); + if (!TryGetPresentation(out LogReleasePresentationController presentation)) + yield break; + + string[] localized = null; + yield return ResolveTextReferences( + GetCommandLocale(), + values => localized = values, + liePhrases, + truthPhrase); + if (localized == null || localized.Length != 2 || + !ValidateRequiredText(localized[0], "expression_truth_lie_flicker", nameof(liePhrases)) || + !ValidateRequiredText(localized[1], "expression_truth_lie_flicker", nameof(truthPhrase))) + yield break; + + presentation.StartTruthLieFlicker(localized[0], localized[1], duration, interval, fontSize); } /// /// 定格的谎话被 glitch 翻转成真话(阻塞):大字乱跳中长度从谎话渐变到真话、真假底色抢闪, /// 最后落在暖色真话。紧接 expression_memory_cut + expression_truth_snap 收尾。 - /// <> + /// <> /// [YarnCommand("expression_lie_morph_truth")] public static IEnumerator ExpressionLieMorphTruth( @@ -412,8 +580,19 @@ namespace AibisDream.MiniGame.Language { if (!TryGetPresentation(out LogReleasePresentationController presentation)) yield break; + string[] localized = null; + yield return ResolveTextReferences( + GetCommandLocale(), + values => localized = values, + lieText, + truthText); + if (localized == null || localized.Length != 2 || + !ValidateRequiredText(localized[0], "expression_lie_morph_truth", nameof(lieText)) || + !ValidateRequiredText(localized[1], "expression_lie_morph_truth", nameof(truthText))) + yield break; + yield return ExpressionManager.StartCoroutine( - presentation.MorphLieToTruth(lieText, truthText, duration)); + presentation.MorphLieToTruth(localized[0], localized[1], duration)); } [YarnCommand("expression_truth_leak")] @@ -421,24 +600,44 @@ namespace AibisDream.MiniGame.Language { if (!TryGetPresentation(out LogReleasePresentationController presentation)) yield break; + string[] localized = null; + yield return ResolveTextReferences( + GetCommandLocale(), + values => localized = values, + fragment); + if (localized == null || localized.Length != 1 || + !ValidateRequiredText(localized[0], "expression_truth_leak", nameof(fragment))) + yield break; + yield return ExpressionManager.StartCoroutine( - presentation.LeakTruthFragment(fragment, duration)); + presentation.LeakTruthFragment(localized[0], duration)); } [YarnCommand("expression_lie_shuffle")] - public static void ExpressionLieShuffle( + public static IEnumerator ExpressionLieShuffle( string characterPool, float duration, float interval) { - if (TryGetPresentation(out LogReleasePresentationController presentation)) - presentation.StartLieCharacterShuffle(characterPool, duration, interval); + if (!TryGetPresentation(out LogReleasePresentationController presentation)) + yield break; + + string[] localized = null; + yield return ResolveTextReferences( + GetCommandLocale(), + values => localized = values, + characterPool); + if (localized == null || localized.Length != 1 || + !ValidateRequiredText(localized[0], "expression_lie_shuffle", nameof(characterPool))) + yield break; + + presentation.StartLieCharacterShuffle(localized[0], duration, interval); } /// /// 演员大字闪现(阻塞):抖动的粒子字瞬间隐藏,屏幕中央出现白色大字(红青色散双影+急速缩放), /// 停顿 holdDuration 秒后始终恢复粒子字抖动;结尾定格请使用 expression_actor_flash_in。 - /// <> + /// <> /// [YarnCommand("expression_actor_flash")] public static IEnumerator ExpressionActorFlash( @@ -454,9 +653,19 @@ namespace AibisDream.MiniGame.Language { if (!TryGetPresentation(out LogReleasePresentationController presentation)) yield break; + + string[] localized = null; + yield return ResolveTextReferences( + GetCommandLocale(), + values => localized = values, + text); + if (localized == null || localized.Length != 1 || + !ValidateRequiredText(localized[0], "expression_actor_flash", nameof(text))) + yield break; + yield return ExpressionManager.StartCoroutine( presentation.FlashActorLie( - text, + localized[0], holdDuration, fontSize, settledScale, @@ -470,7 +679,7 @@ namespace AibisDream.MiniGame.Language /// /// 演员大字入场并保持(阻塞):入场表现与 expression_actor_flash 相同,但不执行退场, /// 适合在段落结尾定格。后续清场命令仍会正常移除它。 - /// <> + /// <> /// [YarnCommand("expression_actor_flash_in")] public static IEnumerator ExpressionActorFlashIn( @@ -486,9 +695,19 @@ namespace AibisDream.MiniGame.Language { if (!TryGetPresentation(out LogReleasePresentationController presentation)) yield break; + + string[] localized = null; + yield return ResolveTextReferences( + GetCommandLocale(), + values => localized = values, + text); + if (localized == null || localized.Length != 1 || + !ValidateRequiredText(localized[0], "expression_actor_flash_in", nameof(text))) + yield break; + yield return ExpressionManager.StartCoroutine( presentation.FlashActorLie( - text, + localized[0], holdDuration, fontSize, settledScale, @@ -505,10 +724,36 @@ namespace AibisDream.MiniGame.Language /// <> /// [YarnCommand("expression_actor_flash_loop_start")] - public static void ExpressionActorFlashLoopStart() + public static IEnumerator ExpressionActorFlashLoopStart() { - if (TryGetPresentation(out LogReleasePresentationController presentation)) - presentation.StartActorFlashLoop(); + if (!TryGetPresentation(out LogReleasePresentationController presentation)) + yield break; + + ExpressionContentCatalog catalog = ExpressionManager.ContentCatalog; + if (catalog == null || !LocalizationKit.IsLocalizedParam(catalog.ActorLoopReference)) + { + UnityEngine.Debug.LogError( + "[LanguageYarnCommand] Actor Loop 本地化引用未配置;命令已安全结束。"); + yield break; + } + + string[] localized = null; + yield return ResolveTextReferences( + GetCommandLocale(), + values => localized = values, + catalog.ActorLoopReference); + if (localized == null || localized.Length != 1 || + !ValidateRequiredText(localized[0], "expression_actor_flash_loop_start", "ActorLoopReference")) + yield break; + + if (!TryParseExpressionTokens(localized[0], out List phrases)) + { + UnityEngine.Debug.LogError( + "[LanguageYarnCommand] Actor Loop 短句列表无效;命令已安全结束。"); + yield break; + } + + presentation.StartActorFlashLoop(phrases); } /// @@ -525,7 +770,7 @@ namespace AibisDream.MiniGame.Language /// /// 真话闪现(阻塞):谎话大字之间,暖色完整真话带细微抖动挣扎浮现一拍; /// "被掐断"由紧随其后的 expression_glitch_pulse 表现。不推进谎话冲击等级。 - /// <> + /// <> /// [YarnCommand("expression_actor_truth_flash")] public static IEnumerator ExpressionActorTruthFlash( @@ -537,25 +782,52 @@ namespace AibisDream.MiniGame.Language { if (!TryGetPresentation(out LogReleasePresentationController presentation)) yield break; + + string[] localized = null; + yield return ResolveTextReferences( + GetCommandLocale(), + values => localized = values, + text); + if (localized == null || localized.Length != 1 || + !ValidateRequiredText(localized[0], "expression_actor_truth_flash", nameof(text))) + yield break; + yield return ExpressionManager.StartCoroutine( - presentation.FlashActorTruth(text, holdDuration, fontSize, alpha, jitter)); + presentation.FlashActorTruth(localized[0], holdDuration, fontSize, alpha, jitter)); } /// /// 老虎机换字(非阻塞,节拍由 Yarn wait 控制):聚焦字在 duration 内只从 charPool 里疯狂换字。 /// truthPool 非空时其字符也混入换字池,并以真话暖色显示。 - /// <> + /// <> /// [YarnCommand("expression_actor_slot")] - public static void ExpressionActorSlot( + public static IEnumerator ExpressionActorSlot( string charPool, float duration, float interval = 0.05f, float fontSize = 14f, string truthPool = "") { - if (TryGetPresentation(out LogReleasePresentationController presentation)) - presentation.StartActorSlotMachine(charPool, duration, interval, fontSize, truthPool); + if (!TryGetPresentation(out LogReleasePresentationController presentation)) + yield break; + + string[] localized = null; + yield return ResolveTextReferences( + GetCommandLocale(), + values => localized = values, + charPool, + truthPool); + if (localized == null || localized.Length != 2 || + !ValidateRequiredText(localized[0], "expression_actor_slot", nameof(charPool))) + yield break; + + presentation.StartActorSlotMachine( + localized[0], + duration, + interval, + fontSize, + localized[1] ?? string.Empty); } /// @@ -583,7 +855,7 @@ namespace AibisDream.MiniGame.Language /// /// 单字/短词满屏重击,并等待该拍结束。 - /// <> + /// <> /// [YarnCommand("expression_actor_word_hit")] public static IEnumerator ExpressionActorWordHit( @@ -594,13 +866,23 @@ namespace AibisDream.MiniGame.Language { if (!TryGetPresentation(out LogReleasePresentationController presentation)) yield break; + + string[] localized = null; + yield return ResolveTextReferences( + GetCommandLocale(), + values => localized = values, + text); + if (localized == null || localized.Length != 1 || + !ValidateRequiredText(localized[0], "expression_actor_word_hit", nameof(text))) + yield break; + yield return ExpressionManager.StartCoroutine( - presentation.PlayActorWordHit(text, holdDuration, fontSize, punchScale)); + presentation.PlayActorWordHit(localized[0], holdDuration, fontSize, punchScale)); } /// /// 单列词组严格裁在表情屏内,像字幕一样无缝向下滚动。 - /// <> + /// <> /// [YarnCommand("expression_actor_scroll")] public static IEnumerator ExpressionActorScroll( @@ -612,9 +894,19 @@ namespace AibisDream.MiniGame.Language { if (!TryGetPresentation(out LogReleasePresentationController presentation)) yield break; + + string[] localized = null; + yield return ResolveTextReferences( + GetCommandLocale(), + values => localized = values, + phraseList); + if (localized == null || localized.Length != 1 || + !ValidateRequiredText(localized[0], "expression_actor_scroll", nameof(phraseList))) + yield break; + yield return ExpressionManager.StartCoroutine( presentation.ScrollActorLies( - phraseList, + localized[0], scrollDuration, loopDuration, holdDuration, @@ -742,7 +1034,7 @@ namespace AibisDream.MiniGame.Language /// /// 用现有目标粒子完整显示一个红色攻击词,并等待确定性的冲击动作结束。 /// 超过目标粒子数量时会安全跳过,不会截断。 - /// <> + /// <> /// [YarnCommand("expression_truth_attack")] public static IEnumerator ExpressionTruthAttack( @@ -753,17 +1045,26 @@ namespace AibisDream.MiniGame.Language if (!TryGetPresentation(out LogReleasePresentationController presentation)) yield break; + string[] localized = null; + yield return ResolveTextReferences( + GetCommandLocale(), + values => localized = values, + text); + if (localized == null || localized.Length != 1 || + !ValidateRequiredText(localized[0], "expression_truth_attack", nameof(text))) + yield break; + // 红屏武装时每个攻击词都把屏幕顶一下(punch),力度跟着这个词的 impact 走。 // 写在这里而不是 Yarn 里逐条加 pulse:攻击列表经常重调,两边手写必然脱节。 ExpressionManager.PunchScreenForAttack(impact); yield return ExpressionManager.StartCoroutine( - presentation.PlayTruthAttack(text, duration, impact)); + presentation.PlayTruthAttack(localized[0], duration, impact)); } /// /// Keep an already resolved truth phrase in place while it gradually becomes unstable and red. - /// <> + /// <> /// [YarnCommand("expression_truth_destabilize")] public static IEnumerator ExpressionTruthDestabilize( @@ -775,9 +1076,19 @@ namespace AibisDream.MiniGame.Language { if (!TryGetPresentation(out LogReleasePresentationController presentation)) yield break; + + string[] localized = null; + yield return ResolveTextReferences( + GetCommandLocale(), + values => localized = values, + text); + if (localized == null || localized.Length != 1 || + !ValidateRequiredText(localized[0], "expression_truth_destabilize", nameof(text))) + yield break; + yield return ExpressionManager.StartCoroutine( presentation.DestabilizeTruthPhrase( - text, + localized[0], duration, shakeAmplitude, shakeSpeed, @@ -786,7 +1097,7 @@ namespace AibisDream.MiniGame.Language /// /// 将当前攻击词重组为固定青绿色真话,并等待稳定完成。 - /// <> + /// <> /// [YarnCommand("expression_truth_settle")] public static IEnumerator ExpressionTruthSettle( @@ -795,8 +1106,18 @@ namespace AibisDream.MiniGame.Language { if (!TryGetPresentation(out LogReleasePresentationController presentation)) yield break; + + string[] localized = null; + yield return ResolveTextReferences( + GetCommandLocale(), + values => localized = values, + text); + if (localized == null || localized.Length != 1 || + !ValidateRequiredText(localized[0], "expression_truth_settle", nameof(text))) + yield break; + yield return ExpressionManager.StartCoroutine( - presentation.SettleTruthPhrase(text, duration)); + presentation.SettleTruthPhrase(localized[0], duration)); } [YarnCommand("expression_truth_blackout")] diff --git a/Assets/Scripts/MiniGame/HuoShan/Language/LogReleasePresentationController.cs b/Assets/Scripts/MiniGame/HuoShan/Language/LogReleasePresentationController.cs index 7543401dc..623578141 100644 --- a/Assets/Scripts/MiniGame/HuoShan/Language/LogReleasePresentationController.cs +++ b/Assets/Scripts/MiniGame/HuoShan/Language/LogReleasePresentationController.cs @@ -808,14 +808,19 @@ namespace AibisDream.MiniGame.Language } } - public void StartActorFlashLoop() + public void StartActorFlashLoop(IReadOnlyList phrases) { EnsureRuntimeObjects(); if (!ExpectState(nameof(StartActorFlashLoop), PresentationState.Memory)) return; + if (phrases == null || phrases.Count == 0) + { + Debug.LogError("[LogReleasePresentation] Actor Flash Loop 没有有效短句;已安全跳过。"); + return; + } StopActorFlashLoopImmediate(true); - actorFlashLoopRoutine = StartCoroutine(ActorFlashLoopRoutine()); + actorFlashLoopRoutine = StartCoroutine(ActorFlashLoopRoutine(phrases)); } public void StopActorFlashLoop() @@ -823,27 +828,33 @@ namespace AibisDream.MiniGame.Language StopActorFlashLoopImmediate(true); } - private IEnumerator ActorFlashLoopRoutine() + private IEnumerator ActorFlashLoopRoutine(IReadOnlyList phrases) { while (state == PresentationState.Memory) { - yield return FlashActorLie( - "没问题", 0.25f, 10.5f, 1.1f, 1.45f, 0.008f, 0.06f, 0.50f, 0f); - if (state != PresentationState.Memory) - break; - yield return new WaitForSeconds(0.3f); + for (int i = 0; i < phrases.Count && state == PresentationState.Memory; i++) + { + int style = i % 3; + float hold = style == 2 ? 0.15f : 0.25f; + float fontSize = style == 0 ? 10.5f : style == 1 ? 11.2f : 12f; + float punchScale = style == 0 ? 1.45f : style == 1 ? 1.85f : 2.20f; + float chromaDistance = style == 2 ? 0.010f : 0.008f; + float chromaAlpha = style == 2 ? 0.08f : 0.06f; + float impact = style == 0 ? 0.50f : style == 1 ? 0.72f : 1f; - yield return FlashActorLie( - "冇问题", 0.25f, 11.2f, 1.1f, 1.85f, 0.008f, 0.06f, 0.72f, 0f); - if (state != PresentationState.Memory) - break; - yield return new WaitForSeconds(0.3f); - - yield return FlashActorLie( - "帽问题", 0.15f, 12.0f, 1.1f, 2.20f, 0.010f, 0.08f, 1.00f, 0f); - if (state != PresentationState.Memory) - break; - yield return new WaitForSeconds(0.3f); + yield return FlashActorLie( + phrases[i], + hold, + fontSize, + 1.1f, + punchScale, + chromaDistance, + chromaAlpha, + impact, + 0f); + if (state == PresentationState.Memory) + yield return new WaitForSeconds(0.3f); + } } actorFlashLoopRoutine = null; diff --git a/Assets/StreamingAssets/Config/params.csv b/Assets/StreamingAssets/Config/params.csv deleted file mode 100644 index 2f40c4aa3..000000000 --- a/Assets/StreamingAssets/Config/params.csv +++ /dev/null @@ -1,9 +0,0 @@ -键,中文,英文,日文 -Key,Cn,En,Ja -天空,天空,Sky,そら -海报,海报,Poster,ポスター -植物,植物,Plant,植物 -朋友,朋友,Friend,友達 -情绪,情绪,Emo,情緒 -记忆,记忆,Memory,記憶 -逻辑,逻辑,Logic,論理 diff --git a/Assets/Yarn/FP/FP_Huoshan1/Stage6.yarn b/Assets/Yarn/FP/FP_Huoshan1/Stage6.yarn index 6b875a86d..b73ec5dfd 100644 --- a/Assets/Yarn/FP/FP_Huoshan1/Stage6.yarn +++ b/Assets/Yarn/FP/FP_Huoshan1/Stage6.yarn @@ -71,7 +71,7 @@ me: 这一条看起来……是一个关于焦虑的记录。 #line:046ab93d Task: 第 1 条log:左键排斥、右键吸引,连接目标并分离干扰 #line:07a299c <> <> -<> +<> === @@ -131,8 +131,8 @@ position: 900,2100 // JumpDevTool 专用:从 Stage6就绪存档恢复后,跳过粒子玩法直接预览 LOG1 演出 <> <> -<> -// <> +<> +// <> === @@ -243,7 +243,7 @@ Task: 第 2 条log:左键排斥、右键吸引,连接目标并分离干扰 # <> <> <> -<> +<> === @@ -297,7 +297,7 @@ position: 900,2550 // JumpDevTool 专用:从 Stage6就绪存档恢复后,跳过粒子玩法直接预览 LOG2 演出 <> <> -<> +<> === @@ -399,7 +399,7 @@ Task: 第 3 条log:左键排斥、右键吸引,连接目标并分离干扰 # <> <> <> -<> +<> === @@ -462,7 +462,7 @@ position: 900,3000 // JumpDevTool 专用:从 Stage6就绪存档恢复后,跳过粒子玩法直接预览 LOG3 演出 <> <> -<> +<> === @@ -507,7 +507,7 @@ hs: …… #line:085bda6 <> <> hs: 不要那样看我…… #line:00e7b75 -<> +<> <> === @@ -528,69 +528,69 @@ me: 等会…其他的log好像有异常传入…… #line:0d3cc39 // 节奏不是一句一格。四种拍子交替:**空**(留白)/ **连**(挤在一起吐出来)/ // **顿**(狠话之后的静默)/ **急**(越来越快)。均匀推进会把最狠的几句读平。 // —— 空:起手两记,各自留白 -<> +<> hs: 为什么#auto_next #line:059ef58 <> -<> +<> hs: 不相信我? #line:06810ae <> // —— 连:辩解一口气倒出来,中间不喘 -<> +<> hs: 我明白我垫底, #line:00248ff -<> +<> hs: 可是这周天气真的不行! #line:0ffc958 -<> +<> hs: 其它销售……也是一顶都卖不出去! #line:0d2c25f // —— 顿:辩解说完,发现没人信,卡壳一下 <> -<> +<> hs: 为什么我都这么努力解释了,#auto_next #line:0ebd252 -<> +<> hs: 你还是不愿意相信我? #line:0328a7c -<> +<> hs: 到底它们是你的员工,还是我?#auto_next #line:0a915ef hs: 你对我从来没有一点信任…… #line:0b69892 // —— 顿:最长的一次静默,攒住再爆 <> -<> +<> hs: 我真的受够了! #line:0312421 hs: 总是被你当狗一样训,#auto_next #line:00412cb -<> +<> <> -<> +<> <> -<> +<> <> -<> +<> hs: 路斯,我它码真该死! #line:03678e3 -<> +<> hs: 我真没用! #line:093977a <> -<> +<> hs: 你不知道我有多想证明自己,#auto_next #line:0555cab -<> +<> hs: 可是我什么都做不到…… #line:0cf70a3 <> // —— 急:一句比一句挤,往最狠的那句冲 -<> +<> hs: 我没办法追上你,#auto_next #line:0deff66 -<> +<> hs: 没办法帮到英理,#auto_next #line:0c2e7b8 -<> +<> hs: 甚至连自己被羞辱了都没有勇气#auto_next #line:00ab3a7 -<> +<> hs: 说出来,#auto_next #line:0ad49b4 // —— 顿:最狠那句前面一定要有空 <> -<> +<> hs: 我就是个废物…… #line:079cc40 -<> +<> <> -<> +<> <> -<> +<> <> -<> +<> hs: 我它码就是个烂泥扶不上墙的废物!! #line:0e8a4da // —— 空:吼到底之后整个泄下来,转成气音 <> @@ -602,15 +602,15 @@ hs: 就这么想赢一次…… #line:08aece0 <> // 五连击必须**错开**:原本五条挤在同一帧,五记 punch 会合成一记,读不出连击。 // 间隔越来越短 = 越捶越急。 -<> +<> <> -<> +<> <> -<> +<> <> -<> +<> <> -<> +<> <> <> <> @@ -622,7 +622,7 @@ hs: 就这么想赢一次…… #line:08aece0 <> <> hs: 我真的好想… #line:02fff1d -<> +<> <> <> me: …… #line:04c2999 diff --git a/Docs/HuoShan/HuoshanStage6LocalizationPlan.md b/Docs/HuoShan/HuoshanStage6LocalizationPlan.md index ba0098b19..7d5183d27 100644 --- a/Docs/HuoShan/HuoshanStage6LocalizationPlan.md +++ b/Docs/HuoShan/HuoshanStage6LocalizationPlan.md @@ -1,412 +1,93 @@ -# 火山 Stage6 本地化完整实施计划 +# 火山 Stage6 / ExpressSystem 本地化方案 -## 1. 目标与边界 +## 当前决策 -Stage6 继续使用项目现有的两套本地化入口: +ExpressSystem 的玩家可见玩法文字统一通过 `l10n.*` 引用,并以 Unity +Localization 的 `Params` String Table Collection 作为唯一数据源。 -- 固定 UI:Unity Localization 的现有 `UIText` String Table Collection。 -- Stage6 玩法及演出内容:新增独立的 `HuoshanStage6` String Table Collection。 -- Yarn 普通对白:继续沿用项目 Yarn Spinner 本地化体系,但本轮不启用 `FP_Huoshan1` 的整章对白翻译。 -- `params.csv` 继续只用于 `l10n.*` 通用短语替换,不承载 Stage6 玩法文本。 +- `l10n.xxx` 查询 `Params/xxx`。 +- 查询使用 `DontUseFallback`,不回退中文或其他 Locale。 +- 中文表保存源数据;英文、日文表保留同一套 Key,未翻译值留空。 +- Key 不存在时显示 `⟦key⟧` 并记录包含 Table、Key、Locale 的错误。 +- Express 必填值解析为空时安全终止命令,不启动粒子或释放演出。 +- `StreamingAssets/Config/params.csv` 已退出运行链路。 +- 固定 UI 继续使用 `UIText`;普通 Yarn 对白、Task、教学和角色名不迁入 + `Params`。 -本轮纳入: +## 轮次配置 -- 表达小游戏目标、输出、整合率、干扰数量等固定 UI。 -- 三轮语言粒子的目标句和干扰 token。 -- LOG3 释放演出中的攻击词、稳定词、失稳词等动态文字。 -- 相关程序接口、Yarn 命令协议和使用文档。 -- 中、英、日三种语言数据;英日文首版标记为待母语审校。 +三轮正式内容由 `ExpressionContentCatalog` 管理,场景中的 +`ExpressionManager` 持有 Catalog 引用: -本轮明确暂缓: +| Round ID | Target | Tokens | 非目标粒子数 | +|---|---|---|---:| +| `log1` | `l10n.hs.exp.log1.target` | `l10n.hs.exp.log1.tokens` | 16 | +| `log2` | `l10n.hs.exp.log2.target` | `l10n.hs.exp.log2.tokens` | 28 | +| `log3` | `l10n.hs.exp.log3.target` | `l10n.hs.exp.log3.tokens` | 38 | -- 普通角色对白。 -- Task 面板中的“释放表达模块的阻塞 log”和 LOG1/2/3 任务提示。 -- “左键排斥、右键吸引”等 Yarn 操作教学。 -- Yarn line tag 和 Yarn 本地化 CSV。 -- 调试日志、Inspector Tooltip、对象名、节点名等开发文本。 +Actor Loop 使用 `l10n.hs.exp.actor.loop`。Round ID 大小写敏感;重复 ID、 +未知 ID、空引用、非 `l10n.*` 引用或解析后的空值均视为数据错误。 -因此,本轮完成后核心小游戏及释放演出可以多语言运行,但任务面板与操作教学仍会显示中文,等待之后随对白本地化统一处理。 - -## 2. 本地化数据结构 - -### 2.1 固定 UI:`UIText` - -继续使用现有键,不新建重复条目: - -| Key | 用途 | 中文示例 | 英文草稿 | 日文草稿 | -|---|---|---|---|---| -| `huoshan_expression_objective` | 小游戏目标 | 目标:分离红色干扰,连接蓝色真心 | OBJECTIVE: ISOLATE RED INTERFERENCE. CONNECT THE BLUE TRUTH. | 目標:赤い干渉を分離し、青い本音をつなげる | -| `huoshan_expression_output` | 输出按钮 | 输出 | OUTPUT | 出力 | -| `huoshan_expression_integration` | 整合率 | 语言整合率:{0}% | LANGUAGE INTEGRATION: {0}% | 言語統合率:{0}% | -| `huoshan_expression_interference` | 干扰数量 | 干扰思绪:{0} | INTERFERENCE SIGNALS: {0} | 干渉思考:{0} | - -要求: - -- 保留 `{0}` 参数,程序继续通过格式化参数填入数值。 -- 英文和日文目前的中文占位必须替换。 -- 固定 UI 继续使用现有 `LocalizeStringEvent` 或 `StringDatabase` 绑定。 -- 切换 Locale 时立即刷新这些固定 UI。 - -### 2.2 玩法内容:`HuoshanStage6` - -新增 String Table Collection: - -```text -HuoshanStage6 -├─ zh-Hans -├─ en -└─ ja-JP -``` - -键名只表达语义,不包含语言、行号或 Yarn 节点名。 - -#### 语言粒子数据 - -```text -round.log1.target -round.log1.interference_tokens - -round.log2.target -round.log2.interference_tokens - -round.log3.target -round.log3.interference_tokens -``` - -中文源数据: - -| Key | 中文值 | -|---|---| -| `round.log1.target` | 我就是个笑话 | -| `round.log1.interference_tokens` | 哈哈\|嘿嘿\|呵呵 | -| `round.log2.target` | 不要离开我 | -| `round.log2.interference_tokens` | 哈哈哈\|嘿嘿嘿\|呵呵呵\|笑死了\|好好笑 | -| `round.log3.target` | 不要那样看我 | -| `round.log3.interference_tokens` | 哈哈哈哈\|笑死我了\|太好笑了\|绷不住了\|笑不活了\|真的太搞笑 | - -`interference_tokens` 的表值使用 `|` 分隔。解析规则: - -- 按 `|` 拆分。 -- 对每项执行 Trim。 -- 丢弃空项。 -- 至少保留一个有效 token,否则视为数据错误。 -- token 由本地化人员针对语言重新创作,不要求与中文逐字对应。 -- 英文示例:`HA|HAHA|LOL|JK`。 -- 日文示例:`ハハ|笑|冗談|ウケる`。 - -#### LOG3 释放演出数据 - -按语义建立以下键;重复演出复用同一个键: - -```text -release.attack.why -release.attack.dont_believe_me -release.attack.i_know_im_last -release.attack.bad_weather -release.attack.cannot_sell -release.attack.try_to_explain -release.attack.refuse_to_believe -release.attack.a_little_trust -release.attack.had_enough -release.attack.damn -release.attack.useless -release.attack.prove_myself -release.attack.cannot_do_anything -release.attack.catch_up -release.attack.help_yingli -release.attack.no_courage -release.attack.say_it -release.attack.trash -release.attack.hopeless -release.attack.why_loop - -release.settle.want_to_win_once -release.destabilize.dont_look_at_me -``` - -映射规则: - -| 中文文本 | Key | -|---|---| -| 为什么 | `release.attack.why` | -| 不相信我 | `release.attack.dont_believe_me` | -| 我明白我垫底 | `release.attack.i_know_im_last` | -| 天气真的不行 | `release.attack.bad_weather` | -| 卖不出去 | `release.attack.cannot_sell` | -| 努力解释 | `release.attack.try_to_explain` | -| 不愿意相信我 | `release.attack.refuse_to_believe` | -| 一点信任 | `release.attack.a_little_trust` | -| 受够了 | `release.attack.had_enough` | -| 真该死 | `release.attack.damn` | -| 真没用 | `release.attack.useless` | -| 证明自己 | `release.attack.prove_myself` | -| 什么都做不到 | `release.attack.cannot_do_anything` | -| 追上你 | `release.attack.catch_up` | -| 帮到英理 | `release.attack.help_yingli` | -| 没有勇气 | `release.attack.no_courage` | -| 说出来 | `release.attack.say_it` | -| 废物 | `release.attack.trash` | -| 烂泥扶不上墙 | `release.attack.hopeless` | -| 为什么为什么 | `release.attack.why_loop` | -| 我想赢一次 | `release.settle.want_to_win_once` | -| 不要那样看我 | `release.destabilize.dont_look_at_me` | - -Yarn 中重复出现的“受够了”“废物”“为什么为什么”继续保持原来的重复次数和演出参数,只复用同一条本地化键,不能因去重而改变演出节奏。 - -未来如果 Stage6 使用 `expression_system_message` 或其他会显示字符串的释放命令,也必须新增对应的 `release.*` 键,不能直接写玩家可见文本。 - -### 2.3 表格元数据 - -英日文第一版可以直接投入开发测试,但每个非中文条目增加统一备注: - -```text -DRAFT / NEEDS NATIVE REVIEW -Context: HuoShan Stage6 expression minigame -``` - -释放演出条目应额外注明: - -- 显示形式,例如瞬间闪现、连续攻击或最终稳定文字。 -- 建议长度。 -- 是否允许创译。 -- 重复次数由 Yarn 控制,本地化值本身不要手工重复。 - -## 3. 程序接口和数据流 - -### 3.1 本地化引用协议 - -Yarn 命令参数支持两种输入: - -```text -@h6. 从 HuoshanStage6 String Table 解析 -普通字符串 按原始字符串使用,保持旧内容兼容 -``` - -示例: +正式 Yarn 使用: ```yarn -<> - -<> -<> -<> +<> ``` -其中: +旧 `start_expression` 继续兼容原始字符串,也允许将前两个参数写为 +`l10n.*`。`completionNode`、`memoryKey`、`preset`、颜色、Timeline 名称及 +数值参数均为结构参数,不做本地化解析。 -- 前两个 `start_expression` 参数是本地化内容。 -- 完成节点 `LOG1梳理完成` 是 Yarn 流程标识,不做本地化。 -- 粒子数量、持续时间、颜色、Timeline、memory preset 等均为结构参数,不做本地化。 +## Locale 生命周期 -### 3.2 建议公共数据结构 +启动轮次时捕获当前 Locale,目标句和 token 并行解析;校验通过后才初始化 +粒子系统。该 Locale 快照会保留到下一轮开始或 ExpressSystem 关闭,当前轮 +后续释放演出都使用同一快照。没有活动轮次时,演出命令使用命令调用时的 +当前 Locale。 -```csharp -public readonly struct Stage6TextReference -{ - public string RawValue { get; } - public bool IsLocalized { get; } - public string EntryKey { get; } -} +这可避免切换语言时旧异步请求覆盖新一轮内容。`PunchTapeItem` 也通过请求 +版本号避免旧名称刷新覆盖新 Locale。 -public sealed class Stage6RoundText -{ - public string TargetText { get; init; } - public IReadOnlyList InterferenceTokens { get; init; } -} +## Key 约定 -public sealed class Stage6LocalizationContext -{ - public const string TableName = "HuoshanStage6"; - public const string ReferencePrefix = "@h6."; +Express 使用短前缀 `hs.exp.*`: - public Locale RoundLocale { get; private set; } +- 三轮目标和 token:`hs.exp.log{1..3}.target`、`.tokens` +- Actor Loop:`hs.exp.actor.loop` +- LOG3 攻击词:`hs.exp.atk.*` +- 最终稳定文字:`hs.exp.final.win` - public IEnumerator BeginRound(Locale locale); +重复演出复用同一 Key,但 Yarn 中仍保留原有调用次数、等待、数值、顺序和 +`#line:` 标签。 - public IEnumerator Resolve( - string textReference, - Action completed); +## token 与 Unicode - public IEnumerator Resolve( - string textReference, - Locale locale, - Action completed); +干扰 token 只接受半角 `|`: - public IEnumerator LoadRound( - string roundId, - Locale locale, - Action completed); -} -``` +- 按 `|` 拆分; +- Trim 并删除空项; +- 全空时中止轮次; +- 全角 `|` 是数据错误。 -职责划分: +正式目标句和释放演出文字按 `StringInfo` 的 Unicode 文本元素处理。汉字、 +假名、拉丁字母、组合字符、代理字符和标点均保持完整;空白不生成粒子,但 +最终排列保留词间距。目标粒子数量、最终排列、攻击词长度校验和 truth leak +匹配使用同一套文本元素结果。 -- `Stage6TextReference`:判断参数是本地化引用还是兼容用原始字符串。 -- `Stage6LocalizationContext`:统一访问 `HuoshanStage6`,避免每个 Yarn 命令各写一套解析逻辑。 -- `Stage6RoundText`:向粒子系统提供已经解析完成的数据;粒子系统不直接访问 String Table。 -- `LanguageYarnCommand`:在启动小游戏前等待本地化解析完成,再把纯文本数据交给 `LanguageParticleManager`。 -- Log 释放相关 Yarn Command:显示前通过同一 resolver 解析字符串,再交给 `LogReleasePresentationController`。 -- `LanguageParticleManager` 和 `LogReleasePresentationController` 最终只接收已解析的文本,不关心键、表名或 Locale。 +本次明确不修改 `TextParticle` 的随机字符池、`chineseChars`、 +`chineseWords`、目标粒子入场阶段的随机字符来源,以及背景/干扰粒子的字符 +池策略。 -### 3.3 解析和错误处理 +## 内容校验 -解析规则必须集中实现: +编辑器校验覆盖: -1. 参数不以 `@h6.` 开头时,直接返回原字符串。 -2. 参数以 `@h6.` 开头时,取后半部分作为 Entry Key。 -3. Entry Key 为空时记录错误,并显示可识别的错误占位。 -4. 优先获取指定 Locale 的条目。 -5. 当前语言缺失时回退到 `zh-Hans` 的同名条目。 -6. 中文源条目也缺失时: - - 输出明确错误日志,包含 Table、Key、Locale 和调用命令。 - - 玩家画面显示 `⟦缺失的键名⟧`。 - - 不得直接显示 `@h6.xxx`,也不能静默返回空字符串。 -7. `interference_tokens` 解析后为空时终止该次 `start_expression`,输出数据错误,避免粒子生成阶段产生难以定位的异常。 +- Stage6 正式 Express 文字参数不得残留中文硬编码; +- Yarn 和 Catalog 中引用的 `l10n.*` Key 必须存在于 `Params` Shared Data; +- 中文值必须非空; +- 英文、日文空值只报告待翻译,不导致校验失败; +- `.tokens` 至少包含一个有效项,且不能使用全角 `|`; +- completion node、preset、memory key、颜色、Timeline 等结构参数不误报。 -### 3.4 Locale 切换行为 - -固定 UI: - -- `objective`、`output`、`integration`、`interference` 监听 `SelectedLocaleChanged`。 -- 切换语言后立即刷新。 -- 当前数值必须保留,只替换格式模板。 - -语言粒子: - -- 每次 `start_expression` 开始时捕获当前 Locale。 -- 同一轮的目标句、目标粒子和干扰 token 全部使用该快照。 -- 轮次进行中切换 Locale,不重建、不替换现有粒子。 -- 下一次 `start_expression` 使用新的 Locale。 -- 异步解析结束前不得开始生成粒子。 - -LOG 释放命令: - -- 每条命令执行时解析其文字参数。 -- 如果该命令属于仍在进行的表达轮次,沿用该轮的 Locale 快照。 -- 没有活动轮次时使用命令执行时的当前 Locale。 -- 单次命令解析完成后再启动对应演出,避免先显示 key、后替换文字。 - -### 3.5 Unicode 与粒子拆分 - -目标句不能按 UTF-16 `char` 或 `string.Length` 拆分,必须使用 Unicode 文本元素/字素簇。 - -规则: - -- 中文汉字、日文假名、拉丁字母、组合音标均按完整文本元素处理。 -- 英文空格不生成粒子,但在成功连接后的排版中形成单词间距。 -- 标点是否生成粒子沿用当前玩法规则,但不能被拆成代理项。 -- 有效目标粒子数量、完成判断和候选粒子数量都基于同一套文本元素结果。 -- 干扰 token 是本地化短词;生成粒子时再按同样的文本元素规则取字符。 -- 不使用英文 `string.Length` 直接决定粒子数量。 - -## 4. 具体改动安排 - -### 4.1 Unity Localization 资源 - -- 补齐 `UIText` 中四个现有火山 UI 键的英日文。 -- 新建 `HuoshanStage6` Collection 及中、英、日三张表。 -- 将 Collection 按项目现有 Localization Addressables 方式纳入构建。 -- 配置英日文字体或 TMP fallback,覆盖拉丁字符、日文假名、常用标点。 - -### 4.2 语言粒子系统 - -- `LanguageYarnCommand` 接入统一的 `@h6.` resolver。 -- `start_expression` 在调用 `LanguageParticleManager` 前解析目标句和 token。 -- `LanguageParticleManager` 接收 `Stage6RoundText` 或等价的纯数据参数。 -- 保留现有原始字符串调用方式,避免其他测试场景和旧 Yarn 立即失效。 -- 将固定 UI 的中文常量仅作为开发兜底,不作为正式语言数据来源。 - -### 4.3 LOG 释放演出 - -- 所有接受玩家可见字符串的释放命令接入相同 resolver。 -- 将 Stage6 中的攻击、settle、destabilize 参数改为 `@h6.` 引用。 -- 不修改数值演出参数、调用顺序和重复次数。 -- `LogReleasePresentationController` 保持表现层职责,只接收最终显示文本。 - -### 4.4 Yarn 内容 - -- 仅替换本地化范围内的 Yarn 命令参数。 -- 不添加或修改 `#line:` 标签。 -- 不配置 `FP_Huoshan1.yarnproject` 的整章 localization。 -- 不生成 Stage6 Yarn 翻译 CSV。 -- 被注释的示例命令不需要建立正式本地化条目;若未来启用,必须先补键。 -- Task 行、鼠标教学和普通对白保持原样。 - -### 4.5 Prefab、图片与文档 - -- `ExpressManager.prefab` 当前没有确认的运行时玩家文本,也不是 Stage6 正式文本数据源,本轮不向其中写多语言文案。 -- `ExpressionScreen_NoText.png` 已确认无烘焙文字,不制作多语言 Sprite。 -- 场景或 Prefab 中诸如“输出”的序列化 TMP 文本只作为编辑器预览;运行时必须由 `UIText` 覆盖。 -- 更新 `LogReleaseYarnCommands.md`: - - 说明 `@h6.` 引用格式。 - - 列出哪些参数可本地化、哪些是结构参数。 - - 提供 attack、settle、destabilize 示例。 - - 记录回退与错误显示规则。 -- 同步更新语言系统说明文档中的 `start_expression` 示例,展示本地化键和旧原始字符串两种写法。 - -## 5. 测试与验收 - -### 5.1 数据完整性测试 - -- `HuoshanStage6` 的中、英、日表拥有相同键集合。 -- 中文源条目不得为空。 -- 英日文不得残留中文占位;允许通过备注标记待审校。 -- 所有 `interference_tokens` 至少包含一个有效 token。 -- 所有 Stage6 中实际执行的本地化命令参数都能解析。 -- 结构参数没有被误当成本地化键。 -- 扫描 Stage6 后,剩余硬编码玩家文本必须全部属于已声明的暂缓范围。 - -### 5.2 编辑模式测试 - -- `@h6.key` 能解析指定 Locale。 -- 普通字符串保持原值。 -- 缺少目标语言时回退中文。 -- 三种语言都缺失时显示 `⟦key⟧` 并记录错误。 -- token 能正确 Trim 和过滤空项。 -- Unicode 文本元素拆分能处理中文、英文、日文和组合字符。 -- 英文空格不生成粒子,但结果排版保留词间距。 -- 重复攻击词引用同一 Key 时仍能按 Yarn 调用次数重复播放。 - -### 5.3 PlayMode/场景验收 - -分别使用 `zh-Hans`、`en`、`ja-JP` 运行 LOG1、LOG2、LOG3: - -- 固定 UI 显示正确,格式参数正确。 -- 每轮目标句和干扰 token 使用对应语言。 -- 粒子连接、排斥、吸引和完成判断不受文本长度影响。 -- LOG3 attack、settle、destabilize 全部显示对应语言。 -- 演出调用顺序、节奏、颜色和持续时间与改造前一致。 -- 中途切换语言时,固定 UI 立即更新,当前轮粒子不变,下一轮使用新语言。 -- 字体无缺字方框。 -- 英文长文本和日文文本没有溢出、异常换行或关键画面遮挡。 - -### 5.4 回归验收 - -- 使用旧的原始字符串调用 `start_expression` 仍然可以运行。 -- `languageTest` 等测试内容不因新增 resolver 失效。 -- Yarn 编译无错误。 -- Stage6 的完成节点跳转保持不变。 -- Task 完成逻辑和 Yarn line ID 不受影响。 -- Addressables/Localization 构建包含新增表格。 -- 在 Unity Editor 中完成最终视觉检查;当前无法通过 Unity MCP 自动进行场景级检查。 - -## 6. 交付判定 - -满足以下条件后视为本轮完成: - -- Stage6 固定小游戏 UI 已具备中英日数据。 -- 三轮粒子内容和全部正式 LOG 释放文本均从 `HuoshanStage6` 获取。 -- Yarn 中相关硬编码文本已替换为 `@h6.` 引用。 -- 原始字符串调用保持兼容。 -- Locale 快照、回退、缺键和 Unicode 拆分规则均有测试覆盖。 -- 文档足以让后续策划直接新增本地化键并在 Yarn 命令中使用。 -- 任务提示、操作教学和普通对白仍明确列为后续 Yarn 本地化工作,不被误报为已完成。 +运行时和 EditMode 测试同时覆盖原始字符串、空值不回退、缺 Key 占位、 +显式 Locale、轮次配置、token 解析和 Unicode 文本元素行为。