feat: 火山玩法本地化
仍有硬编码残留,待处理
This commit is contained in:
@@ -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}
|
||||
|
||||
@@ -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:
|
||||
|
||||
+31
@@ -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}
|
||||
+3
-2
@@ -1,7 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e721272cd05718a4d8872d0f3e3f5b8c
|
||||
DefaultImporter:
|
||||
guid: 089fc86f714d6a94bafea3b117fbd639
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+31
@@ -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}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1c592abe924a856439461f3785ec53da
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+31
@@ -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}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 26fd03652560e7f4abec092229fc38f2
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+45
@@ -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
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b430e9b782d3119489b903dce4915542
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+16
@@ -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
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 70748601b27158b47810538f6ed1c013
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+45
@@ -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
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b251ae14d0b273843b5674d239daa31c
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+16
@@ -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
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 808fc72235ececa47aa55960a94a9a9a
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+45
@@ -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
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3ddf5f47a1b265049825b193e7e93fbd
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+16
@@ -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
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3e5e5c2400a39814994a9fcb8eaf49a6
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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<string> chineseTask = LocalizationKit.LocalizeParamAsync(
|
||||
"l10n.hs.exp.log1.target",
|
||||
chinese);
|
||||
Task<string> englishTask = LocalizationKit.LocalizeParamAsync(
|
||||
"l10n.hs.exp.log1.target",
|
||||
english);
|
||||
|
||||
while (!chineseTask.IsCompleted || !englishTask.IsCompleted)
|
||||
yield return null;
|
||||
|
||||
LocalizationSettings.SelectedLocale = previous;
|
||||
Assert.That(chineseTask.Result, Is.EqualTo("我就是个笑话"));
|
||||
Assert.That(englishTask.Result, Is.Empty);
|
||||
}
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator LocalizeParamAsync_ReturnsRawTextAndMissingKeyMarker()
|
||||
{
|
||||
Task<string> rawTask = LocalizationKit.LocalizeParamAsync("raw text");
|
||||
while (!rawTask.IsCompleted)
|
||||
yield return null;
|
||||
Assert.That(rawTask.Result, Is.EqualTo("raw text"));
|
||||
|
||||
Locale chinese = LocalizationEditorSettings.GetLocale(
|
||||
new LocaleIdentifier("zh-Hans"));
|
||||
LogAssert.Expect(
|
||||
LogType.Error,
|
||||
new Regex(@"Params 缺少条目.*Key=hs\.exp\.missing.*Locale=zh-Hans"));
|
||||
Task<string> missingTask = LocalizationKit.LocalizeParamAsync(
|
||||
"l10n.hs.exp.missing",
|
||||
chinese);
|
||||
while (!missingTask.IsCompleted)
|
||||
yield return null;
|
||||
Assert.That(missingTask.Result, Is.EqualTo("⟦hs.exp.missing⟧"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void LegacyCommonParams_ArePresentInUnityParams()
|
||||
{
|
||||
StringTableCollection collection =
|
||||
LocalizationEditorSettings.GetStringTableCollection(ConstRef.ParamsTable);
|
||||
Assert.That(collection, Is.Not.Null);
|
||||
|
||||
string[] legacyKeys = { "天空", "海报", "植物", "朋友", "情绪", "记忆", "逻辑" };
|
||||
foreach (string key in legacyKeys)
|
||||
Assert.That(collection.SharedData.GetEntry(key), Is.Not.Null, key);
|
||||
|
||||
Assert.That(AssetDatabase.LoadAssetAtPath<TextAsset>(
|
||||
"Assets/StreamingAssets/Config/params.csv"), Is.Null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ExpressionCatalog_IsValidAndCaseSensitive()
|
||||
{
|
||||
ExpressionContentCatalog catalog =
|
||||
AssetDatabase.LoadAssetAtPath<ExpressionContentCatalog>(CatalogPath);
|
||||
Assert.That(catalog, Is.Not.Null);
|
||||
Assert.That(catalog.GetValidationErrors(), Is.Empty);
|
||||
Assert.That(catalog.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<ExpressionContentCatalog>();
|
||||
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<ExpressionRoundDefinition> { 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c12e0d2c486f4ea6a5d4f70878b6e7c1
|
||||
guid: d6b8797332ac4876b66e381ea3c98723
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
@@ -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; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 校验 Stage6 Express 命令、轮次 Catalog 与 Params String Table 的引用关系。
|
||||
/// 英日空值是待翻译警告,不阻断内容校验。
|
||||
/// </summary>
|
||||
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*<<(?<command>[A-Za-z0-9_]+)(?<arguments>.*?)>>", RegexOptions.Compiled);
|
||||
|
||||
private static readonly Regex QuotedArgumentRegex =
|
||||
new("\"(?<value>(?:\\\\.|[^\"])*)\"", RegexOptions.Compiled);
|
||||
|
||||
private static readonly Regex L10NRegex =
|
||||
new(@"l10n\.(?<key>[A-Za-z0-9._-]+)", RegexOptions.Compiled);
|
||||
|
||||
private static readonly Regex ChineseRegex =
|
||||
new(@"[\u3400-\u9FFF]", RegexOptions.Compiled);
|
||||
|
||||
private static readonly Dictionary<string, int[]> 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<ExpressionLocalizationIssue> 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<ExpressionLocalizationIssue> Validate()
|
||||
{
|
||||
var issues = new List<ExpressionLocalizationIssue>();
|
||||
var referencedKeys = new HashSet<string>(StringComparer.Ordinal);
|
||||
|
||||
ValidateStage6(issues, referencedKeys);
|
||||
ValidateCatalog(issues, referencedKeys);
|
||||
ValidateParams(issues, referencedKeys);
|
||||
return issues;
|
||||
}
|
||||
|
||||
private static void ValidateStage6(
|
||||
List<ExpressionLocalizationIssue> issues,
|
||||
HashSet<string> 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<Match>()
|
||||
.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<ExpressionLocalizationIssue> issues,
|
||||
HashSet<string> referencedKeys)
|
||||
{
|
||||
ExpressionContentCatalog catalog =
|
||||
AssetDatabase.LoadAssetAtPath<ExpressionContentCatalog>(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<ExpressionLocalizationIssue> issues,
|
||||
HashSet<string> 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<ExpressionLocalizationIssue> 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<ExpressionLocalizationIssue> 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<string> referencedKeys)
|
||||
{
|
||||
if (LocalizationKit.IsLocalizedParam(reference))
|
||||
referencedKeys.Add(LocalizationKit.GetL10NParamKey(reference));
|
||||
}
|
||||
|
||||
private static void AddError(
|
||||
List<ExpressionLocalizationIssue> issues,
|
||||
string message)
|
||||
{
|
||||
issues.Add(new ExpressionLocalizationIssue(
|
||||
ExpressionLocalizationIssueSeverity.Error,
|
||||
message));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5c602299e38f4bf4899abfbcbed84d77
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b2d94952a18d4d178421691ab103f0fd
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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: []
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 728457f9c2ef69c489530047d495eca5
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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: []
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 63300a11309a7b34bbf7a345b123cab8
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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: []
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c69caec2066296840887d7e0c2eff52e
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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:
|
||||
|
||||
@@ -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: []
|
||||
|
||||
@@ -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: []
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1edf9254b97981643bdecd6d22ed1150
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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: []
|
||||
|
||||
@@ -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: []
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6ad3c9298dbc3b642b1251ffc306eaa6
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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: []
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b91f4281442d2b54f92fbecb5bd56426
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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: []
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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: []
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 380c27123212d66468aa1f3ed3bf72ea
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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:
|
||||
|
||||
@@ -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: []
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7d496f91e240bb143b62a6239c817d09
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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: []
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f679069e455200841a92c27186383940
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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"
|
||||
|
||||
@@ -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 + "/记忆/打孔带目录";
|
||||
}
|
||||
|
||||
@@ -52,6 +52,7 @@ namespace AibisDream.Utility
|
||||
|
||||
public const string UITextTable = "UIText";
|
||||
public const string ChapterInfoTable = "ChapterInfo";
|
||||
public const string ParamsTable = "Params";
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -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<string>[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 的文本转义处理。
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -13,10 +13,8 @@ namespace AibisDream.Kit
|
||||
public class ConfigUtil : SingletonBase<ConfigUtil>
|
||||
{
|
||||
private const string CharacterConfigPath = "/Config/character.csv";
|
||||
private const string ParamConfigPath = "/Config/params.csv";
|
||||
|
||||
private Dictionary<string, Character> _characters;
|
||||
private Dictionary<string, Param> _params;
|
||||
|
||||
/// <summary>
|
||||
/// 用于单例模式
|
||||
@@ -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<Param>(Application.streamingAssetsPath + ParamConfigPath);
|
||||
_params = paramList
|
||||
.Where(item => !string.IsNullOrWhiteSpace(item.Key))
|
||||
.ToDictionary(item => item.Key, item => item);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 按key获取角色预设
|
||||
/// </summary>
|
||||
@@ -72,17 +60,6 @@ namespace AibisDream.Kit
|
||||
return _characters.TryGetValue(key, out character);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 按key获取参数本地化配置
|
||||
/// </summary>
|
||||
/// <param name="key">key</param>
|
||||
/// <param name="param">参数配置</param>
|
||||
/// <returns>是否能获取</returns>
|
||||
public bool TryGetParam(string key, out Param param)
|
||||
{
|
||||
return _params.TryGetValue(key, out param);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 按类型读取Json配置
|
||||
/// </summary>
|
||||
@@ -176,4 +153,4 @@ namespace AibisDream.Kit
|
||||
return fileNames;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 本地化参数
|
||||
/// 判断字符串是否为 l10n 参数引用。
|
||||
/// </summary>
|
||||
/// <param name="param">参数名</param>
|
||||
/// <returns></returns>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 按指定语言列解析本地化参数,供需要响应 Locale 切换的非 Yarn UI 使用。
|
||||
/// 从 Params String Table 异步解析 l10n 参数。非 l10n 参数原样返回。
|
||||
/// 当前 Locale 条目为空时保留空值,不使用任何 Locale 回退。
|
||||
/// </summary>
|
||||
public static string LocalizeParam(string param, LocaleKind localeKind)
|
||||
public static async Task<string> 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
|
||||
: "<null>";
|
||||
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 ?? "<null>";
|
||||
Debug.LogError(
|
||||
$"[LocalizationKit] Params 查询失败:Table={ConstRef.ParamsTable}, " +
|
||||
$"Key={localizationKey}, Locale={localeCode}。\n{exception}");
|
||||
return $"⟦{localizationKey}⟧";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 协程版 l10n 参数解析,供 Yarn Command 和 MonoBehaviour 使用。
|
||||
/// </summary>
|
||||
public static IEnumerator LocalizeParam(
|
||||
string param,
|
||||
Locale locale,
|
||||
Action<string> completed)
|
||||
{
|
||||
Task<string> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -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<ExpressionRoundDefinition> rounds = new();
|
||||
|
||||
private Dictionary<string, ExpressionRoundDefinition> roundById;
|
||||
|
||||
public string ActorLoopReference => actorLoopReference;
|
||||
public IReadOnlyList<ExpressionRoundDefinition> 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<string> GetValidationErrors()
|
||||
{
|
||||
var errors = new List<string>();
|
||||
if (!LocalizationKit.IsLocalizedParam(actorLoopReference))
|
||||
errors.Add("Actor Loop 必须配置为 l10n.* 引用。");
|
||||
|
||||
var ids = new HashSet<string>(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<string, ExpressionRoundDefinition>(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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a86a72005fba4c99a59962fb9e92df83
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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<string> defaultAnxietyPhrases = new List<string>
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/// <summary>当前屏幕 Glitch 档位(0–1),由 expression_screen_glitch 持续保持。</summary>
|
||||
public float ScreenGlitchLevel => screenGlitchIntensity;
|
||||
@@ -274,6 +288,7 @@ namespace AibisDream
|
||||
/// </summary>
|
||||
public void CloseView()
|
||||
{
|
||||
ActiveExpressionLocale = null;
|
||||
screenPresentation?.ResetImmediate();
|
||||
ResetScreenGlitchState();
|
||||
ApplyScreenGlitchProperties();
|
||||
@@ -941,6 +956,7 @@ namespace AibisDream
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
ActiveExpressionLocale = null;
|
||||
ResetScreenGlitchState();
|
||||
ApplyScreenGlitchProperties();
|
||||
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
|
||||
namespace AibisDream.MiniGame.Language
|
||||
{
|
||||
/// <summary>
|
||||
/// 将玩家可见文字按 Unicode 文本元素拆分。空白保留在布局中,但不生成粒子。
|
||||
/// 随机粒子字符池不使用本类,维持现有行为。
|
||||
/// </summary>
|
||||
public static class ExpressionTextTokenizer
|
||||
{
|
||||
public readonly struct Layout
|
||||
{
|
||||
public Layout(List<string> visibleElements, List<float> offsets)
|
||||
{
|
||||
VisibleElements = visibleElements;
|
||||
Offsets = offsets;
|
||||
}
|
||||
|
||||
public IReadOnlyList<string> VisibleElements { get; }
|
||||
public IReadOnlyList<float> Offsets { get; }
|
||||
public int Count => VisibleElements?.Count ?? 0;
|
||||
}
|
||||
|
||||
public static List<string> GetVisibleElements(string text)
|
||||
{
|
||||
var result = new List<string>();
|
||||
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<string>();
|
||||
var positions = new List<float>();
|
||||
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<string> source,
|
||||
IReadOnlyList<string> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f8ba4ca26b9b46649c0da7161dc9c756
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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<CandidateParticle> particles = GetPresentationPhraseParticles();
|
||||
if (!ValidatePresentationPhrase(text, particles, "真话失稳"))
|
||||
yield break;
|
||||
List<string> 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<Vector3> destinations = CalculatePresentationPhrasePositions(text.Length);
|
||||
UpdateFinalPresentationPhrase(text, destinations);
|
||||
List<Vector3> 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<CandidateParticle> particles = GetPresentationPhraseParticles();
|
||||
if (!ValidatePresentationPhrase(text, particles, "真话攻击"))
|
||||
yield break;
|
||||
List<string> 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<Vector3> destinations = CalculatePresentationPhrasePositions(text.Length);
|
||||
UpdateFinalPresentationPhrase(text, destinations);
|
||||
List<Vector3> 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<CandidateParticle> particles = GetPresentationPhraseParticles();
|
||||
if (!ValidatePresentationPhrase(text, particles, "真话稳定"))
|
||||
yield break;
|
||||
List<string> characters = ExpressionTextTokenizer.GetVisibleElements(text);
|
||||
|
||||
PreparePresentationPhraseControl();
|
||||
|
||||
@@ -2384,8 +2397,8 @@ namespace AibisDream.MiniGame.Language
|
||||
|
||||
yield return new WaitForSeconds(breakDuration);
|
||||
|
||||
List<Vector3> destinations = CalculatePresentationPhrasePositions(text.Length);
|
||||
UpdateFinalPresentationPhrase(text, destinations);
|
||||
List<Vector3> 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<Vector3> CalculatePresentationPhrasePositions(int characterCount)
|
||||
private List<Vector3> CalculatePresentationPhrasePositions(string text)
|
||||
{
|
||||
var positions = new List<Vector3>(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<Vector3>(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<Vector3> positions)
|
||||
private void UpdateFinalPresentationPhrase(
|
||||
IReadOnlyList<string> characters,
|
||||
List<Vector3> 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<string> targetElements = ExpressionTextTokenizer.GetVisibleElements(targetSentence);
|
||||
List<string> 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<CandidateParticle>();
|
||||
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);
|
||||
|
||||
@@ -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<AibisDream.ExpressionManager>();
|
||||
|
||||
private static Locale GetCommandLocale()
|
||||
{
|
||||
Locale current = LocalizationSettings.SelectedLocale;
|
||||
return ExpressionManager != null
|
||||
? ExpressionManager.GetExpressionLocale(current)
|
||||
: current;
|
||||
}
|
||||
|
||||
private static IEnumerator ResolveTextReferences(
|
||||
Locale locale,
|
||||
System.Action<string[]> completed,
|
||||
params string[] references)
|
||||
{
|
||||
var tasks = new Task<string>[references.Length];
|
||||
for (int i = 0; i < references.Length; i++)
|
||||
tasks[i] = LocalizationKit.LocalizeParamAsync(references[i], locale);
|
||||
|
||||
Task<string[]> all = Task.WhenAll(tasks);
|
||||
while (!all.IsCompleted)
|
||||
yield return null;
|
||||
|
||||
completed?.Invoke(all.Status == TaskStatus.RanToCompletion
|
||||
? all.Result
|
||||
: System.Array.Empty<string>());
|
||||
}
|
||||
|
||||
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<string> tokens)
|
||||
{
|
||||
tokens = new List<string>();
|
||||
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 对同名 <see cref="YarnCommand"/> 重载支持不可靠,只保留一个注册入口,用可选参数区分 3/4 参调用。
|
||||
/// <<start_expression "哈哈|嘿嘿|呵呵" "我很害怕" "ExpressionCompleted">>
|
||||
/// <<start_expression "哈哈|嘿嘿|呵呵" "我很害怕" "ExpressionCompleted" 24>>
|
||||
/// <<start_expression "l10n.hs.exp.log1.tokens" "l10n.hs.exp.log1.target" "ExpressionCompleted">>
|
||||
/// <<start_expression "l10n.hs.exp.log1.tokens" "l10n.hs.exp.log1.target" "ExpressionCompleted" 24>>
|
||||
/// (第四项为「非目标」候选粒子总数,候选池 = 目标句字数 + 该值;不写第四项则用 Inspector 的 Candidate Count)
|
||||
/// </summary>
|
||||
/// <param name="anxietyPhrasesStr">干扰短句(笑话等),用 | 分隔,用于非目标粒子字符池,如:"哈哈|嘿嘿|呵呵"</param>
|
||||
@@ -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<string> 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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用 ExpressionContentCatalog 中的轮次配置启动表达系统。
|
||||
/// <<start_expression_round "log1" "LOG1梳理完成">>
|
||||
/// </summary>
|
||||
[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<string> 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<string> 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
|
||||
/// <summary>
|
||||
/// 结尾真假快速闪切(非阻塞,节奏由同长度 wait 控制):真心话与谎话交替独占屏幕,
|
||||
/// 两者的屏幕底色(电视头背景)不同,越到后面切得越快,结束定格在真话帧。
|
||||
/// <<expression_truth_lie_flicker "没问题|冇问题|帽问题" "我就是个笑话" 3.2 0.24 12>>
|
||||
/// <<expression_truth_lie_flicker "l10n.hs.exp.actor.loop" "l10n.hs.exp.log1.target" 3.2 0.24 12>>
|
||||
/// </summary>
|
||||
[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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 定格的谎话被 glitch 翻转成真话(阻塞):大字乱跳中长度从谎话渐变到真话、真假底色抢闪,
|
||||
/// 最后落在暖色真话。紧接 expression_memory_cut + expression_truth_snap 收尾。
|
||||
/// <<expression_lie_morph_truth "没问题" "我就是个笑话" 0.9>>
|
||||
/// <<expression_lie_morph_truth "l10n.hs.exp.atk.why" "l10n.hs.exp.log1.target" 0.9>>
|
||||
/// </summary>
|
||||
[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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 演员大字闪现(阻塞):抖动的粒子字瞬间隐藏,屏幕中央出现白色大字(红青色散双影+急速缩放),
|
||||
/// 停顿 holdDuration 秒后始终恢复粒子字抖动;结尾定格请使用 expression_actor_flash_in。
|
||||
/// <<expression_actor_flash "没问题" 0.6 10.5 1.15 1.60 0.03 0.22 0.5 0.2>>
|
||||
/// <<expression_actor_flash "l10n.hs.exp.atk.why" 0.6 10.5 1.15 1.60 0.03 0.22 0.5 0.2>>
|
||||
/// </summary>
|
||||
[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
|
||||
/// <summary>
|
||||
/// 演员大字入场并保持(阻塞):入场表现与 expression_actor_flash 相同,但不执行退场,
|
||||
/// 适合在段落结尾定格。后续清场命令仍会正常移除它。
|
||||
/// <<expression_actor_flash_in "没问题" 0.35 10.5 1.1 1.45 0.008 0.06 0.5 0>>
|
||||
/// <<expression_actor_flash_in "l10n.hs.exp.atk.why" 0.35 10.5 1.1 1.45 0.008 0.06 0.5 0>>
|
||||
/// </summary>
|
||||
[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
|
||||
/// <<expression_actor_flash_loop_start>>
|
||||
/// </summary>
|
||||
[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<string> phrases))
|
||||
{
|
||||
UnityEngine.Debug.LogError(
|
||||
"[LanguageYarnCommand] Actor Loop 短句列表无效;命令已安全结束。");
|
||||
yield break;
|
||||
}
|
||||
|
||||
presentation.StartActorFlashLoop(phrases);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -525,7 +770,7 @@ namespace AibisDream.MiniGame.Language
|
||||
/// <summary>
|
||||
/// 真话闪现(阻塞):谎话大字之间,暖色完整真话带细微抖动挣扎浮现一拍;
|
||||
/// "被掐断"由紧随其后的 expression_glitch_pulse 表现。不推进谎话冲击等级。
|
||||
/// <<expression_actor_truth_flash "我就是个笑话" 0.35 8.5 0.55 0.02>>
|
||||
/// <<expression_actor_truth_flash "l10n.hs.exp.log1.target" 0.35 8.5 0.55 0.02>>
|
||||
/// </summary>
|
||||
[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));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 老虎机换字(非阻塞,节拍由 Yarn wait 控制):聚焦字在 duration 内只从 charPool 里疯狂换字。
|
||||
/// truthPool 非空时其字符也混入换字池,并以真话暖色显示。
|
||||
/// <<expression_actor_slot "没问题冇" 5 0.05 14 "我就是个笑话">>
|
||||
/// <<expression_actor_slot "l10n.hs.exp.actor.loop" 5 0.05 14 "l10n.hs.exp.log1.target">>
|
||||
/// </summary>
|
||||
[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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -583,7 +855,7 @@ namespace AibisDream.MiniGame.Language
|
||||
|
||||
/// <summary>
|
||||
/// 单字/短词满屏重击,并等待该拍结束。
|
||||
/// <<expression_actor_word_hit "没" 0.16 18 1.55>>
|
||||
/// <<expression_actor_word_hit "l10n.hs.exp.atk.why" 0.16 18 1.55>>
|
||||
/// </summary>
|
||||
[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));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 单列词组严格裁在表情屏内,像字幕一样无缝向下滚动。
|
||||
/// <<expression_actor_scroll "没问题|冇问题|帽问题" 1.8 0.85 0.45 5.2>>
|
||||
/// <<expression_actor_scroll "l10n.hs.exp.actor.loop" 1.8 0.85 0.45 5.2>>
|
||||
/// </summary>
|
||||
[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
|
||||
/// <summary>
|
||||
/// 用现有目标粒子完整显示一个红色攻击词,并等待确定性的冲击动作结束。
|
||||
/// 超过目标粒子数量时会安全跳过,不会截断。
|
||||
/// <<expression_truth_attack "为什么" 0.34 0.45>>
|
||||
/// <<expression_truth_attack "l10n.hs.exp.atk.why" 0.34 0.45>>
|
||||
/// </summary>
|
||||
[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));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Keep an already resolved truth phrase in place while it gradually becomes unstable and red.
|
||||
/// <<expression_truth_destabilize "不要那样看我" 1.6 0.13 22 "FF304D">>
|
||||
/// <<expression_truth_destabilize "l10n.hs.exp.log3.target" 1.6 0.13 22 "FF304D">>
|
||||
/// </summary>
|
||||
[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
|
||||
|
||||
/// <summary>
|
||||
/// 将当前攻击词重组为固定青绿色真话,并等待稳定完成。
|
||||
/// <<expression_truth_settle "我想赢一次" 0.8>>
|
||||
/// <<expression_truth_settle "l10n.hs.exp.final.win" 0.8>>
|
||||
/// </summary>
|
||||
[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")]
|
||||
|
||||
@@ -808,14 +808,19 @@ namespace AibisDream.MiniGame.Language
|
||||
}
|
||||
}
|
||||
|
||||
public void StartActorFlashLoop()
|
||||
public void StartActorFlashLoop(IReadOnlyList<string> 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<string> 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;
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
键,中文,英文,日文
|
||||
Key,Cn,En,Ja
|
||||
天空,天空,Sky,そら
|
||||
海报,海报,Poster,ポスター
|
||||
植物,植物,Plant,植物
|
||||
朋友,朋友,Friend,友達
|
||||
情绪,情绪,Emo,情緒
|
||||
记忆,记忆,Memory,記憶
|
||||
逻辑,逻辑,Logic,論理
|
||||
|
@@ -71,7 +71,7 @@ me: 这一条看起来……是一个关于焦虑的记录。 #line:046ab93d
|
||||
Task: 第 1 条log:左键排斥、右键吸引,连接目标并分离干扰 #line:07a299c
|
||||
<<play_sfx event:/Scriptal/hs1_log1>>
|
||||
<<set_global_param hs1LogStage 1>>
|
||||
<<start_expression "哈哈|嘿嘿|呵呵" "我就是个笑话" "LOG1梳理完成" 16>>
|
||||
<<start_expression_round "log1" "LOG1梳理完成">>
|
||||
|
||||
===
|
||||
|
||||
@@ -131,8 +131,8 @@ position: 900,2100
|
||||
// JumpDevTool 专用:从 Stage6就绪存档恢复后,跳过粒子玩法直接预览 LOG1 演出
|
||||
<<clear_tasks>>
|
||||
<<switch_fix_system_to Expression>>
|
||||
<<start_expression "哈哈|嘿嘿|呵呵" "我就是个笑话" "LOG1梳理完成" 16>>
|
||||
// <<start_expression "哈哈哈哈|笑死我了|太好笑了|绷不住了|笑不活了|真的太搞笑" "不要那样看我" "LOG疏通全部完成" 38>>
|
||||
<<start_expression_round "log1" "LOG1梳理完成">>
|
||||
// <<start_expression "l10n.hs.exp.log3.tokens" "l10n.hs.exp.log3.target" "LOG疏通全部完成" 38>>
|
||||
===
|
||||
|
||||
|
||||
@@ -243,7 +243,7 @@ Task: 第 2 条log:左键排斥、右键吸引,连接目标并分离干扰 #
|
||||
<<set_global_param hs1LogStage 1>>
|
||||
<<stop_sfx event:/Scriptal/hs1_log1>>
|
||||
<<play_sfx event:/Scriptal/hs1_log2>>
|
||||
<<start_expression "哈哈哈|嘿嘿嘿|呵呵呵|笑死了|好好笑" "不要离开我" "LOG2梳理完成" 28>>
|
||||
<<start_expression_round "log2" "LOG2梳理完成">>
|
||||
===
|
||||
|
||||
|
||||
@@ -297,7 +297,7 @@ position: 900,2550
|
||||
// JumpDevTool 专用:从 Stage6就绪存档恢复后,跳过粒子玩法直接预览 LOG2 演出
|
||||
<<clear_tasks>>
|
||||
<<switch_fix_system_to Expression>>
|
||||
<<start_expression "哈哈哈|嘿嘿嘿|呵呵呵|笑死了|好好笑" "不要离开我" "LOG2梳理完成" 28>>
|
||||
<<start_expression_round "log2" "LOG2梳理完成">>
|
||||
===
|
||||
|
||||
|
||||
@@ -399,7 +399,7 @@ Task: 第 3 条log:左键排斥、右键吸引,连接目标并分离干扰 #
|
||||
<<set_global_param hs1LogStage 1>>
|
||||
<<stop_sfx event:/Scriptal/hs1_log2>>
|
||||
<<play_sfx event:/Scriptal/hs1_log3>>
|
||||
<<start_expression "哈哈哈哈|笑死我了|太好笑了|绷不住了|笑不活了|真的太搞笑" "为什么不相信我" "LOG疏通全部完成" 38>>
|
||||
<<start_expression_round "log3" "LOG疏通全部完成">>
|
||||
===
|
||||
|
||||
|
||||
@@ -462,7 +462,7 @@ position: 900,3000
|
||||
// JumpDevTool 专用:从 Stage6就绪存档恢复后,跳过粒子玩法直接预览 LOG3 演出
|
||||
<<clear_tasks>>
|
||||
<<switch_fix_system_to Expression>>
|
||||
<<start_expression "哈哈哈哈|笑死我了|太好笑了|绷不住了|笑不活了|真的太搞笑" "为什么不相信我" "LOG疏通全部完成" 38>>
|
||||
<<start_expression_round "log3" "LOG疏通全部完成">>
|
||||
===
|
||||
|
||||
|
||||
@@ -507,7 +507,7 @@ hs: …… #line:085bda6
|
||||
<<expression_truth_gather 1 0.5>>
|
||||
<<wait 0.5>>
|
||||
hs: <speed=0.2>不要那样看我…… #line:00e7b75
|
||||
<<expression_truth_destabilize "为什么不相信我" 1.6 0.05 22 "FF304D">>
|
||||
<<expression_truth_destabilize "l10n.hs.exp.log3.target" 1.6 0.05 22 "FF304D">>
|
||||
<<jump LOG3到结束>>
|
||||
===
|
||||
|
||||
@@ -528,69 +528,69 @@ me: 等会…其他的log好像有异常传入…… #line:0d3cc39
|
||||
// 节奏不是一句一格。四种拍子交替:**空**(留白)/ **连**(挤在一起吐出来)/
|
||||
// **顿**(狠话之后的静默)/ **急**(越来越快)。均匀推进会把最狠的几句读平。
|
||||
// —— 空:起手两记,各自留白
|
||||
<<expression_truth_attack "为什么" 0.30 0.32>>
|
||||
<<expression_truth_attack "l10n.hs.exp.atk.why" 0.30 0.32>>
|
||||
hs: <speed=0.1>为什么#auto_next #line:059ef58
|
||||
<<wait 2>>
|
||||
<<expression_truth_attack "不相信我" 0.32 0.40>>
|
||||
<<expression_truth_attack "l10n.hs.exp.atk.doubt" 0.32 0.40>>
|
||||
hs: <speed=0.5>不相信我? #line:06810ae
|
||||
<<wait 1.5>>
|
||||
// —— 连:辩解一口气倒出来,中间不喘
|
||||
<<expression_truth_attack "我明白我垫底" 0.32 0.34>>
|
||||
<<expression_truth_attack "l10n.hs.exp.atk.last" 0.32 0.34>>
|
||||
hs: <speed=0.2>我明白我垫底, #line:00248ff
|
||||
<<expression_truth_attack "天气真的不行" 0.34 0.46>>
|
||||
<<expression_truth_attack "l10n.hs.exp.atk.weather" 0.34 0.46>>
|
||||
hs: <speed=0.2>可是这周天气真的不行! #line:0ffc958
|
||||
<<expression_truth_attack "卖不出去" 0.34 0.50>>
|
||||
<<expression_truth_attack "l10n.hs.exp.atk.unsold" 0.34 0.50>>
|
||||
hs: <speed=0.5>其它销售……也是一顶都卖不出去! #line:0d2c25f
|
||||
// —— 顿:辩解说完,发现没人信,卡壳一下
|
||||
<<wait 0.6>>
|
||||
<<expression_truth_attack "努力解释" 0.34 0.52>>
|
||||
<<expression_truth_attack "l10n.hs.exp.atk.explain" 0.34 0.52>>
|
||||
hs: <speed=0.2>为什么我都这么努力解释了,#auto_next #line:0ebd252
|
||||
<<expression_truth_attack "不愿意相信我" 0.36 0.58>>
|
||||
<<expression_truth_attack "l10n.hs.exp.atk.refuse" 0.36 0.58>>
|
||||
hs: <speed=0.5>你还是不愿意相信我? #line:0328a7c
|
||||
<<expression_truth_attack "一点信任" 0.38 0.62>>
|
||||
<<expression_truth_attack "l10n.hs.exp.atk.trust" 0.38 0.62>>
|
||||
hs: <speed=0.2>到底它们是你的员工,还是我?#auto_next #line:0a915ef
|
||||
hs: <speed=0.5>你对我从来没有一点信任…… #line:0b69892
|
||||
// —— 顿:最长的一次静默,攒住再爆
|
||||
<<wait 1.0>>
|
||||
<<expression_truth_attack "受够了" 0.42 0.76>>
|
||||
<<expression_truth_attack "l10n.hs.exp.atk.enough" 0.42 0.76>>
|
||||
hs: <speed=0.5>我真的受够了! #line:0312421
|
||||
hs: <speed=0.2>总是被你当狗一样训,#auto_next #line:00412cb
|
||||
<<expression_truth_attack "受够了" 0.28 0.88>>
|
||||
<<expression_truth_attack "l10n.hs.exp.atk.enough" 0.28 0.88>>
|
||||
<<wait 0.2>>
|
||||
<<expression_truth_attack "受够了" 0.28 0.88>>
|
||||
<<expression_truth_attack "l10n.hs.exp.atk.enough" 0.28 0.88>>
|
||||
<<wait 0.2>>
|
||||
<<expression_truth_attack "受够了" 0.28 0.88>>
|
||||
<<expression_truth_attack "l10n.hs.exp.atk.enough" 0.28 0.88>>
|
||||
<<wait 1.5>>
|
||||
<<expression_truth_attack "真该死" 0.34 0.55>>
|
||||
<<expression_truth_attack "l10n.hs.exp.atk.damn" 0.34 0.55>>
|
||||
hs: <speed=0.2>路斯,我它码真该死! #line:03678e3
|
||||
<<expression_truth_attack "真没用" 0.34 0.58>>
|
||||
<<expression_truth_attack "l10n.hs.exp.atk.useless" 0.34 0.58>>
|
||||
hs: 我真没用! #line:093977a
|
||||
<<wait 0.45>>
|
||||
<<expression_truth_attack "证明自己" 0.35 0.60>>
|
||||
<<expression_truth_attack "l10n.hs.exp.atk.prove" 0.35 0.60>>
|
||||
hs: <speed=0.2>你不知道我有多想证明自己,#auto_next #line:0555cab
|
||||
<<expression_truth_attack "什么都做不到" 0.38 0.66>>
|
||||
<<expression_truth_attack "l10n.hs.exp.atk.nothing" 0.38 0.66>>
|
||||
hs: <speed=0.5>可是我什么都做不到…… #line:0cf70a3
|
||||
<<wait 0.5>>
|
||||
// —— 急:一句比一句挤,往最狠的那句冲
|
||||
<<expression_truth_attack "追上你" 0.36 0.64>>
|
||||
<<expression_truth_attack "l10n.hs.exp.atk.catchup" 0.36 0.64>>
|
||||
hs: <speed=0.2>我没办法追上你,#auto_next #line:0deff66
|
||||
<<expression_truth_attack "帮到英理" 0.38 0.68>>
|
||||
<<expression_truth_attack "l10n.hs.exp.atk.yingli" 0.38 0.68>>
|
||||
hs: <speed=0.2>没办法帮到英理,#auto_next #line:0c2e7b8
|
||||
<<expression_truth_attack "没有勇气" 0.40 0.74>>
|
||||
<<expression_truth_attack "l10n.hs.exp.atk.courage" 0.40 0.74>>
|
||||
hs: <speed=0.2>甚至连自己被羞辱了都没有勇气#auto_next #line:00ab3a7
|
||||
<<expression_truth_attack "说出来" 0.40 0.78>>
|
||||
<<expression_truth_attack "l10n.hs.exp.atk.say" 0.40 0.78>>
|
||||
hs: <speed=0.5>说出来,#auto_next #line:0ad49b4
|
||||
// —— 顿:最狠那句前面一定要有空
|
||||
<<wait 0.55>>
|
||||
<<expression_truth_attack "废物" 0.42 0.82>>
|
||||
<<expression_truth_attack "l10n.hs.exp.atk.trash" 0.42 0.82>>
|
||||
hs: <speed=0.2>我就是个废物…… #line:079cc40
|
||||
<<expression_truth_attack "废物" 0.22 0.82>>
|
||||
<<expression_truth_attack "l10n.hs.exp.atk.trash" 0.22 0.82>>
|
||||
<<wait 0.2>>
|
||||
<<expression_truth_attack "废物" 0.22 0.82>>
|
||||
<<expression_truth_attack "l10n.hs.exp.atk.trash" 0.22 0.82>>
|
||||
<<wait 0.2>>
|
||||
<<expression_truth_attack "废物" 0.22 0.82>>
|
||||
<<expression_truth_attack "l10n.hs.exp.atk.trash" 0.22 0.82>>
|
||||
<<wait 0.2>>
|
||||
<<expression_truth_attack "烂泥扶不上墙" 0.58 1.00>>
|
||||
<<expression_truth_attack "l10n.hs.exp.atk.hopeless" 0.58 1.00>>
|
||||
hs: <speed=0.8>我它码就是个烂泥扶不上墙的废物!! #line:0e8a4da
|
||||
// —— 空:吼到底之后整个泄下来,转成气音
|
||||
<<wait 0.85>>
|
||||
@@ -602,15 +602,15 @@ hs: <speed=0.3>就这么想赢一次…… #line:08aece0
|
||||
<<expression_screen_stress 0.3 1.5>>
|
||||
// 五连击必须**错开**:原本五条挤在同一帧,五记 punch 会合成一记,读不出连击。
|
||||
// 间隔越来越短 = 越捶越急。
|
||||
<<expression_truth_attack "为什么为什么" 0.4 1.00>>
|
||||
<<expression_truth_attack "l10n.hs.exp.atk.why_loop" 0.4 1.00>>
|
||||
<<wait 0.3>>
|
||||
<<expression_truth_attack "为什么为什么" 0.3 1.00>>
|
||||
<<expression_truth_attack "l10n.hs.exp.atk.why_loop" 0.3 1.00>>
|
||||
<<wait 0.24>>
|
||||
<<expression_truth_attack "为什么为什么" 0.2 1.00>>
|
||||
<<expression_truth_attack "l10n.hs.exp.atk.why_loop" 0.2 1.00>>
|
||||
<<wait 0.18>>
|
||||
<<expression_truth_attack "为什么为什么" 0.2 1.00>>
|
||||
<<expression_truth_attack "l10n.hs.exp.atk.why_loop" 0.2 1.00>>
|
||||
<<wait 0.14>>
|
||||
<<expression_truth_attack "为什么为什么" 0.2 1.00>>
|
||||
<<expression_truth_attack "l10n.hs.exp.atk.why_loop" 0.2 1.00>>
|
||||
<<start_timeline "火山Glitch/火山头痛3">>
|
||||
<<expression_memory_cut>>
|
||||
<<expression_glitch_pulse 0.82 0.03 0.18>>
|
||||
@@ -622,7 +622,7 @@ hs: <speed=0.3>就这么想赢一次…… #line:08aece0
|
||||
<<expression_screen_rage 0 0.55>>
|
||||
<<expression_truth_snap 0.1 0.25>>
|
||||
hs: <speed=0.2><shake a=0.3>我真的好想…</shake> #line:02fff1d
|
||||
<<expression_truth_settle "我想赢一次" 0.55>>
|
||||
<<expression_truth_settle "l10n.hs.exp.final.win" 0.55>>
|
||||
<<wait_timeline "火山Glitch/火山头痛3">>
|
||||
<<wait 0.6>>
|
||||
me: …… #line:04c2999
|
||||
|
||||
@@ -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.<entry-key> 从 HuoshanStage6 String Table 解析
|
||||
普通字符串 按原始字符串使用,保持旧内容兼容
|
||||
```
|
||||
|
||||
示例:
|
||||
正式 Yarn 使用:
|
||||
|
||||
```yarn
|
||||
<<start_expression
|
||||
"@h6.round.log1.interference_tokens"
|
||||
"@h6.round.log1.target"
|
||||
"LOG1梳理完成"
|
||||
16>>
|
||||
|
||||
<<expression_truth_attack "@h6.release.attack.why" 0.30 0.32>>
|
||||
<<expression_truth_settle "@h6.release.settle.want_to_win_once" 0.55>>
|
||||
<<expression_truth_destabilize
|
||||
"@h6.release.destabilize.dont_look_at_me"
|
||||
1.6
|
||||
0.05
|
||||
22
|
||||
"FF304D">>
|
||||
<<start_expression_round "log1" "LOG1梳理完成">>
|
||||
```
|
||||
|
||||
其中:
|
||||
旧 `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<string> 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<string> completed);
|
||||
## token 与 Unicode
|
||||
|
||||
public IEnumerator Resolve(
|
||||
string textReference,
|
||||
Locale locale,
|
||||
Action<string> completed);
|
||||
干扰 token 只接受半角 `|`:
|
||||
|
||||
public IEnumerator LoadRound(
|
||||
string roundId,
|
||||
Locale locale,
|
||||
Action<Stage6RoundText> 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 文本元素行为。
|
||||
|
||||
Reference in New Issue
Block a user