diff --git a/Assets/AddressableAssetsData/AssetGroups/Localization-String-Tables-Portuguese (Brazil) (pt-BR).asset b/Assets/AddressableAssetsData/AssetGroups/Localization-String-Tables-Portuguese (Brazil) (pt-BR).asset index 11ff7620c..61ec7e31b 100644 --- a/Assets/AddressableAssetsData/AssetGroups/Localization-String-Tables-Portuguese (Brazil) (pt-BR).asset +++ b/Assets/AddressableAssetsData/AssetGroups/Localization-String-Tables-Portuguese (Brazil) (pt-BR).asset @@ -23,6 +23,18 @@ MonoBehaviour: m_SerializedLabels: - Locale-pt-BR FlaggedDuringContentUpdateRestriction: 0 + - m_GUID: 6ad3c9298dbc3b642b1251ffc306eaa6 + m_Address: Params_pt-BR + m_ReadOnly: 1 + m_SerializedLabels: + - Locale-pt-BR + FlaggedDuringContentUpdateRestriction: 0 + - m_GUID: 7d496f91e240bb143b62a6239c817d09 + m_Address: UIText_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: diff --git a/Assets/AddressableAssetsData/AssetGroups/Localization-String-Tables-Russian (ru).asset b/Assets/AddressableAssetsData/AssetGroups/Localization-String-Tables-Russian (ru).asset index e9d766346..7b69f4afa 100644 --- a/Assets/AddressableAssetsData/AssetGroups/Localization-String-Tables-Russian (ru).asset +++ b/Assets/AddressableAssetsData/AssetGroups/Localization-String-Tables-Russian (ru).asset @@ -23,6 +23,18 @@ MonoBehaviour: m_SerializedLabels: - Locale-ru FlaggedDuringContentUpdateRestriction: 0 + - m_GUID: b91f4281442d2b54f92fbecb5bd56426 + m_Address: Params_ru + m_ReadOnly: 1 + m_SerializedLabels: + - Locale-ru + FlaggedDuringContentUpdateRestriction: 0 + - m_GUID: f679069e455200841a92c27186383940 + m_Address: UIText_ru + m_ReadOnly: 1 + m_SerializedLabels: + - Locale-ru + FlaggedDuringContentUpdateRestriction: 0 m_ReadOnly: 1 m_Settings: {fileID: 11400000, guid: 77169ce22e430f64fb36c771815a4a7b, type: 2} m_SchemaSet: diff --git a/Assets/AddressableAssetsData/AssetGroups/Localization-String-Tables-Spanish (es).asset b/Assets/AddressableAssetsData/AssetGroups/Localization-String-Tables-Spanish (es).asset index 48bafd2a8..584d3089c 100644 --- a/Assets/AddressableAssetsData/AssetGroups/Localization-String-Tables-Spanish (es).asset +++ b/Assets/AddressableAssetsData/AssetGroups/Localization-String-Tables-Spanish (es).asset @@ -23,6 +23,18 @@ MonoBehaviour: m_SerializedLabels: - Locale-es FlaggedDuringContentUpdateRestriction: 0 + - m_GUID: 1edf9254b97981643bdecd6d22ed1150 + m_Address: Params_es + m_ReadOnly: 1 + m_SerializedLabels: + - Locale-es + FlaggedDuringContentUpdateRestriction: 0 + - m_GUID: 380c27123212d66468aa1f3ed3bf72ea + m_Address: UIText_es + m_ReadOnly: 1 + m_SerializedLabels: + - Locale-es + FlaggedDuringContentUpdateRestriction: 0 m_ReadOnly: 1 m_Settings: {fileID: 11400000, guid: 77169ce22e430f64fb36c771815a4a7b, type: 2} m_SchemaSet: diff --git a/Assets/Editor/Huoshan/ExpressionLocalizationTests.cs b/Assets/Editor/Huoshan/ExpressionLocalizationTests.cs index 3059607fb..529e06503 100644 --- a/Assets/Editor/Huoshan/ExpressionLocalizationTests.cs +++ b/Assets/Editor/Huoshan/ExpressionLocalizationTests.cs @@ -41,6 +41,26 @@ namespace AibisDream.EditorTests.Huoshan Assert.That(LocalizationKit.GetL10NParamKey("raw"), Is.EqualTo("raw")); } + [Test] + public void MissingParamResult_RequiresMatchingLocalizedReference() + { + Assert.That( + LocalizationKit.IsMissingParamResult( + "l10n.hs.exp.pool", + "⟦hs.exp.pool⟧"), + Is.True); + Assert.That( + LocalizationKit.IsMissingParamResult( + "l10n.hs.exp.pool", + "⟦another.key⟧"), + Is.False); + Assert.That( + LocalizationKit.IsMissingParamResult( + "raw", + "⟦raw⟧"), + Is.False); + } + [UnityTest] public IEnumerator LocalizeParamAsync_UsesExplicitLocaleAndDoesNotFallback() { @@ -111,7 +131,6 @@ namespace AibisDream.EditorTests.Huoshan AssetDatabase.LoadAssetAtPath(CatalogPath); Assert.That(catalog, Is.Not.Null); Assert.That(catalog.GetValidationErrors(), Is.Empty); - Assert.That(catalog.ActorLoopReference, Is.EqualTo("l10n.hs.exp.actor.loop")); Assert.That(catalog.Rounds.Select(round => round.Id), Is.EqualTo(new[] { "log1", "log2", "log3" })); @@ -128,7 +147,6 @@ namespace AibisDream.EditorTests.Huoshan ScriptableObject.CreateInstance(); try { - SetPrivateField(catalog, "actorLoopReference", "raw"); var first = new ExpressionRoundDefinition(); var second = new ExpressionRoundDefinition(); ConfigureRound(first, "log1", "l10n.target", "l10n.tokens", 1); @@ -139,7 +157,6 @@ namespace AibisDream.EditorTests.Huoshan new List { first, second }); string errors = string.Join("\n", catalog.GetValidationErrors()); - Assert.That(errors, Does.Contain("Actor Loop")); Assert.That(errors, Does.Contain("重复")); Assert.That(errors, Does.Contain("l10n.*")); } @@ -168,6 +185,100 @@ namespace AibisDream.EditorTests.Huoshan Is.False); } + [Test] + public void CharacterPoolParser_PreservesUnicodeDuplicatesAndRejectsSeparators() + { + string combining = "e\u0301"; + Assert.That( + LanguageYarnCommand.TryParseExpressionPool( + $" 中 中 あ {combining} 😀。 ", + out List pool), + Is.True); + Assert.That( + pool, + Is.EqualTo(new[] { "中", "中", "あ", combining, "😀", "。" })); + Assert.That( + LanguageYarnCommand.TryParseExpressionPool("中|あ", out _), + Is.False); + Assert.That( + LanguageYarnCommand.TryParseExpressionPool("中|あ", out _), + Is.False); + Assert.That( + LanguageYarnCommand.TryParseExpressionPool(" \t ", out _), + Is.False); + } + + [Test] + public void Params_ContainsLocalizedExpressPoolForEveryLocale() + { + StringTableCollection collection = + LocalizationEditorSettings.GetStringTableCollection(ConstRef.ParamsTable); + Assert.That(collection, Is.Not.Null); + + string key = + LocalizationKit.GetL10NParamKey(ConstRef.ExpressParticlePoolParam); + Assert.That(collection.SharedData.GetEntry(key), Is.Not.Null); + + string[] localeCodes = { "zh-Hans", "en", "ja-JP", "es", "ru", "pt-BR" }; + foreach (string localeCode in localeCodes) + { + StringTable table = + collection.GetTable(new LocaleIdentifier(localeCode)) as StringTable; + Assert.That(table, Is.Not.Null, localeCode); + Assert.That(table.GetEntry(key), Is.Not.Null, localeCode); + } + + StringTable chinese = + collection.GetTable(new LocaleIdentifier("zh-Hans")) as StringTable; + Assert.That( + ExpressionTextTokenizer.GetVisibleElements( + chinese.GetEntry(key).LocalizedValue), + Is.Not.Empty); + + foreach (string localeCode in localeCodes.Skip(1)) + { + StringTable table = + collection.GetTable(new LocaleIdentifier(localeCode)) as StringTable; + Assert.That(table.GetEntry(key).LocalizedValue, Is.Empty, localeCode); + } + } + + [Test] + public void TextParticle_UsesConfiguredRoleAndOverridePoolsWithoutFirstFrameFallback() + { + var gameObject = new GameObject("TextParticlePoolTest"); + try + { + CandidateParticle particle = gameObject.AddComponent(); + Assert.That(particle.currentChar, Is.Empty); + + string combining = "e\u0301"; + particle.SetCharacterPools( + new[] { "😀" }, + new[] { combining }); + + particle.originalIsRed = true; + particle.InitializeRandomCharacter(); + Assert.That(particle.currentChar, Is.EqualTo("😀")); + + particle.originalIsRed = false; + particle.InitializeRandomCharacter(); + Assert.That(particle.currentChar, Is.EqualTo(combining)); + + particle.SetOverrideCharacterPool(new[] { "。" }); + particle.InitializeRandomCharacter(); + Assert.That(particle.currentChar, Is.EqualTo("。")); + + particle.SetOverrideCharacterPool(null); + particle.InitializeRandomCharacter(); + Assert.That(particle.currentChar, Is.EqualTo(combining)); + } + finally + { + Object.DestroyImmediate(gameObject); + } + } + [Test] public void UnicodeTokenizer_PreservesTextElementsAndDropsWhitespace() { @@ -221,7 +332,7 @@ namespace AibisDream.EditorTests.Huoshan issues.Any(issue => issue.Severity == ExpressionLocalizationIssueSeverity.Warning), Is.True, - "英日空条目应作为待翻译警告保留。"); + "未翻译 Locale 的空条目应作为待翻译警告保留。"); } private static void ConfigureRound( diff --git a/Assets/Editor/Huoshan/ExpressionLocalizationValidator.cs b/Assets/Editor/Huoshan/ExpressionLocalizationValidator.cs index 511b2c175..9c657f7e0 100644 --- a/Assets/Editor/Huoshan/ExpressionLocalizationValidator.cs +++ b/Assets/Editor/Huoshan/ExpressionLocalizationValidator.cs @@ -36,7 +36,7 @@ namespace AibisDream.EditorTests.Huoshan /// /// 校验 Stage6 Express 命令、轮次 Catalog 与 Params String Table 的引用关系。 - /// 英日空值是待翻译警告,不阻断内容校验。 + /// 未翻译 Locale 的空值是待翻译警告,不阻断内容校验。 /// public static class ExpressionLocalizationValidator { @@ -60,7 +60,6 @@ namespace AibisDream.EditorTests.Huoshan 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 }, @@ -109,6 +108,8 @@ namespace AibisDream.EditorTests.Huoshan ValidateStage6(issues, referencedKeys); ValidateCatalog(issues, referencedKeys); + referencedKeys.Add( + LocalizationKit.GetL10NParamKey(ConstRef.ExpressParticlePoolParam)); ValidateParams(issues, referencedKeys); return issues; } @@ -178,7 +179,6 @@ namespace AibisDream.EditorTests.Huoshan foreach (string validationError in catalog.GetValidationErrors()) AddError(issues, validationError); - AddCatalogReference(catalog.ActorLoopReference, referencedKeys); foreach (ExpressionRoundDefinition round in catalog.Rounds) { if (round == null) @@ -206,6 +206,14 @@ namespace AibisDream.EditorTests.Huoshan collection.GetTable(new LocaleIdentifier("en")) as StringTable; StringTable japanese = collection.GetTable(new LocaleIdentifier("ja-JP")) as StringTable; + StringTable spanish = + collection.GetTable(new LocaleIdentifier("es")) as StringTable; + StringTable russian = + collection.GetTable(new LocaleIdentifier("ru")) as StringTable; + StringTable portuguese = + collection.GetTable(new LocaleIdentifier("pt-BR")) as StringTable; + string poolKey = + LocalizationKit.GetL10NParamKey(ConstRef.ExpressParticlePoolParam); foreach (string key in referencedKeys.OrderBy(value => value, StringComparer.Ordinal)) { @@ -222,14 +230,51 @@ namespace AibisDream.EditorTests.Huoshan continue; } - if (key.EndsWith(".tokens", StringComparison.Ordinal) || - string.Equals(key, "hs.exp.actor.loop", StringComparison.Ordinal)) + if (key.EndsWith(".tokens", StringComparison.Ordinal)) { ValidatePipeList(issues, key, chineseEntry.LocalizedValue); } - AddPendingTranslation(issues, english, "en", key); - AddPendingTranslation(issues, japanese, "ja-JP", key); + if (string.Equals(key, poolKey, StringComparison.Ordinal)) + { + ValidateCharacterPool( + issues, + key, + "zh-Hans", + chineseEntry.LocalizedValue); + AddPendingPoolTranslation(issues, english, "en", key); + AddPendingPoolTranslation(issues, japanese, "ja-JP", key); + AddPendingPoolTranslation(issues, spanish, "es", key); + AddPendingPoolTranslation(issues, russian, "ru", key); + AddPendingPoolTranslation(issues, portuguese, "pt-BR", key); + } + else + { + AddPendingTranslation(issues, english, "en", key); + AddPendingTranslation(issues, japanese, "ja-JP", key); + } + } + } + + private static void ValidateCharacterPool( + List issues, + string key, + string localeCode, + string value) + { + if (value.Contains('|') || value.Contains('|')) + { + AddError( + issues, + $"Params/{key}, Locale={localeCode} 的字符池不能包含“|”或“|”。"); + return; + } + + if (!LanguageYarnCommand.TryParseExpressionPool(value, out _)) + { + AddError( + issues, + $"Params/{key}, Locale={localeCode} 没有有效的 Unicode 文本元素。"); } } @@ -272,6 +317,34 @@ namespace AibisDream.EditorTests.Huoshan } } + private static void AddPendingPoolTranslation( + List issues, + StringTable table, + string localeCode, + string key) + { + StringTableEntry entry = table?.GetEntry(key); + if (entry == null) + { + AddError(issues, $"Params/{key} 缺少 {localeCode} 条目。"); + return; + } + + if (string.IsNullOrEmpty(entry.LocalizedValue)) + { + issues.Add(new ExpressionLocalizationIssue( + ExpressionLocalizationIssueSeverity.Warning, + $"待翻译:Params/{key}, Locale={localeCode}")); + return; + } + + ValidateCharacterPool( + issues, + key, + localeCode, + entry.LocalizedValue); + } + private static void AddCatalogReference( string reference, HashSet referencedKeys) diff --git a/Assets/Editor/Huoshan/LogReleasePresentationTests.cs b/Assets/Editor/Huoshan/LogReleasePresentationTests.cs index 8c316f3d7..5ad2a5228 100644 --- a/Assets/Editor/Huoshan/LogReleasePresentationTests.cs +++ b/Assets/Editor/Huoshan/LogReleasePresentationTests.cs @@ -105,17 +105,12 @@ namespace AibisDream.EditorTests.Huoshan Assert.That(lastCompletion, Is.EqualTo(totalDuration).Within(0.0001f)); } - [TestCase("LightPreset", 0.10f, 0.35f, 0.25f, 0.25f, 0.70f, 0.15f)] - [TestCase("MediumPreset", 0.20f, 0.55f, 0.50f, 0.35f, 0.90f, 0.30f)] - [TestCase("HeavyPreset", 0.35f, 0.80f, 0.85f, 0.50f, 1.10f, 0.50f)] + [TestCase("LightPreset", 0.10f)] + [TestCase("MediumPreset", 0.20f)] + [TestCase("HeavyPreset", 0.35f)] public void PresentationPreset_UsesApprovedValues( string presetFieldName, - float baseDistortion, - float liePause, - float breakPeak, - float breakDuration, - float truthDuration, - float truthPeripheral) + float baseDistortion) { const BindingFlags flags = BindingFlags.Static | BindingFlags.NonPublic; FieldInfo presetField = typeof(LogReleasePresentationController).GetField(presetFieldName, flags); @@ -124,11 +119,6 @@ namespace AibisDream.EditorTests.Huoshan Type presetType = preset.GetType(); AssertPresetField(presetType, preset, "BaseDistortion", baseDistortion); - AssertPresetField(presetType, preset, "LiePause", liePause); - AssertPresetField(presetType, preset, "BreakPeak", breakPeak); - AssertPresetField(presetType, preset, "BreakDuration", breakDuration); - AssertPresetField(presetType, preset, "TruthDuration", truthDuration); - AssertPresetField(presetType, preset, "TruthPeripheral", truthPeripheral); } [Test] diff --git a/Assets/Font/Assets/方正黑体简体 SDF.asset b/Assets/Font/Assets/方正黑体简体 SDF.asset index d1eef57a0..3799d5b43 100644 --- a/Assets/Font/Assets/方正黑体简体 SDF.asset +++ b/Assets/Font/Assets/方正黑体简体 SDF.asset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5afb8210c7b9dde3cfe36dadfb239feebe6a4a7f076ff4b7b6979dfc04f1456c -size 8566586 +oid sha256:b22f4332d3d76690e085b3ecc0516e1cfc0d65a9a2fc53c33ce3d08ac48963a8 +size 8568631 diff --git a/Assets/GameContent/Feature_Huoshan/Expression/ExpressionContentCatalog.asset b/Assets/GameContent/Feature_Huoshan/Expression/ExpressionContentCatalog.asset index 174b2a156..452d1dfd9 100644 --- a/Assets/GameContent/Feature_Huoshan/Expression/ExpressionContentCatalog.asset +++ b/Assets/GameContent/Feature_Huoshan/Expression/ExpressionContentCatalog.asset @@ -12,7 +12,6 @@ MonoBehaviour: 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 diff --git a/Assets/Language/Params/Params Shared Data.asset b/Assets/Language/Params/Params Shared Data.asset index 6698a0112..1100a2da7 100644 --- a/Assets/Language/Params/Params Shared Data.asset +++ b/Assets/Language/Params/Params Shared Data.asset @@ -67,10 +67,6 @@ MonoBehaviour: 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: @@ -155,6 +151,10 @@ MonoBehaviour: m_Key: hs.exp.final.win m_Metadata: m_Items: [] + - m_Id: 200000000000001029 + m_Key: hs.exp.pool + m_Metadata: + m_Items: [] m_Metadata: m_Items: [] m_KeyGenerator: diff --git a/Assets/Language/Params/Params.asset b/Assets/Language/Params/Params.asset index 428ec4545..a29689969 100644 --- a/Assets/Language/Params/Params.asset +++ b/Assets/Language/Params/Params.asset @@ -17,6 +17,9 @@ MonoBehaviour: - {fileID: 11400000, guid: 9248cb179e7aafa4c903f1fc54b41ef7, type: 2} - {fileID: 11400000, guid: aeb318c895d57014b89d5540c6baff95, type: 2} - {fileID: 11400000, guid: 9ed23d1d910a1a147ba5c316a87cbad4, type: 2} + - {fileID: 11400000, guid: 6ad3c9298dbc3b642b1251ffc306eaa6, type: 2} + - {fileID: 11400000, guid: b91f4281442d2b54f92fbecb5bd56426, type: 2} + - {fileID: 11400000, guid: 1edf9254b97981643bdecd6d22ed1150, type: 2} m_Extensions: [] m_Group: String Table references: diff --git a/Assets/Language/Params/Params_en.asset b/Assets/Language/Params/Params_en.asset index 535805882..b087b7c69 100644 --- a/Assets/Language/Params/Params_en.asset +++ b/Assets/Language/Params/Params_en.asset @@ -70,10 +70,6 @@ MonoBehaviour: m_Localized: m_Metadata: m_Items: [] - - m_Id: 200000000000001007 - m_Localized: - m_Metadata: - m_Items: [] - m_Id: 200000000000001008 m_Localized: m_Metadata: @@ -158,6 +154,10 @@ MonoBehaviour: m_Localized: m_Metadata: m_Items: [] + - m_Id: 200000000000001029 + m_Localized: + m_Metadata: + m_Items: [] references: version: 2 RefIds: [] diff --git a/Assets/Language/Params/Params_es.asset b/Assets/Language/Params/Params_es.asset index caedbf494..5a8f4f83c 100644 --- a/Assets/Language/Params/Params_es.asset +++ b/Assets/Language/Params/Params_es.asset @@ -17,7 +17,11 @@ MonoBehaviour: m_SharedData: {fileID: 11400000, guid: 4a2d391471bd9cd439150c52659e9846, type: 2} m_Metadata: m_Items: [] - m_TableData: [] + m_TableData: + - m_Id: 200000000000001029 + m_Localized: + m_Metadata: + m_Items: [] references: version: 2 RefIds: [] diff --git a/Assets/Language/Params/Params_ja-JP.asset b/Assets/Language/Params/Params_ja-JP.asset index ead8fda35..f95e06640 100644 --- a/Assets/Language/Params/Params_ja-JP.asset +++ b/Assets/Language/Params/Params_ja-JP.asset @@ -70,10 +70,6 @@ MonoBehaviour: m_Localized: m_Metadata: m_Items: [] - - m_Id: 200000000000001007 - m_Localized: - m_Metadata: - m_Items: [] - m_Id: 200000000000001008 m_Localized: m_Metadata: @@ -158,6 +154,10 @@ MonoBehaviour: m_Localized: m_Metadata: m_Items: [] + - m_Id: 200000000000001029 + m_Localized: + m_Metadata: + m_Items: [] references: version: 2 RefIds: [] diff --git a/Assets/Language/Params/Params_pt-BR.asset b/Assets/Language/Params/Params_pt-BR.asset index c0628fea0..d2960d65b 100644 --- a/Assets/Language/Params/Params_pt-BR.asset +++ b/Assets/Language/Params/Params_pt-BR.asset @@ -17,7 +17,11 @@ MonoBehaviour: m_SharedData: {fileID: 11400000, guid: 4a2d391471bd9cd439150c52659e9846, type: 2} m_Metadata: m_Items: [] - m_TableData: [] + m_TableData: + - m_Id: 200000000000001029 + m_Localized: + m_Metadata: + m_Items: [] references: version: 2 RefIds: [] diff --git a/Assets/Language/Params/Params_ru.asset b/Assets/Language/Params/Params_ru.asset index aa0e7a050..815b39a08 100644 --- a/Assets/Language/Params/Params_ru.asset +++ b/Assets/Language/Params/Params_ru.asset @@ -17,7 +17,11 @@ MonoBehaviour: m_SharedData: {fileID: 11400000, guid: 4a2d391471bd9cd439150c52659e9846, type: 2} m_Metadata: m_Items: [] - m_TableData: [] + m_TableData: + - m_Id: 200000000000001029 + m_Localized: + m_Metadata: + m_Items: [] references: version: 2 RefIds: [] diff --git a/Assets/Language/Params/Params_zh-Hans.asset b/Assets/Language/Params/Params_zh-Hans.asset index 6f3544b82..7b31cbd11 100644 --- a/Assets/Language/Params/Params_zh-Hans.asset +++ b/Assets/Language/Params/Params_zh-Hans.asset @@ -70,10 +70,6 @@ MonoBehaviour: 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: @@ -158,6 +154,10 @@ MonoBehaviour: m_Localized: "\u6211\u60F3\u8D62\u4E00\u6B21" m_Metadata: m_Items: [] + - m_Id: 200000000000001029 + m_Localized: 的一是在不了有和人这中大为上个国我以要他时来用们生到作地于出就分对成会可主发年动同工也能下过子说产种面而方后多定行学法所民得经十三之进着等部度家电力里如水化高自二理起小物现实加量都两体制机当使点从业本去把性好应开它合还因由其些然前外天政四日那社义事平形相全表间样与关各重新线内数正心反你明看原又么利比或但质气第向道命此变条只没结解问意建月公无系军很情者最立代想已通并提直题党程展五果料象员革位入常文总次品式活设及管特件长求老头基资边流路级少图山统接知较将组见计别她手角期根论运农指几九区强放决西被干做必战先回则任取据处队南给色光门即保治北造百规热领七海口东导器压志世金增争济阶油思术极交受联什认六共权收证改清己美再采转更单风切打白教速花带安场身车例真务具万每目至达走积示议声报斗完类八离华名确才科张信马节话米整空元况今集温传土许步群广石记需段研界拉林律叫且究观越织装影算低持音众书布复容儿须际商非验连断深难近矿千周委素技备半办青省列习响约支般史感劳便团往酸历市克何除消构府称太准精值号率族维划选标写存候毛亲快效斯院查江型眼王按格养易置派层片始却专状育厂京识适属圆包火住调满县局照参红细引听该铁价严龙飞 + m_Metadata: + m_Items: [] references: version: 2 RefIds: [] diff --git a/Assets/Language/UIText/UIText Shared Data.asset b/Assets/Language/UIText/UIText Shared Data.asset index d92107e9f..ada202421 100644 --- a/Assets/Language/UIText/UIText Shared Data.asset +++ b/Assets/Language/UIText/UIText Shared Data.asset @@ -19,10 +19,6 @@ MonoBehaviour: m_Key: start_newGame m_Metadata: m_Items: [] - - m_Id: 22296654778368 - m_Key: start_title - m_Metadata: - m_Items: [] - m_Id: 22296671555584 m_Key: start_selcetLevel m_Metadata: @@ -31,38 +27,10 @@ MonoBehaviour: m_Key: start_quit m_Metadata: m_Items: [] - - m_Id: 22296675749889 - m_Key: start_survey - m_Metadata: - m_Items: [] - - m_Id: 22296675749890 - m_Key: community - m_Metadata: - m_Items: [] - - m_Id: 22296675749891 - m_Key: record_title - m_Metadata: - m_Items: [] - m_Id: 26730768109568 m_Key: record_continue m_Metadata: m_Items: [] - - m_Id: 22296675749892 - m_Key: main_setting - m_Metadata: - m_Items: [] - - m_Id: 22296675749893 - m_Key: main_record - m_Metadata: - m_Items: [] - - m_Id: 22296675749894 - m_Key: main_auto - m_Metadata: - m_Items: [] - - m_Id: 22296675749895 - m_Key: main_fast - m_Metadata: - m_Items: [] - m_Id: 22296675749896 m_Key: setting_title m_Metadata: @@ -119,10 +87,6 @@ MonoBehaviour: m_Key: setting_textSpeed_fast m_Metadata: m_Items: [] - - m_Id: 752706112196608 - m_Key: save_warning - m_Metadata: - m_Items: [] - m_Id: 753317281009664 m_Key: save_return m_Metadata: diff --git a/Assets/Language/UIText/UIText.asset b/Assets/Language/UIText/UIText.asset index 0f692344b..c0a5be18b 100644 --- a/Assets/Language/UIText/UIText.asset +++ b/Assets/Language/UIText/UIText.asset @@ -17,6 +17,9 @@ MonoBehaviour: - {fileID: 11400000, guid: f3e7269fc46911b4dbe7ea36aa77aa90, type: 2} - {fileID: 11400000, guid: e6ecffb64cf71b44bbc44ebf958029cc, type: 2} - {fileID: 11400000, guid: 7c757424270c91e43bf45198f69c983a, type: 2} + - {fileID: 11400000, guid: 7d496f91e240bb143b62a6239c817d09, type: 2} + - {fileID: 11400000, guid: f679069e455200841a92c27186383940, type: 2} + - {fileID: 11400000, guid: 380c27123212d66468aa1f3ed3bf72ea, type: 2} m_Extensions: [] m_Group: String Table references: diff --git a/Assets/Language/UIText/UIText_en.asset b/Assets/Language/UIText/UIText_en.asset index 647465939..620119da2 100644 --- a/Assets/Language/UIText/UIText_en.asset +++ b/Assets/Language/UIText/UIText_en.asset @@ -16,18 +16,12 @@ MonoBehaviour: m_Code: en m_SharedData: {fileID: 11400000, guid: 994c69bf4b2403043bc9562f2d24ee56, type: 2} m_Metadata: - m_Items: - - rid: 7917695930343620616 + m_Items: [] m_TableData: - m_Id: 3350325104640 m_Localized: New Game m_Metadata: m_Items: [] - - m_Id: 22296654778368 - m_Localized: All Our\nBroken Parts - m_Metadata: - m_Items: - - rid: 7917695930343620616 - m_Id: 22296671555584 m_Localized: Chapter Select m_Metadata: @@ -40,34 +34,6 @@ MonoBehaviour: m_Localized: Exit m_Metadata: m_Items: [] - - m_Id: 22296675749889 - m_Localized: Questionnaire - m_Metadata: - m_Items: [] - - m_Id: 22296675749890 - m_Localized: Discord - m_Metadata: - m_Items: [] - - m_Id: 22296675749891 - m_Localized: Medical Records - m_Metadata: - m_Items: [] - - m_Id: 22296675749892 - m_Localized: Settings - m_Metadata: - m_Items: [] - - m_Id: 22296675749893 - m_Localized: Log - m_Metadata: - m_Items: [] - - m_Id: 22296675749894 - m_Localized: Auto - m_Metadata: - m_Items: [] - - m_Id: 22296675749895 - m_Localized: Fast Forward - m_Metadata: - m_Items: [] - m_Id: 22296675749896 m_Localized: m_Metadata: @@ -128,19 +94,6 @@ MonoBehaviour: m_Localized: Fast m_Metadata: m_Items: [] - - m_Id: 752706112196608 - m_Localized: 'This is a temporary feature prepared for testing purposes. - - - If - this is your first playthrough, we recommend going back and clicking Start - Game to begin. - - - If you wish to select a chapter, please restart the - game before using this feature, as bugs may otherwise occur.' - m_Metadata: - m_Items: [] - m_Id: 753317281009664 m_Localized: Back m_Metadata: @@ -258,25 +211,21 @@ MonoBehaviour: m_Metadata: m_Items: [] - m_Id: 200000000000000001 - m_Localized: + m_Localized: m_Metadata: m_Items: [] - m_Id: 200000000000000002 - m_Localized: + m_Localized: m_Metadata: m_Items: [] - m_Id: 200000000000000003 - m_Localized: + m_Localized: m_Metadata: m_Items: [] - m_Id: 200000000000000004 - m_Localized: + m_Localized: m_Metadata: m_Items: [] references: version: 2 - RefIds: - - rid: 7917695930343620616 - type: {class: SmartFormatTag, ns: UnityEngine.Localization.Metadata, asm: Unity.Localization} - data: - m_Entries: 0010305847140000 + RefIds: [] diff --git a/Assets/Language/UIText/UIText_ja-JP.asset b/Assets/Language/UIText/UIText_ja-JP.asset index 70b897290..6e40791e4 100644 --- a/Assets/Language/UIText/UIText_ja-JP.asset +++ b/Assets/Language/UIText/UIText_ja-JP.asset @@ -22,10 +22,6 @@ MonoBehaviour: m_Localized: "\u30CB\u30E5\u30FC\u30B2\u30FC\u30E0" m_Metadata: m_Items: [] - - m_Id: 22296654778368 - m_Localized: "\u611B\u3068\u30ED\u30DC\u30C3\u30C8\n\u4FEE\u7406\u6280\u8853" - m_Metadata: - m_Items: [] - m_Id: 22296671555584 m_Localized: "\u30C1\u30E3\u30D7\u30BF\u30FC\u9078\u629E" m_Metadata: @@ -38,38 +34,10 @@ MonoBehaviour: m_Localized: "\u7D42\u4E86" m_Metadata: m_Items: [] - - m_Id: 22296675749889 - m_Localized: "\u30A2\u30F3\u30B1\u30FC\u30C8" - m_Metadata: - m_Items: [] - - m_Id: 22296675749890 - m_Localized: Discord - m_Metadata: - m_Items: [] - - m_Id: 22296675749891 - m_Localized: "\u30AB\u30EB\u30C6" - m_Metadata: - m_Items: [] - m_Id: 26730768109568 m_Localized: "\u623B\u308B" m_Metadata: m_Items: [] - - m_Id: 22296675749892 - m_Localized: "\u8A2D\u5B9A" - m_Metadata: - m_Items: [] - - m_Id: 22296675749893 - m_Localized: "\u30D0\u30C3\u30AF\u30ED\u30B0" - m_Metadata: - m_Items: [] - - m_Id: 22296675749894 - m_Localized: "\u81EA\u52D5" - m_Metadata: - m_Items: [] - - m_Id: 22296675749895 - m_Localized: "\u65E9\u9001\u308A" - m_Metadata: - m_Items: [] - m_Id: 22296675749896 m_Localized: "<\u8A2D\u5B9A>" m_Metadata: @@ -126,10 +94,6 @@ MonoBehaviour: m_Localized: "\u9AD8\u901F" m_Metadata: m_Items: [] - - m_Id: 752706112196608 - m_Localized: "\u3053\u308C\u306F\u30C6\u30B9\u30C8\u7528\u306E\u4E00\u6642\u7684\u306A\u6A5F\u80FD\u3067\u3059\n\n\u521D\u3081\u3066\u30D7\u30EC\u30A4\u3059\u308B\u5834\u5408\u306F\u3001\u623B\u3063\u3066\u300C\u30B2\u30FC\u30E0\u30B9\u30BF\u30FC\u30C8\u300D\u304B\u3089\u59CB\u3081\u308B\u3053\u3068\u3092\u304A\u52E7\u3081\u3057\u307E\u3059\n\n\u30C1\u30E3\u30D7\u30BF\u30FC\u9078\u629E\u3092\u4F7F\u7528\u3057\u305F\u3044\u65B9\u306F\u3001\u30B2\u30FC\u30E0\u3092\u518D\u8D77\u52D5\u3057\u3066\u304B\u3089\u3054\u5229\u7528\u304F\u3060\u3055\u3044\u3002\u30D0\u30B0\u304C\u767A\u751F\u3059\u308B\u6050\u308C\u304C\u3042\u308A\u307E\u3059" - m_Metadata: - m_Items: [] - m_Id: 753317281009664 m_Localized: "\u623B\u308B" m_Metadata: @@ -247,19 +211,19 @@ MonoBehaviour: m_Metadata: m_Items: [] - m_Id: 200000000000000001 - m_Localized: + m_Localized: m_Metadata: m_Items: [] - m_Id: 200000000000000002 - m_Localized: + m_Localized: m_Metadata: m_Items: [] - m_Id: 200000000000000003 - m_Localized: + m_Localized: m_Metadata: m_Items: [] - m_Id: 200000000000000004 - m_Localized: + m_Localized: m_Metadata: m_Items: [] references: diff --git a/Assets/Language/UIText/UIText_zh-Hans.asset b/Assets/Language/UIText/UIText_zh-Hans.asset index c606c3421..2a28371a4 100644 --- a/Assets/Language/UIText/UIText_zh-Hans.asset +++ b/Assets/Language/UIText/UIText_zh-Hans.asset @@ -16,18 +16,12 @@ MonoBehaviour: m_Code: zh-Hans m_SharedData: {fileID: 11400000, guid: 994c69bf4b2403043bc9562f2d24ee56, type: 2} m_Metadata: - m_Items: - - rid: 7917695939544875419 + m_Items: [] m_TableData: - m_Id: 3350325104640 m_Localized: "\u65B0\u6E38\u620F" m_Metadata: m_Items: [] - - m_Id: 22296654778368 - m_Localized: "\u7231\u4E0E\u673A\u5668\u4EBA\\n\u7EF4\u4FEE\u6280\u672F" - m_Metadata: - m_Items: - - rid: 7917695939544875419 - m_Id: 22296671555584 m_Localized: "\u9009\u62E9\u7AE0\u8282" m_Metadata: @@ -40,38 +34,10 @@ MonoBehaviour: m_Localized: "\u9000\u51FA" m_Metadata: m_Items: [] - - m_Id: 22296675749889 - m_Localized: "\u95EE\u5377" - m_Metadata: - m_Items: [] - - m_Id: 22296675749890 - m_Localized: 325268983 - m_Metadata: - m_Items: [] - - m_Id: 22296675749891 - m_Localized: "\u8BCA\u7597\u8BB0\u5F55" - m_Metadata: - m_Items: [] - m_Id: 26730768109568 m_Localized: "\u8FD4\u56DE" m_Metadata: m_Items: [] - - m_Id: 22296675749892 - m_Localized: "\u8BBE\u7F6E" - m_Metadata: - m_Items: [] - - m_Id: 22296675749893 - m_Localized: "\u8BB0\u5F55" - m_Metadata: - m_Items: [] - - m_Id: 22296675749894 - m_Localized: "\u81EA\u52A8" - m_Metadata: - m_Items: [] - - m_Id: 22296675749895 - m_Localized: "\u5FEB\u8FDB" - m_Metadata: - m_Items: [] - m_Id: 22296675749896 m_Localized: "<\u8BBE\u7F6E>" m_Metadata: @@ -128,12 +94,6 @@ MonoBehaviour: m_Localized: "\u5FEB\u901F" m_Metadata: m_Items: [] - - m_Id: 752706112196608 - m_Localized: "\u8FD9\u662F\u4E00\u4E2A\u4E3A\u4FBF\u4E8E\u6D4B\u8BD5\u51C6\u5907\u7684\u4E34\u65F6\u529F\u80FD\n - \n \u5982\u679C\u9996\u6B21\u6E38\u73A9\uFF0C\u63A8\u8350\u60A8\u8FD4\u56DE\u5E76\u70B9\u51FB\u5F00\u59CB\u6E38\u620F\u76F4\u63A5\u5F00\u59CB\n - \n \u63A8\u8350\u60F3\u8981\u9009\u5173\u7684\u73A9\u5BB6\u91CD\u542F\u6E38\u620F\u540E\u518D\u4F7F\u7528\u672C\u529F\u80FD,\u5426\u5219\u53EF\u80FD\u51FA\u73B0bug" - m_Metadata: - m_Items: [] - m_Id: 753317281009664 m_Localized: "\u8FD4\u56DE" m_Metadata: @@ -268,8 +228,4 @@ MonoBehaviour: m_Items: [] references: version: 2 - RefIds: - - rid: 7917695939544875419 - type: {class: SmartFormatTag, ns: UnityEngine.Localization.Metadata, asm: Unity.Localization} - data: - m_Entries: 0010305847140000 + RefIds: [] diff --git a/Assets/Prefabs/LanguageParticleSystem.prefab b/Assets/Prefabs/LanguageParticleSystem.prefab index c2f20a167..09dc87aa2 100644 --- a/Assets/Prefabs/LanguageParticleSystem.prefab +++ b/Assets/Prefabs/LanguageParticleSystem.prefab @@ -173,8 +173,6 @@ MonoBehaviour: redParticleCount: 8 connectionDistance: 2 textMargin: 1 - anxietyPhrases: [] - targetSentence: "\u522B\u8FC7\u6765\u6211\u611F\u89C9\u5BB3\u6015" completionDialogNode: waveformAmplitude: 0.3 interactionRadius: 0.5 diff --git a/Assets/Scenes/HuoShanFixScene.unity b/Assets/Scenes/HuoShanFixScene.unity index 0f71c9d1c..c500f550b 100644 --- a/Assets/Scenes/HuoShanFixScene.unity +++ b/Assets/Scenes/HuoShanFixScene.unity @@ -16128,13 +16128,6 @@ MonoBehaviour: logReleasePresentation: {fileID: 2138889543} screenPresentation: {fileID: 2138889544} contentCatalog: {fileID: 11400000, guid: b2d94952a18d4d178421691ab103f0fd, type: 2} - defaultAnxietyPhrases: - - "\u6211\u597D\u6015" - - "\u600E\u4E48\u529E" - - "\u4E0D\u884C" - - "\u597D\u96BE" - - "\u505A\u4E0D\u5230" - defaultTargetSentence: "\u8FD9\u662F\u4E00\u4E2A\u6D4B\u8BD5\u793A\u4F8B" screenOverlayRenderer: {fileID: 2063129674} volcanoOverlayRenderer: {fileID: 1944146357} spriteOverlayFadeInAlpha: 1 @@ -16218,7 +16211,6 @@ MonoBehaviour: memoryExitDuration: 0.45 faceDipRatio: 0.015 lieColor: {r: 0.66, g: 0.94, b: 1, a: 1} - errorColor: {r: 1, g: 0.34, b: 0.18, a: 1} truthWarmColor: {r: 1, g: 1, b: 1, a: 1} truthAccentColor: {r: 1, g: 0.84, b: 0.6, a: 1} truthBackdropColor: {r: 0.3, g: 0.2, b: 0.1, a: 0.78} @@ -18471,8 +18463,6 @@ MonoBehaviour: redParticleCount: 8 connectionDistance: 1.8 textMargin: 0.45 - anxietyPhrases: [] - targetSentence: "\u522B\u8FC7\u6765\u6211\u611F\u89C9\u5BB3\u6015" completionDialogNode: waveformAmplitude: 0.3 interactionRadius: 0.5 @@ -18576,7 +18566,7 @@ MonoBehaviour: m_OnCullStateChanged: m_PersistentCalls: m_Calls: [] - m_text: "\u8F93\u51FA" + m_text: m_isRightToLeft: 0 m_fontAsset: {fileID: 11400000, guid: f242fb3dde1933640859f1c54591c9c8, type: 2} m_sharedMaterial: {fileID: -6659093157667844055, guid: f242fb3dde1933640859f1c54591c9c8, type: 2} diff --git a/Assets/Scripts/AssetRefs/ConstRef.cs b/Assets/Scripts/AssetRefs/ConstRef.cs index e5f75494e..1031b6bd9 100644 --- a/Assets/Scripts/AssetRefs/ConstRef.cs +++ b/Assets/Scripts/AssetRefs/ConstRef.cs @@ -53,6 +53,7 @@ namespace AibisDream.Utility public const string UITextTable = "UIText"; public const string ChapterInfoTable = "ChapterInfo"; public const string ParamsTable = "Params"; + public const string ExpressParticlePoolParam = "l10n.hs.exp.pool"; #endregion diff --git a/Assets/Scripts/Framework/OtherKit/LocalizationKit.cs b/Assets/Scripts/Framework/OtherKit/LocalizationKit.cs index 789e3e46a..4c65938f8 100644 --- a/Assets/Scripts/Framework/OtherKit/LocalizationKit.cs +++ b/Assets/Scripts/Framework/OtherKit/LocalizationKit.cs @@ -210,6 +210,22 @@ namespace AibisDream.Framework return IsLocalizedParam(key) ? key[LocalizationPrefix.Length..] : key; } + /// + /// 判断解析结果是否是当前 l10n. 引用对应的缺失标记。 + /// 非本地化原始文本即使使用相同括号格式也不会被误判。 + /// + public static bool IsMissingParamResult(string source, string localizedValue) + { + if (!IsLocalizedParam(source)) + return false; + + string key = GetL10NParamKey(source); + string marker = string.IsNullOrEmpty(key) + ? "⟦empty⟧" + : $"⟦{key}⟧"; + return string.Equals(localizedValue, marker, StringComparison.Ordinal); + } + /// /// 确保本地化系统已初始化 /// diff --git a/Assets/Scripts/MiniGame/HuoShan/Language/CandidateParticle.cs b/Assets/Scripts/MiniGame/HuoShan/Language/CandidateParticle.cs index ad6a6aaa2..e5f3d6f7e 100644 --- a/Assets/Scripts/MiniGame/HuoShan/Language/CandidateParticle.cs +++ b/Assets/Scripts/MiniGame/HuoShan/Language/CandidateParticle.cs @@ -230,7 +230,7 @@ namespace AibisDream.MiniGame.Language } /// - /// 目标粒子:chineseChars 随机单字;非目标粒子:从笑话/干扰短句字符中随机。 + /// 目标粒子:从本轮本地化默认池随机;非目标粒子:从笑话/干扰短句字符中随机。 /// 使用 IsTarget 判断,与红蓝颜色解绑。 /// protected override string GetNextDisplayChar() @@ -264,7 +264,7 @@ namespace AibisDream.MiniGame.Language private void UpdateFocusInterferenceCharacters() { if (focusInterferenceProgress <= 0f || - string.IsNullOrEmpty(overrideCharPool) || + !HasOverrideCharacterPool || (!isStatic && !isCalmed)) { return; diff --git a/Assets/Scripts/MiniGame/HuoShan/Language/ExpressionContentCatalog.cs b/Assets/Scripts/MiniGame/HuoShan/Language/ExpressionContentCatalog.cs index aa6f9f8d5..61afbce82 100644 --- a/Assets/Scripts/MiniGame/HuoShan/Language/ExpressionContentCatalog.cs +++ b/Assets/Scripts/MiniGame/HuoShan/Language/ExpressionContentCatalog.cs @@ -54,12 +54,10 @@ namespace AibisDream.MiniGame.Language menuName = AibisAssetMenus.HuoShanExpressionContent)] public sealed class ExpressionContentCatalog : ScriptableObject { - [SerializeField] private string actorLoopReference; [SerializeField] private List rounds = new(); private Dictionary roundById; - public string ActorLoopReference => actorLoopReference; public IReadOnlyList Rounds => rounds; public bool TryGetRound(string roundId, out ExpressionRoundDefinition definition) @@ -77,9 +75,6 @@ namespace AibisDream.MiniGame.Language public List GetValidationErrors() { var errors = new List(); - if (!LocalizationKit.IsLocalizedParam(actorLoopReference)) - errors.Add("Actor Loop 必须配置为 l10n.* 引用。"); - var ids = new HashSet(StringComparer.Ordinal); if (rounds == null || rounds.Count == 0) { diff --git a/Assets/Scripts/MiniGame/HuoShan/Language/ExpressionManager.cs b/Assets/Scripts/MiniGame/HuoShan/Language/ExpressionManager.cs index 8a3322ce0..4951855a6 100644 --- a/Assets/Scripts/MiniGame/HuoShan/Language/ExpressionManager.cs +++ b/Assets/Scripts/MiniGame/HuoShan/Language/ExpressionManager.cs @@ -20,15 +20,6 @@ namespace AibisDream [SerializeField] private ExpressionScreenPresentationController screenPresentation; [SerializeField] private ExpressionContentCatalog contentCatalog; - [Header("默认配置")] - [SerializeField] private List defaultAnxietyPhrases = new List - { - "哈哈", - "嘿嘿", - "呵呵" - }; - [SerializeField] private string defaultTargetSentence = "这是一个测试示例"; - [Header("Sprite 淡入淡出")] [SerializeField] private SpriteRenderer screenOverlayRenderer; [SerializeField] private SpriteRenderer volcanoOverlayRenderer; @@ -162,20 +153,37 @@ namespace AibisDream /// /// 干扰短句列表(笑话等,非目标粒子从中随机取字符) /// 目标句子 + /// 当前 Locale 的默认随机字符池 /// 完成时触发的对话节点名称(可选) /// 非目标候选粒子总数;<0 时使用 Inspector 默认候选池大小 - public void StartSystem(List anxietyPhrases, string targetSentence, string completionNodeName = null, int nonTargetParticleTotal = -1) + public void StartSystem( + List anxietyPhrases, + string targetSentence, + IReadOnlyList defaultCharacterPool, + string completionNodeName = null, + int nonTargetParticleTotal = -1) { HideSpriteOverlaysForMinigame(); if (particleManager != null) { particleManager.SetPresentationController(logReleasePresentation); - // 使用传入的配置,如果没有则使用默认配置 - var phrases = anxietyPhrases ?? defaultAnxietyPhrases; - var sentence = targetSentence ?? defaultTargetSentence; - - particleManager.InitializeSystem(phrases, sentence, completionNodeName, nonTargetParticleTotal); + if (anxietyPhrases == null || anxietyPhrases.Count == 0 || + string.IsNullOrWhiteSpace(targetSentence) || + defaultCharacterPool == null || + defaultCharacterPool.Count == 0) + { + Debug.LogError( + "[ExpressionManager] 启动参数缺少目标文字、干扰 token 或默认字符池;系统未启动。"); + return; + } + + particleManager.InitializeSystem( + anxietyPhrases, + targetSentence, + defaultCharacterPool, + completionNodeName, + nonTargetParticleTotal); } } @@ -211,10 +219,20 @@ namespace AibisDream /// /// 启动粒子系统(字符串数组版本,方便 Yarn 调用) /// - public void StartSystem(string[] anxietyPhrases, string targetSentence, string completionNodeName = null, int nonTargetParticleTotal = -1) + public void StartSystem( + string[] anxietyPhrases, + string targetSentence, + IReadOnlyList defaultCharacterPool, + string completionNodeName = null, + int nonTargetParticleTotal = -1) { List phrasesList = anxietyPhrases != null ? new List(anxietyPhrases) : null; - StartSystem(phrasesList, targetSentence, completionNodeName, nonTargetParticleTotal); + StartSystem( + phrasesList, + targetSentence, + defaultCharacterPool, + completionNodeName, + nonTargetParticleTotal); } /// @@ -921,28 +939,6 @@ namespace AibisDream targetOpacity); } - public IEnumerator BeginExpressionLie(string keyword, string preset) - { - if (logReleasePresentation == null) - { - Debug.LogError("[ExpressionManager] LogReleasePresentationController 未配置;谎话命令安全结束。"); - yield break; - } - yield return logReleasePresentation.BeginLie(keyword, preset); - } - - public IEnumerator BreakExpressionLie() - { - if (logReleasePresentation == null) yield break; - yield return logReleasePresentation.BreakLie(); - } - - public IEnumerator RevealExpressionTruth() - { - if (logReleasePresentation == null) yield break; - yield return logReleasePresentation.RevealTruth(); - } - public IEnumerator EndExpressionMemory() { if (logReleasePresentation == null) yield break; diff --git a/Assets/Scripts/MiniGame/HuoShan/Language/ExpressionTextTokenizer.cs b/Assets/Scripts/MiniGame/HuoShan/Language/ExpressionTextTokenizer.cs index e01958d45..a0712b059 100644 --- a/Assets/Scripts/MiniGame/HuoShan/Language/ExpressionTextTokenizer.cs +++ b/Assets/Scripts/MiniGame/HuoShan/Language/ExpressionTextTokenizer.cs @@ -4,8 +4,8 @@ using System.Globalization; namespace AibisDream.MiniGame.Language { /// - /// 将玩家可见文字按 Unicode 文本元素拆分。空白保留在布局中,但不生成粒子。 - /// 随机粒子字符池不使用本类,维持现有行为。 + /// 将玩家可见文字按 Unicode 文本元素拆分。 + /// 空白保留在布局中但不生成粒子;随机字符池直接忽略空白。 /// public static class ExpressionTextTokenizer { @@ -39,6 +39,17 @@ namespace AibisDream.MiniGame.Language return result; } + public static List BuildCharacterPool(IEnumerable texts) + { + var result = new List(); + if (texts == null) + return result; + + foreach (string text in texts) + result.AddRange(GetVisibleElements(text)); + return result; + } + public static Layout BuildLayout( string text, float characterSpacing, diff --git a/Assets/Scripts/MiniGame/HuoShan/Language/LanguageParticleManager.cs b/Assets/Scripts/MiniGame/HuoShan/Language/LanguageParticleManager.cs index b3d8ed0f0..475100b1b 100644 --- a/Assets/Scripts/MiniGame/HuoShan/Language/LanguageParticleManager.cs +++ b/Assets/Scripts/MiniGame/HuoShan/Language/LanguageParticleManager.cs @@ -64,8 +64,10 @@ namespace AibisDream.MiniGame.Language [SerializeField] private float textMargin = 1f; [Header("游戏配置")] - [SerializeField] private List anxietyPhrases = new List(); // 干扰短句(笑话等,用于非目标粒子,Yarn 会覆盖) - [SerializeField] private string targetSentence = "这是一个测试示例"; // 目标句子 + private List anxietyPhrases = new List(); + private string targetSentence = string.Empty; + private IReadOnlyList defaultCharacterPool = new List().AsReadOnly(); + private IReadOnlyList interferenceCharacterPool = new List().AsReadOnly(); [SerializeField] private string completionDialogNode = ""; // 完成时触发的对话节点 [Header("波形参数")] @@ -346,15 +348,39 @@ namespace AibisDream.MiniGame.Language /// /// 焦虑短句列表 /// 目标句子 + /// 当前 Locale 的默认随机字符池 /// 完成时触发的对话节点 /// /// 非目标候选粒子总数(蓝/干扰侧可交互粒子数量)。≥0 时候选池大小 = 目标句字数 + 该值;<0 时使用 Inspector 的 Candidate Count。 /// - public void InitializeSystem(List phrases, string sentence, string dialogNode = null, int nonTargetParticleTotal = -1) + public void InitializeSystem( + List phrases, + string sentence, + IReadOnlyList localizedDefaultPool, + string dialogNode = null, + int nonTargetParticleTotal = -1) { CaptureDefaultCandidateCountFromInspector(); - string useSentence = string.IsNullOrEmpty(sentence) ? "这是一个测试示例" : sentence; + if (phrases == null || phrases.Count == 0 || string.IsNullOrWhiteSpace(sentence)) + { + Debug.LogError( + "[LanguageParticleManager] 目标文字或干扰 token 为空;初始化已中止。"); + return; + } + + List resolvedDefaultPool = + ExpressionTextTokenizer.BuildCharacterPool(localizedDefaultPool); + List resolvedInterferencePool = + ExpressionTextTokenizer.BuildCharacterPool(phrases); + if (resolvedDefaultPool.Count == 0 || resolvedInterferencePool.Count == 0) + { + Debug.LogError( + "[LanguageParticleManager] 默认字符池或干扰字符池没有有效的 Unicode 文本元素;初始化已中止。"); + return; + } + + string useSentence = sentence; int visibleTargetCount = ExpressionTextTokenizer.GetVisibleElements(useSentence).Count; if (visibleTargetCount == 0) { @@ -373,6 +399,13 @@ namespace AibisDream.MiniGame.Language candidateCount = defaultCandidateCountFromInspector; } + // 在创建或复用粒子前先提交本轮不可变快照,防止首帧或上一轮字符泄漏。 + anxietyPhrases = new List(phrases); + targetSentence = useSentence; + completionDialogNode = dialogNode ?? ""; + defaultCharacterPool = resolvedDefaultPool.AsReadOnly(); + interferenceCharacterPool = resolvedInterferencePool.AsReadOnly(); + // 如果是第一次初始化,先执行基础初始化 if (!isInitialized) { @@ -407,16 +440,9 @@ namespace AibisDream.MiniGame.Language else { EnsureCandidatePoolSize(candidateCount); + ApplyCharacterPoolsToParticles(); } - // 设置游戏配置 - anxietyPhrases = phrases ?? new List(); - targetSentence = useSentence; - completionDialogNode = dialogNode ?? ""; - - // 设置全局焦虑短句配置 - TextParticle.SetAnxietyPhrases(anxietyPhrases); - // 根据目标句子更新目标粒子数量 redParticleCount = Mathf.Min(visibleTargetCount, candidateCount); @@ -700,6 +726,7 @@ namespace AibisDream.MiniGame.Language Vector3 pos = GetRandomPositionInBounds(); GameObject obj = CreateParticleObject(pos, $"FloatingParticle_{i}"); FloatingTextParticle particle = obj.AddComponent(); + particle.SetCharacterPools(defaultCharacterPool, interferenceCharacterPool); particle.SetFont(chineseFontAsset, chineseFontMaterial); particle.SetColor(floatingTextColor); particle.SetFontSize(floatingTextFontSize); @@ -744,6 +771,7 @@ namespace AibisDream.MiniGame.Language Vector3 pos = GetRandomPositionInBounds(); GameObject obj = CreateParticleObject(pos, $"CandidateParticle_{index}"); CandidateParticle particle = obj.AddComponent(); + particle.SetCharacterPools(defaultCharacterPool, interferenceCharacterPool); particle.SetFont(chineseFontAsset, chineseFontMaterial); particle.SetColors(targetParticleColor, nonTargetParticleColor, nonTargetParticleColor, targetCalmColor); particle.SetNonTargetMarquee( @@ -760,6 +788,22 @@ namespace AibisDream.MiniGame.Language candidateParticles.Add(particle); } + private void ApplyCharacterPoolsToParticles() + { + foreach (CandidateParticle particle in candidateParticles) + particle?.SetCharacterPools(defaultCharacterPool, interferenceCharacterPool); + foreach (FloatingTextParticle particle in floatingParticles) + particle?.SetCharacterPools(defaultCharacterPool, interferenceCharacterPool); + } + + private void RefreshAllRandomCharacters() + { + foreach (CandidateParticle particle in candidateParticles) + particle?.InitializeRandomCharacter(); + foreach (FloatingTextParticle particle in floatingParticles) + particle?.InitializeRandomCharacter(); + } + /// /// 增删候选粒子以匹配目标数量(用于 Yarn 动态指定非目标粒子总数后复用同一视图) /// @@ -2016,6 +2060,8 @@ namespace AibisDream.MiniGame.Language Debug.LogWarning("[LanguageParticleManager] 老虎机换字字符池为空;已安全跳过。"); return; } + IReadOnlyList poolElements = + ExpressionTextTokenizer.GetVisibleElements(pool).AsReadOnly(); float changeInterval = Mathf.Max(0.02f, interval); foreach (TargetFocusVisual visual in focusVisuals.Values) @@ -2023,7 +2069,7 @@ namespace AibisDream.MiniGame.Language if (visual?.particle == null) continue; - visual.particle.SetOverrideCharPool(pool); + visual.particle.SetOverrideCharacterPool(poolElements); visual.particle.SetChangeInterval(changeInterval); visual.particle.ResetChangeTimer(changeInterval); visual.particle.isStatic = false; @@ -2040,8 +2086,12 @@ namespace AibisDream.MiniGame.Language { if (string.IsNullOrEmpty(charPool)) return null; - string cleaned = charPool.Replace(" ", string.Empty).Replace("|", string.Empty); - return cleaned.Length == 0 ? null : cleaned; + string withoutSeparators = charPool + .Replace("|", string.Empty) + .Replace("|", string.Empty); + List elements = + ExpressionTextTokenizer.GetVisibleElements(withoutSeparators); + return elements.Count == 0 ? null : string.Concat(elements); } public void StopSlotMachineShuffle() @@ -2053,7 +2103,7 @@ namespace AibisDream.MiniGame.Language } foreach (CandidateParticle particle in targetParticles) - particle?.SetOverrideCharPool(null); + particle?.SetOverrideCharacterPool(null); } private IEnumerator EndSlotMachineShuffleAfter(float duration) @@ -2061,7 +2111,7 @@ namespace AibisDream.MiniGame.Language yield return new WaitForSeconds(duration); slotMachineRoutine = null; foreach (CandidateParticle particle in targetParticles) - particle?.SetOverrideCharPool(null); + particle?.SetOverrideCharacterPool(null); } public void PrepareTruthReleaseFromLie() @@ -3151,6 +3201,7 @@ namespace AibisDream.MiniGame.Language RebuildScreenClipMaterials(); SelectRedParticles(); + RefreshAllRandomCharacters(); UpdateStatusUI(); if (playEntranceEffect) { diff --git a/Assets/Scripts/MiniGame/HuoShan/Language/LanguageYarnCommand.cs b/Assets/Scripts/MiniGame/HuoShan/Language/LanguageYarnCommand.cs index 8487124fb..77f54b34a 100644 --- a/Assets/Scripts/MiniGame/HuoShan/Language/LanguageYarnCommand.cs +++ b/Assets/Scripts/MiniGame/HuoShan/Language/LanguageYarnCommand.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using AibisDream.FixSystem; using AibisDream; using AibisDream.Framework; +using AibisDream.Utility; using UnityEngine.Localization; using UnityEngine.Localization.Settings; using Yarn.Unity; @@ -38,9 +39,19 @@ namespace AibisDream.MiniGame.Language while (!all.IsCompleted) yield return null; - completed?.Invoke(all.Status == TaskStatus.RanToCompletion - ? all.Result - : System.Array.Empty()); + if (all.Status != TaskStatus.RanToCompletion) + { + completed?.Invoke(System.Array.Empty()); + yield break; + } + + string[] values = all.Result; + for (int i = 0; i < values.Length; i++) + { + if (LocalizationKit.IsMissingParamResult(references[i], values[i])) + values[i] = null; + } + completed?.Invoke(values); } private static bool ValidateRequiredText(string value, string command, string parameter) @@ -77,6 +88,22 @@ namespace AibisDream.MiniGame.Language return tokens.Count > 0; } + public static bool TryParseExpressionPool( + string value, + out List characters) + { + characters = new List(); + if (string.IsNullOrWhiteSpace(value) || + value.Contains('|') || + value.Contains('|')) + { + return false; + } + + characters = ExpressionTextTokenizer.GetVisibleElements(value); + return characters.Count > 0; + } + private static bool TryGetPresentation(out LogReleasePresentationController presentation) { presentation = ExpressionManager != null @@ -130,10 +157,12 @@ namespace AibisDream.MiniGame.Language locale, values => localized = values, anxietyPhrasesStr, - targetSentence); - if (localized == null || localized.Length != 2 || + targetSentence, + ConstRef.ExpressParticlePoolParam); + if (localized == null || localized.Length != 3 || !ValidateRequiredText(localized[0], "start_expression", nameof(anxietyPhrasesStr)) || - !ValidateRequiredText(localized[1], "start_expression", nameof(targetSentence))) + !ValidateRequiredText(localized[1], "start_expression", nameof(targetSentence)) || + !ValidateRequiredText(localized[2], "start_expression", "defaultCharacterPool")) yield break; if (!TryParseExpressionTokens(localized[0], out List phrases)) @@ -142,6 +171,12 @@ namespace AibisDream.MiniGame.Language "[LanguageYarnCommand] start_expression 没有有效的干扰 token;命令已安全结束。"); yield break; } + if (!TryParseExpressionPool(localized[2], out List defaultCharacterPool)) + { + UnityEngine.Debug.LogError( + "[LanguageYarnCommand] start_expression 的默认字符池无效;命令已安全结束。"); + yield break; + } // 先清理上一轮演出,再淡出旧文字/连线并恢复 Log 操作界面。 yield return ExpressionManager.PreparePresentationForNextRound(); @@ -151,6 +186,7 @@ namespace AibisDream.MiniGame.Language ExpressionManager.StartSystem( phrases, localized[1], + defaultCharacterPool, completionNode, nonTargetParticleTotal); yield return ExpressionManager.WaitUntilExpressionFlowReady(); @@ -199,10 +235,12 @@ namespace AibisDream.MiniGame.Language locale, values => localized = values, round.TokenReference, - round.TargetReference); - if (localized == null || localized.Length != 2 || + round.TargetReference, + ConstRef.ExpressParticlePoolParam); + if (localized == null || localized.Length != 3 || !ValidateRequiredText(localized[0], "start_expression_round", nameof(round.TokenReference)) || - !ValidateRequiredText(localized[1], "start_expression_round", nameof(round.TargetReference))) + !ValidateRequiredText(localized[1], "start_expression_round", nameof(round.TargetReference)) || + !ValidateRequiredText(localized[2], "start_expression_round", "defaultCharacterPool")) yield break; if (!TryParseExpressionTokens(localized[0], out List phrases)) @@ -211,6 +249,12 @@ namespace AibisDream.MiniGame.Language $"[LanguageYarnCommand] 表达轮次 '{roundId}' 没有有效的干扰 token。"); yield break; } + if (!TryParseExpressionPool(localized[2], out List defaultCharacterPool)) + { + UnityEngine.Debug.LogError( + $"[LanguageYarnCommand] 表达轮次 '{roundId}' 的默认字符池无效。"); + yield break; + } yield return ExpressionManager.PreparePresentationForNextRound(); yield return ExpressionManager.FadeOutParticlesBeforeNewRound(0f); @@ -219,6 +263,7 @@ namespace AibisDream.MiniGame.Language ExpressionManager.StartSystem( phrases, localized[1], + defaultCharacterPool, completionNode ?? string.Empty, round.NonTargetParticleCount); yield return ExpressionManager.WaitUntilExpressionFlowReady(); @@ -379,49 +424,6 @@ namespace AibisDream.MiniGame.Language presentation.BeginFaceEntry(duration)); } - [YarnCommand("expression_lie_begin")] - public static IEnumerator ExpressionLieBegin(string keyword, string preset) - { - if (ExpressionManager == null) - { - 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(localized[0], preset)); - } - - [YarnCommand("expression_lie_break")] - public static IEnumerator ExpressionLieBreak() - { - if (ExpressionManager == null) - { - UnityEngine.Debug.LogError("ExpressionManager 未找到!"); - yield break; - } - yield return ExpressionManager.StartCoroutine(ExpressionManager.BreakExpressionLie()); - } - - [YarnCommand("expression_truth_reveal")] - public static IEnumerator ExpressionTruthReveal() - { - if (ExpressionManager == null) - { - UnityEngine.Debug.LogError("ExpressionManager 未找到!"); - yield break; - } - yield return ExpressionManager.StartCoroutine(ExpressionManager.RevealExpressionTruth()); - } - // 以下命令是一动作一命令。非 IEnumerator 命令只启动效果,节拍由 Yarn 的 wait 控制。 [YarnCommand("expression_lie_prepare")] @@ -540,7 +542,7 @@ namespace AibisDream.MiniGame.Language /// /// 结尾真假快速闪切(非阻塞,节奏由同长度 wait 控制):真心话与谎话交替独占屏幕, /// 两者的屏幕底色(电视头背景)不同,越到后面切得越快,结束定格在真话帧。 - /// <> + /// <> /// [YarnCommand("expression_truth_lie_flicker")] public static IEnumerator ExpressionTruthLieFlicker( @@ -719,54 +721,6 @@ namespace AibisDream.MiniGame.Language true)); } - /// - /// 非阻塞启动“没问题 → 冇问题 → 帽问题”三拍无限闪现循环。 - /// <> - /// - [YarnCommand("expression_actor_flash_loop_start")] - public static IEnumerator ExpressionActorFlashLoopStart() - { - if (!TryGetPresentation(out LogReleasePresentationController presentation)) - yield break; - - ExpressionContentCatalog catalog = ExpressionManager.ContentCatalog; - if (catalog == null || !LocalizationKit.IsLocalizedParam(catalog.ActorLoopReference)) - { - UnityEngine.Debug.LogError( - "[LanguageYarnCommand] Actor Loop 本地化引用未配置;命令已安全结束。"); - yield break; - } - - string[] localized = null; - yield return ResolveTextReferences( - GetCommandLocale(), - values => localized = values, - catalog.ActorLoopReference); - if (localized == null || localized.Length != 1 || - !ValidateRequiredText(localized[0], "expression_actor_flash_loop_start", "ActorLoopReference")) - yield break; - - if (!TryParseExpressionTokens(localized[0], out List phrases)) - { - UnityEngine.Debug.LogError( - "[LanguageYarnCommand] Actor Loop 短句列表无效;命令已安全结束。"); - yield break; - } - - presentation.StartActorFlashLoop(phrases); - } - - /// - /// 立即停止无限闪现循环、清除当前大字,并恢复晃动粒子字。 - /// <> - /// - [YarnCommand("expression_actor_flash_loop_stop")] - public static void ExpressionActorFlashLoopStop() - { - if (TryGetPresentation(out LogReleasePresentationController presentation)) - presentation.StopActorFlashLoop(); - } - /// /// 真话闪现(阻塞):谎话大字之间,暖色完整真话带细微抖动挣扎浮现一拍; /// "被掐断"由紧随其后的 expression_glitch_pulse 表现。不推进谎话冲击等级。 @@ -799,7 +753,7 @@ namespace AibisDream.MiniGame.Language /// /// 老虎机换字(非阻塞,节拍由 Yarn wait 控制):聚焦字在 duration 内只从 charPool 里疯狂换字。 /// truthPool 非空时其字符也混入换字池,并以真话暖色显示。 - /// <> + /// <> /// [YarnCommand("expression_actor_slot")] public static IEnumerator ExpressionActorSlot( @@ -882,7 +836,7 @@ namespace AibisDream.MiniGame.Language /// /// 单列词组严格裁在表情屏内,像字幕一样无缝向下滚动。 - /// <> + /// <> /// [YarnCommand("expression_actor_scroll")] public static IEnumerator ExpressionActorScroll( diff --git a/Assets/Scripts/MiniGame/HuoShan/Language/LogReleasePresentationController.cs b/Assets/Scripts/MiniGame/HuoShan/Language/LogReleasePresentationController.cs index 623578141..73ef9e9bb 100644 --- a/Assets/Scripts/MiniGame/HuoShan/Language/LogReleasePresentationController.cs +++ b/Assets/Scripts/MiniGame/HuoShan/Language/LogReleasePresentationController.cs @@ -38,34 +38,19 @@ namespace AibisDream.MiniGame.Language { public readonly string Name; public readonly float BaseDistortion; - public readonly float LiePause; - public readonly float BreakPeak; - public readonly float BreakDuration; - public readonly float TruthDuration; - public readonly float TruthPeripheral; public Preset( string name, - float baseDistortion, - float liePause, - float breakPeak, - float breakDuration, - float truthDuration, - float truthPeripheral) + float baseDistortion) { Name = name; BaseDistortion = baseDistortion; - LiePause = liePause; - BreakPeak = breakPeak; - BreakDuration = breakDuration; - TruthDuration = truthDuration; - TruthPeripheral = truthPeripheral; } } - private static readonly Preset LightPreset = new Preset("Light", 0.10f, 0.35f, 0.25f, 0.25f, 0.70f, 0.15f); - private static readonly Preset MediumPreset = new Preset("Medium", 0.20f, 0.55f, 0.50f, 0.35f, 0.90f, 0.30f); - private static readonly Preset HeavyPreset = new Preset("Heavy", 0.35f, 0.80f, 0.85f, 0.50f, 1.10f, 0.50f); + private static readonly Preset LightPreset = new Preset("Light", 0.10f); + private static readonly Preset MediumPreset = new Preset("Medium", 0.20f); + private static readonly Preset HeavyPreset = new Preset("Heavy", 0.35f); [Header("Scene References")] [SerializeField] private LanguageParticleManager particleManager; @@ -96,7 +81,6 @@ namespace AibisDream.MiniGame.Language [Header("Screen Styling")] [SerializeField] private Color lieColor = new Color(0.66f, 0.94f, 1f, 1f); - [SerializeField] private Color errorColor = new Color(1f, 0.34f, 0.18f, 1f); [SerializeField] private Color truthWarmColor = new Color(1f, 1f, 1f, 1f); [Tooltip("爆发段真话闪现与老虎机真话字符的暖色;需与谎话的冷白明显区分。")] [SerializeField] private Color truthAccentColor = new Color(1f, 0.84f, 0.6f, 1f); @@ -118,7 +102,6 @@ namespace AibisDream.MiniGame.Language private Preset currentPreset = LightPreset; private MemoryKind currentMemoryKind = MemoryKind.Office; private string currentLieKeyword = string.Empty; - private string currentLeakFragment = string.Empty; private Transform runtimeRoot; private SpriteRenderer memoryRendererA; @@ -129,7 +112,6 @@ namespace AibisDream.MiniGame.Language private SpriteMask screenSpriteMask; private TextMeshPro lieText; private TextMeshPro lieGhostText; - private TextMeshPro systemText; private TextMeshPro actorFlashText; private TextMeshPro actorFlashChromaRed; private TextMeshPro actorFlashChromaCyan; @@ -141,7 +123,6 @@ namespace AibisDream.MiniGame.Language private string truthWarmColorHex; private TMPRectClipper lieClipper; private TMPRectClipper lieGhostClipper; - private TMPRectClipper systemClipper; private TMPRectClipper actorFlashClipper; private TMPRectClipper actorFlashChromaRedClipper; private TMPRectClipper actorFlashChromaCyanClipper; @@ -150,7 +131,6 @@ namespace AibisDream.MiniGame.Language private bool actorFlashActive; private int actorFlashCount; private Coroutine actorOverdriveRoutine; - private Coroutine actorFlashLoopRoutine; private Texture2D runtimeWhiteTexture; private Sprite runtimeWhiteSprite; private MaterialPropertyBlock memoryBlockA; @@ -254,7 +234,6 @@ namespace AibisDream.MiniGame.Language TweenMemoryOpacity(memoryRendererB, 0f, duration); FadeText(lieText, 0f, duration); FadeText(lieGhostText, 0f, duration); - FadeText(systemText, 0f, duration); FadeText(actorFlashText, 0f, duration); FadeText(actorFlashChromaRed, 0f, duration); FadeText(actorFlashChromaCyan, 0f, duration); @@ -337,7 +316,6 @@ namespace AibisDream.MiniGame.Language currentPreset = preset; currentMemoryKind = kind; - currentLeakFragment = GetLeakFragment(kind); _ = horizontalSpeed; actorFlashCount = 0; memoryImpact = 0f; @@ -398,84 +376,6 @@ namespace AibisDream.MiniGame.Language Mathf.Max(0.01f, duration)); } - public IEnumerator BeginLie(string keyword, string presetName) - { - EnsureRuntimeObjects(); - if (!TryResolvePreset(presetName, out Preset requestedPreset)) - { - Debug.LogError($"[LogReleasePresentation] 未知演出预设“{presetName}”;命令安全结束。"); - yield break; - } - - if (!string.Equals(requestedPreset.Name, currentPreset.Name, StringComparison.OrdinalIgnoreCase)) - { - Debug.LogWarning( - $"[LogReleasePresentation] expression_lie_begin 的预设 {requestedPreset.Name} " + - $"与当前记忆 {currentPreset.Name} 不一致,将沿用当前记忆预设。"); - } - - PrepareLieVisuals(0.25f, 0.005f); - SqueezeTruthCharacters( - 0.22f, - 0.055f + currentPreset.BreakPeak * 0.065f, - 0.28f); - ShowLieKeyword(keyword, 0.22f, 14f); - PlayScreenScanline(0.32f, currentMemoryKind == MemoryKind.Sunset); - if (currentMemoryKind == MemoryKind.PrivateOffice) - ShowSystemMessage("输出正常", "normal", 0.12f); - else - HideSystemMessageImmediate(); - yield return new WaitForSeconds(0.25f); - } - - public IEnumerator BreakLie() - { - EnsureRuntimeObjects(); - if (!ExpectState(nameof(BreakLie), PresentationState.LieHolding)) - yield break; - - yield return new WaitForSeconds(currentPreset.LiePause); - BeginLieCracking(0.48f); - PlayScreenScanline(0.22f, true); - PlayScreenDim(0.28f, 0.06f, 0.12f); - PlayMemoryJolt(0.08f, 0.24f, 0.11f); - yield return LeakTruthFragment(currentLeakFragment, 0.24f); - - if (currentMemoryKind == MemoryKind.PrivateOffice) - { - HideSystemMessage(0.01f); - yield return new WaitForSeconds(0.08f); - ShowSystemMessage("输出与原始 log 不一致", "error", 0.08f); - yield return PulseGlitch(HeavyPreset.BreakPeak, 0.12f, 0.18f); - } - } - - public IEnumerator RevealTruth() - { - EnsureRuntimeObjects(); - if (!ExpectState( - nameof(RevealTruth), - PresentationState.LieCracking, - PresentationState.LieHolding)) - { - yield break; - } - - DOTween.Kill(this); - yield return HoldTruthBlackout(0.055f, 0.42f); - SplitLieVisual(currentPreset.BreakDuration, 0.18f, 1.85f, 0.42f); - PlayTruthFlash(0.62f, 0.045f, 0.025f, 0.16f); - PlayMemoryBrightnessPulse(0.16f, 0.06f, 0.20f); - PlayMemoryTear(currentPreset.BreakPeak, currentPreset.BreakDuration); - PlayGlitchPulse(currentPreset.BreakPeak, 0.12f, currentPreset.BreakDuration); - PlayFaceDip(currentPreset.BreakDuration, faceDipRatio); - ReleaseTruthCharacters(0.08f); - yield return ResolveTruthCharacters( - currentPreset.TruthDuration, - currentPreset.TruthPeripheral, - 0.42f); - } - public void PrepareLieVisuals(float freezeDuration, float noiseTarget) { EnsureRuntimeObjects(); @@ -560,42 +460,6 @@ namespace AibisDream.MiniGame.Language PlayScanline(Mathf.Max(0.01f, duration), stalled); } - public void ShowSystemMessage(string value, string style, float fadeDuration) - { - EnsureRuntimeObjects(); - if (!ExpectState( - nameof(ShowSystemMessage), - PresentationState.FaceEntering, - PresentationState.Memory, - PresentationState.LieHolding, - PresentationState.LieCracking)) - return; - Color color; - if (string.Equals(style, "error", StringComparison.OrdinalIgnoreCase)) - { - color = errorColor; - } - else - { - if (!string.Equals(style, "normal", StringComparison.OrdinalIgnoreCase)) - Debug.LogWarning($"[LogReleasePresentation] 未知系统提示样式“{style}”,改用 normal。"); - color = new Color(0.58f, 0.78f, 0.82f, 1f); - } - SetSystemMessage(value ?? string.Empty, color, Mathf.Max(0.01f, fadeDuration)); - } - - public void HideSystemMessage(float fadeDuration) - { - if (systemText == null || !systemText.gameObject.activeSelf) - return; - - systemText.DOKill(); - systemText.DOFade(0f, Mathf.Max(0.01f, fadeDuration)) - .SetEase(Ease.OutQuad) - .SetTarget(this) - .OnComplete(HideSystemMessageImmediate); - } - public void BeginLieCracking(float lieAlpha) { if (!ExpectState( @@ -654,9 +518,9 @@ namespace AibisDream.MiniGame.Language PresentationState.LieCracking)) yield break; - currentLeakFragment = fragment ?? string.Empty; + string resolvedFragment = fragment ?? string.Empty; yield return ShuffleLieCharactersRoutine( - currentLeakFragment, + resolvedFragment, Mathf.Max(0.01f, duration), 0.045f); } @@ -688,7 +552,6 @@ namespace AibisDream.MiniGame.Language return; DOTween.Kill(this); - StopActorFlashLoopImmediate(false); StopActorOverdriveImmediate(); memoryPushSpeed = 0f; memoryPushProgress = 0f; @@ -808,73 +671,6 @@ namespace AibisDream.MiniGame.Language } } - public void StartActorFlashLoop(IReadOnlyList phrases) - { - EnsureRuntimeObjects(); - if (!ExpectState(nameof(StartActorFlashLoop), PresentationState.Memory)) - return; - if (phrases == null || phrases.Count == 0) - { - Debug.LogError("[LogReleasePresentation] Actor Flash Loop 没有有效短句;已安全跳过。"); - return; - } - - StopActorFlashLoopImmediate(true); - actorFlashLoopRoutine = StartCoroutine(ActorFlashLoopRoutine(phrases)); - } - - public void StopActorFlashLoop() - { - StopActorFlashLoopImmediate(true); - } - - private IEnumerator ActorFlashLoopRoutine(IReadOnlyList phrases) - { - while (state == PresentationState.Memory) - { - 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( - phrases[i], - hold, - fontSize, - 1.1f, - punchScale, - chromaDistance, - chromaAlpha, - impact, - 0f); - if (state == PresentationState.Memory) - yield return new WaitForSeconds(0.3f); - } - } - - actorFlashLoopRoutine = null; - } - - private void StopActorFlashLoopImmediate(bool restoreParticles) - { - if (actorFlashLoopRoutine != null) - { - StopCoroutine(actorFlashLoopRoutine); - actorFlashLoopRoutine = null; - } - - actorFlashActive = false; - memoryImpact = 0f; - HideActorFlashVisualsImmediate(); - if (restoreParticles && state == PresentationState.Memory) - particleManager?.SetTargetParticlesAlpha(1f, 0f); - } - /// /// 真话闪现:谎话大字之间,暖色完整真话带着细微抖动挣扎浮现一拍。 /// 不推进谎话冲击等级、不累积记忆损伤;"被掐断"由紧随其后的 expression_glitch_pulse 表现。 @@ -1100,7 +896,6 @@ namespace AibisDream.MiniGame.Language state = PresentationState.TruthResolving; StopLieShuffleImmediate(true); HideLieLayersImmediate(); - HideSystemMessageImmediate(); StopScanline(); StopScreenDim(); @@ -1706,7 +1501,6 @@ namespace AibisDream.MiniGame.Language return; state = PresentationState.TruthResolving; - HideSystemMessage(0.15f); particleManager?.PrepareTruthReleaseFromLie(); particleManager?.ApplyPresentationResolvedVisuals(presentationResolvedColor); particleManager?.SetTargetParticlesAlpha(1f, Mathf.Max(0.01f, alphaDuration)); @@ -1752,7 +1546,6 @@ namespace AibisDream.MiniGame.Language } HideLieLayersImmediate(); - HideSystemMessageImmediate(); StopScanline(); StopScreenDim(); ReleaseScreenTextClipMaterials(); @@ -1790,7 +1583,6 @@ namespace AibisDream.MiniGame.Language TweenMemoryOpacity(memoryRendererA, 0f, memoryExitDuration); TweenMemoryOpacity(memoryRendererB, 0f, memoryExitDuration); HideLieLayersImmediate(); - HideSystemMessageImmediate(); StopScanline(); StopScreenDim(); SilenceNoiseOverlayIfPresent(); @@ -1815,7 +1607,6 @@ namespace AibisDream.MiniGame.Language { DOTween.Kill(this); StopAllCoroutines(); - actorFlashLoopRoutine = null; RestoreFaceSpriteImmediate(); memoryOpacityA = 0f; @@ -1824,7 +1615,6 @@ namespace AibisDream.MiniGame.Language DisableMemoryRenderer(memoryRendererB); activeMemoryRenderer = null; HideLieLayersImmediate(); - HideSystemMessageImmediate(); StopScanline(); StopScreenDim(); ReleaseScreenTextClipMaterials(); @@ -1847,7 +1637,6 @@ namespace AibisDream.MiniGame.Language particleManager?.RestoreTargetPresentationDefaults(); state = PresentationState.Idle; currentLieKeyword = string.Empty; - currentLeakFragment = string.Empty; memoryPushSpeed = 0f; memoryPushProgress = 0f; memoryZoom = 1f; @@ -2309,21 +2098,18 @@ namespace AibisDream.MiniGame.Language CreateScreenMask(); lieText = CreateScreenText("LieKeyword", 4.1f, screenTextSortingOrder); lieGhostText = CreateScreenText("LieKeywordGhost", 4.1f, screenTextSortingOrder - 1); - systemText = CreateScreenText("SystemPrompt", 1.35f, screenTextSortingOrder + 1); actorFlashText = CreateScreenText("ActorFlashKeyword", 10.5f, screenTextSortingOrder); actorFlashChromaRed = CreateScreenText("ActorFlashChromaRed", 10.5f, screenTextSortingOrder - 1); actorFlashChromaCyan = CreateScreenText("ActorFlashChromaCyan", 10.5f, screenTextSortingOrder - 1); truthFlashText = CreateScreenText("ActorTruthFlash", 8.5f, screenTextSortingOrder - 1); lieClipper = lieText.gameObject.AddComponent(); lieGhostClipper = lieGhostText.gameObject.AddComponent(); - systemClipper = systemText.gameObject.AddComponent(); actorFlashClipper = actorFlashText.gameObject.AddComponent(); actorFlashChromaRedClipper = actorFlashChromaRed.gameObject.AddComponent(); actorFlashChromaCyanClipper = actorFlashChromaCyan.gameObject.AddComponent(); truthFlashClipper = truthFlashText.gameObject.AddComponent(); lieClipper.Initialize(lieText, expressionScreenMaskRect); lieGhostClipper.Initialize(lieGhostText, expressionScreenMaskRect); - systemClipper.Initialize(systemText, expressionScreenMaskRect); actorFlashClipper.Initialize(actorFlashText, expressionScreenMaskRect); actorFlashChromaRedClipper.Initialize(actorFlashChromaRed, expressionScreenMaskRect); actorFlashChromaCyanClipper.Initialize(actorFlashChromaCyan, expressionScreenMaskRect); @@ -2437,15 +2223,12 @@ namespace AibisDream.MiniGame.Language PositionText(actorFlashChromaRed, center, screen.size); PositionText(actorFlashChromaCyan, center, screen.size); PositionText(truthFlashText, center, screen.size); - Vector3 systemPosition = center + Vector3.up * screen.extents.y * 0.70f; - PositionText(systemText, systemPosition, new Vector2(screen.size.x, screen.size.y * 0.22f)); } private void EnsureScreenTextClipMaterials() { lieClipper?.Initialize(lieText, expressionScreenMaskRect); lieGhostClipper?.Initialize(lieGhostText, expressionScreenMaskRect); - systemClipper?.Initialize(systemText, expressionScreenMaskRect); actorFlashClipper?.Initialize(actorFlashText, expressionScreenMaskRect); actorFlashChromaRedClipper?.Initialize(actorFlashChromaRed, expressionScreenMaskRect); actorFlashChromaCyanClipper?.Initialize(actorFlashChromaCyan, expressionScreenMaskRect); @@ -2458,7 +2241,6 @@ namespace AibisDream.MiniGame.Language { lieClipper?.ReleaseMaterial(); lieGhostClipper?.ReleaseMaterial(); - systemClipper?.ReleaseMaterial(); actorFlashClipper?.ReleaseMaterial(); actorFlashChromaRedClipper?.ReleaseMaterial(); actorFlashChromaCyanClipper?.ReleaseMaterial(); @@ -2751,22 +2533,6 @@ namespace AibisDream.MiniGame.Language screenDimRenderer.gameObject.SetActive(false); } - private void SetSystemMessage(string value, Color color, float fadeDuration) - { - SetTextActive(systemText, value, color); - SetTextAlpha(systemText, 0f); - FadeText(systemText, 1f, fadeDuration); - } - - private void HideSystemMessageImmediate() - { - if (systemText == null) - return; - systemText.DOKill(); - systemText.text = string.Empty; - systemText.gameObject.SetActive(false); - } - private void HideLieLayersImmediate() { StopLieShuffleImmediate(false); @@ -2777,7 +2543,6 @@ namespace AibisDream.MiniGame.Language private void HideActorFlashImmediate() { - StopActorFlashLoopImmediate(false); StopActorOverdriveImmediate(); StopTruthLieFlicker(); actorFlashActive = false; @@ -2885,25 +2650,6 @@ namespace AibisDream.MiniGame.Language } } - private static bool TryResolvePreset(string value, out Preset preset) - { - switch ((value ?? string.Empty).Trim().ToLowerInvariant()) - { - case "light": - preset = LightPreset; - return true; - case "medium": - preset = MediumPreset; - return true; - case "heavy": - preset = HeavyPreset; - return true; - default: - preset = LightPreset; - return false; - } - } - private static Preset GetPresetForMemory(MemoryKind kind) { return kind switch @@ -2914,16 +2660,6 @@ namespace AibisDream.MiniGame.Language }; } - private static string GetLeakFragment(MemoryKind kind) - { - return kind switch - { - MemoryKind.Office => "笑话", - MemoryKind.Sunset => "离开", - _ => "不要那样看我" - }; - } - private bool ExpectState(string command, params PresentationState[] allowed) { for (int i = 0; i < allowed.Length; i++) diff --git a/Assets/Scripts/MiniGame/HuoShan/Language/QuickStart.md b/Assets/Scripts/MiniGame/HuoShan/Language/QuickStart.md index 74c63f1fe..51e3b856b 100644 --- a/Assets/Scripts/MiniGame/HuoShan/Language/QuickStart.md +++ b/Assets/Scripts/MiniGame/HuoShan/Language/QuickStart.md @@ -26,7 +26,7 @@ Floating Count: 100 Candidate Count: 50 Red Particle Count: 8 Connection Distance: 2 -Target Sentence: "别过来我感觉害怕" +Target Sentence: 由启动命令传入,不在 Inspector 中保存测试文案 Waveform Amplitude: 0.3 Interaction Radius: 0.5 ``` @@ -68,7 +68,7 @@ Interaction Radius: 0.5 1. Window → TextMeshPro → Font Asset Creator 2. 选择支持中文的字体(如:思源黑体、微软雅黑) 3. Character Set: Custom Characters -4. 粘贴`TextParticle.cs`中的`chineseChars`字符串 +4. 粘贴 `Params/hs.exp.pool` 及正式 Express 文案所需字符 5. Generate Font Atlas ### 看不到粒子? diff --git a/Assets/Scripts/MiniGame/HuoShan/Language/README.md b/Assets/Scripts/MiniGame/HuoShan/Language/README.md index 7692eaf64..111880343 100644 --- a/Assets/Scripts/MiniGame/HuoShan/Language/README.md +++ b/Assets/Scripts/MiniGame/HuoShan/Language/README.md @@ -50,7 +50,7 @@ Language/ #### 游戏参数 - **Connection Distance**: 连接距离阈值(默认2,Unity单位) -- **Target Sentence**: 目标句子(默认"别过来我感觉害怕") +- **Target Sentence**: 目标句子(必须由启动命令传入) #### 波形参数 - **Waveform Amplitude**: 波形振幅(默认0.3) @@ -91,7 +91,7 @@ Language/ 完成第一阶段后: 1. 红色粒子自动排列成水平一行 2. 蓝色粒子淡出 -3. 红色粒子逐个揭示目标文字:"别过来我感觉害怕" +3. 红色粒子逐个揭示启动命令传入的目标文字 4. 文字出现剧烈抖动(焦虑效果) ### 第三阶段:波形交互 @@ -158,15 +158,12 @@ Language/ ### 添加新的文字内容 -修改`CandidateParticle.cs`中的字符集: -```csharp -protected static string chineseChars = "你的字符集..."; -protected static string[] chineseWords = { "词组1", "词组2", ... }; -``` +在 Unity Localization 的 `Params/hs.exp.pool` 中配置当前语言的随机字符池。 +字符池按 Unicode 文本元素拆分,忽略空白;重复字符可用于提高出现权重。 ### 修改目标句子 -在Manager的Inspector中修改`Target Sentence`字段 +修改 `ExpressionContentCatalog` 对应轮次引用的 `Params` 本地化条目。 ### 调整波形特性 diff --git a/Assets/Scripts/MiniGame/HuoShan/Language/TextParticle.cs b/Assets/Scripts/MiniGame/HuoShan/Language/TextParticle.cs index 27dfc8bf2..b7e20de86 100644 --- a/Assets/Scripts/MiniGame/HuoShan/Language/TextParticle.cs +++ b/Assets/Scripts/MiniGame/HuoShan/Language/TextParticle.cs @@ -27,7 +27,11 @@ namespace AibisDream.MiniGame.Language protected float changeTimer; protected float changeInterval = 1f; - protected string overrideCharPool; + private static readonly IReadOnlyList EmptyCharacterPool = + new List().AsReadOnly(); + protected IReadOnlyList defaultCharacterPool = EmptyCharacterPool; + protected IReadOnlyList interferenceCharacterPool = EmptyCharacterPool; + protected IReadOnlyList overrideCharacterPool = EmptyCharacterPool; protected Bounds movementBounds; protected bool useCircularBounds = false; protected Vector3 boundsCenter; @@ -36,13 +40,6 @@ namespace AibisDream.MiniGame.Language private Material customFontMaterial; private TMPRectClipper screenClipper; - // 字符集 - protected static string chineseChars = "的一是在不了有和人这中大为上个国我以要他时来用们生到作地于出就分对成会可主发年动同工也能下过子说产种面而方后多定行学法所民得经十三之进着等部度家电力里如水化高自二理起小物现实加量都两体制机当使点从业本去把性好应开它合还因由其些然前外天政四日那社义事平形相全表间样与关各重新线内数正心反你明看原又么利比或但质气第向道命此变条只没结解问意建月公无系军很情者最立代想已通并提直题党程展五果料象员革位入常文总次品式活设及管特件长求老头基资边流路级少图山统接知较将组见计别她手角期根论运农指几九区强放决西被干做必战先回则任取据处队南给色光门即保治北造百规热领七海口东导器压志世金增争济阶油思术极交受联什认六共权收证改清己美再采转更单风切打白教速花带安场身车例真务具万每目至达走积示议声报斗完类八离华名确才科张信马节话米整空元况今集温传土许步群广石记需段研界拉林律叫且究观越织装影算低持音众书布复容儿须际商非验连断深难近矿千周委素技备半办青省列习响约支般史感劳便团往酸历市克何除消构府称太准精值号率族维划选标写存候毛亲快效斯院查江型眼王按格养易置派层片始却专状育厂京识适属圆包火住调满县局照参红细引听该铁价严龙飞"; - protected static string[] chineseWords = { "不安", "紧张", "焦虑", "担忧", "烦躁", "恐慌", "恐惧", "绝望", "痛苦", "悲伤", "愤怒", "孤独", "无助", "迷茫", "困惑", "压抑", "沉重", "疲惫", "空虚", "失落" }; - - // 动态配置的干扰短句(笑话/焦虑等,由 Yarn start_expression 传入,用于非目标粒子字符池) - protected static List configuredAnxietyPhrases = new List(); - protected virtual void Awake() { if (textMesh == null) @@ -85,7 +82,7 @@ namespace AibisDream.MiniGame.Language SyncLayerToChildren(); changeTimer = Random.Range(0.5f, 1.5f); - currentChar = GetNextDisplayChar(); + currentChar = string.Empty; UpdateText(); // TMP 重建 mesh / SubMesh 后再对齐一次 layer SyncLayerToChildren(); @@ -112,7 +109,7 @@ namespace AibisDream.MiniGame.Language // 边界检测 CheckBounds(); - // 字符变化:目标粒子用 chineseChars 随机,非目标粒子从干扰短句字符中随机 + // 字符变化:目标粒子用本轮默认池,非目标粒子从干扰短句字符池中随机 changeTimer -= Time.deltaTime; if (changeTimer <= 0) { @@ -209,7 +206,7 @@ namespace AibisDream.MiniGame.Language protected string GetRandomChar() { - return chineseChars[Random.Range(0, chineseChars.Length)].ToString(); + return GetRandomPoolElement(defaultCharacterPool); } /// @@ -218,22 +215,14 @@ namespace AibisDream.MiniGame.Language /// protected string GetRandomCharFromPhrases() { - if (configuredAnxietyPhrases == null || configuredAnxietyPhrases.Count == 0) + if (interferenceCharacterPool == null || interferenceCharacterPool.Count == 0) return GetRandomChar(); - var chars = new List(); - foreach (var phrase in configuredAnxietyPhrases) - { - if (string.IsNullOrEmpty(phrase)) continue; - foreach (char c in phrase) - chars.Add(c); - } - if (chars.Count == 0) return GetRandomChar(); - return chars[Random.Range(0, chars.Count)].ToString(); + return GetRandomPoolElement(interferenceCharacterPool); } /// /// 获取下一次要显示的字符。子类可重写以区分目标粒子与非目标粒子(与红蓝颜色解绑)。 - /// 默认返回 chineseChars 随机单字。 + /// 默认从本轮本地化字符池随机返回一个 Unicode 文本元素。 /// protected virtual string GetNextDisplayChar() { @@ -247,27 +236,60 @@ namespace AibisDream.MiniGame.Language /// public void SetOverrideCharPool(string pool) { - overrideCharPool = string.IsNullOrEmpty(pool) ? null : pool; + SetOverrideCharacterPool( + ExpressionTextTokenizer.GetVisibleElements(pool)); + } + + public void SetOverrideCharacterPool(IReadOnlyList pool) + { + overrideCharacterPool = pool != null && pool.Count > 0 + ? pool + : EmptyCharacterPool; } protected bool TryGetOverridePoolChar(out string character) { - if (string.IsNullOrEmpty(overrideCharPool)) + if (!HasOverrideCharacterPool) { character = null; return false; } - character = overrideCharPool[Random.Range(0, overrideCharPool.Length)].ToString(); + character = GetRandomPoolElement(overrideCharacterPool); return true; } - + /// - /// 设置全局干扰短句配置(笑话等,由 LanguageParticleManager 调用) + /// 设置本轮默认与干扰字符池。调用方负责传入不可变的本轮快照。 /// - public static void SetAnxietyPhrases(List phrases) + public void SetCharacterPools( + IReadOnlyList defaultPool, + IReadOnlyList interferencePool) { - configuredAnxietyPhrases = phrases ?? new List(); + defaultCharacterPool = defaultPool != null && defaultPool.Count > 0 + ? defaultPool + : EmptyCharacterPool; + interferenceCharacterPool = + interferencePool != null && interferencePool.Count > 0 + ? interferencePool + : EmptyCharacterPool; + } + + public void InitializeRandomCharacter() + { + currentChar = GetNextDisplayChar(); + UpdateText(); + changeTimer = Random.Range(0.5f, 1.5f); + } + + protected bool HasOverrideCharacterPool => + overrideCharacterPool != null && overrideCharacterPool.Count > 0; + + private static string GetRandomPoolElement(IReadOnlyList pool) + { + if (pool == null || pool.Count == 0) + return string.Empty; + return pool[Random.Range(0, pool.Count)]; } protected void UpdateText() diff --git a/Docs/HuoShan/HuoshanStage6LocalizationPlan.md b/Docs/HuoShan/HuoshanStage6LocalizationPlan.md index 7d5183d27..1c3dbef77 100644 --- a/Docs/HuoShan/HuoshanStage6LocalizationPlan.md +++ b/Docs/HuoShan/HuoshanStage6LocalizationPlan.md @@ -7,9 +7,9 @@ Localization 的 `Params` String Table Collection 作为唯一数据源。 - `l10n.xxx` 查询 `Params/xxx`。 - 查询使用 `DontUseFallback`,不回退中文或其他 Locale。 -- 中文表保存源数据;英文、日文表保留同一套 Key,未翻译值留空。 -- Key 不存在时显示 `⟦key⟧` 并记录包含 Table、Key、Locale 的错误。 -- Express 必填值解析为空时安全终止命令,不启动粒子或释放演出。 +- 中文表保存源数据;其他 Locale 表保留同一套 Key,未翻译值留空。 +- Key 不存在时返回 `⟦key⟧` 并记录包含 Table、Key、Locale 的错误。 +- Express 必填值解析为空或缺失标记时安全终止命令,不启动粒子或释放演出。 - `StreamingAssets/Config/params.csv` 已退出运行链路。 - 固定 UI 继续使用 `UIText`;普通 Yarn 对白、Task、教学和角色名不迁入 `Params`。 @@ -25,8 +25,8 @@ Localization 的 `Params` String Table Collection 作为唯一数据源。 | `log2` | `l10n.hs.exp.log2.target` | `l10n.hs.exp.log2.tokens` | 28 | | `log3` | `l10n.hs.exp.log3.target` | `l10n.hs.exp.log3.tokens` | 38 | -Actor Loop 使用 `l10n.hs.exp.actor.loop`。Round ID 大小写敏感;重复 ID、 -未知 ID、空引用、非 `l10n.*` 引用或解析后的空值均视为数据错误。 +Round ID 大小写敏感;重复 ID、未知 ID、空引用、非 `l10n.*` 引用或解析 +后的空值均视为数据错误。 正式 Yarn 使用: @@ -40,8 +40,8 @@ Actor Loop 使用 `l10n.hs.exp.actor.loop`。Round ID 大小写敏感;重复 I ## Locale 生命周期 -启动轮次时捕获当前 Locale,目标句和 token 并行解析;校验通过后才初始化 -粒子系统。该 Locale 快照会保留到下一轮开始或 ExpressSystem 关闭,当前轮 +启动轮次时捕获当前 Locale,目标句、token 和 `l10n.hs.exp.pool` 并行解析; +校验通过后才初始化粒子系统。该 Locale 快照会保留到下一轮开始或 ExpressSystem 关闭,当前轮 后续释放演出都使用同一快照。没有活动轮次时,演出命令使用命令调用时的 当前 Locale。 @@ -53,9 +53,9 @@ Actor Loop 使用 `l10n.hs.exp.actor.loop`。Round ID 大小写敏感;重复 I Express 使用短前缀 `hs.exp.*`: - 三轮目标和 token:`hs.exp.log{1..3}.target`、`.tokens` -- Actor Loop:`hs.exp.actor.loop` - LOG3 攻击词:`hs.exp.atk.*` - 最终稳定文字:`hs.exp.final.win` +- 全局随机字符池:`hs.exp.pool` 重复演出复用同一 Key,但 Yarn 中仍保留原有调用次数、等待、数值、顺序和 `#line:` 标签。 @@ -74,9 +74,10 @@ Express 使用短前缀 `hs.exp.*`: 最终排列保留词间距。目标粒子数量、最终排列、攻击词长度校验和 truth leak 匹配使用同一套文本元素结果。 -本次明确不修改 `TextParticle` 的随机字符池、`chineseChars`、 -`chineseWords`、目标粒子入场阶段的随机字符来源,以及背景/干扰粒子的字符 -池策略。 +随机字符池同样按 Unicode 文本元素处理。`hs.exp.pool` 是连续文本,不使用 +`|` 或全角 `|`,空白被忽略,重复元素保留为随机权重。目标粒子从该池取字, +非目标和背景浮动粒子继续从本轮 token 展开的干扰池取字;临时老虎机池结束 +后按粒子角色恢复。旧的 `chineseChars` 与 `chineseWords` 运行时常量均已删除。 ## 内容校验 @@ -85,8 +86,10 @@ Express 使用短前缀 `hs.exp.*`: - Stage6 正式 Express 文字参数不得残留中文硬编码; - Yarn 和 Catalog 中引用的 `l10n.*` Key 必须存在于 `Params` Shared Data; - 中文值必须非空; -- 英文、日文空值只报告待翻译,不导致校验失败; +- 未翻译 Locale 的空值只报告待翻译,不导致校验失败; - `.tokens` 至少包含一个有效项,且不能使用全角 `|`; +- `hs.exp.pool` 必须存在于所有 Params Locale 表;中文非空,非空译文不得 + 包含 `|`、`|` 或只有空白; - completion node、preset、memory key、颜色、Timeline 等结构参数不误报。 运行时和 EditMode 测试同时覆盖原始字符串、空值不回退、缺 Key 占位、 diff --git a/Docs/HuoShan/LogReleaseYarnCommands.md b/Docs/HuoShan/LogReleaseYarnCommands.md index 53eca90db..e386a7730 100644 --- a/Docs/HuoShan/LogReleaseYarnCommands.md +++ b/Docs/HuoShan/LogReleaseYarnCommands.md @@ -2,8 +2,6 @@ `Stage6.yarn` 已改为使用原子命令编排。除特别标记为“等待”的命令外,命令只启动效果并立即让 Yarn 继续;并行效果之间的节奏用 `<>` 调整。 -旧命令 `expression_lie_begin`、`expression_lie_break`、`expression_truth_reveal` 仍然保留,供其他节点兼容使用。Stage6 不再依赖它们。 - ## 火山脸与 LOG1 大字 | 命令 | 参数 | 行为 | @@ -11,8 +9,6 @@ | `expression_face_fade_in` | `duration` | 显式淡入火山脸并等待完成。三条 LOG 都在完成节点中调用,不再由粒子系统自动淡入。 | | `expression_actor_flash` | `"text" holdDuration` | 清晰的大字重击;连续调用会逐级增强,但每次结束都退回晃动粒子字。 | | `expression_actor_flash_in` | 与 `expression_actor_flash` 相同 | 阻塞播放相同的大字重击入场,但保持最后一帧、不退场;用于段落结尾定格。 | -| `expression_actor_flash_loop_start` | 无 | 非阻塞启动“没问题 → 冇问题 → 帽问题”三拍无限循环,参数固定为 LOG1 当前节奏。 | -| `expression_actor_flash_loop_stop` | 无 | 立即停止循环、清掉当前大字并恢复晃动粒子字。 | | `expression_actor_slot` | `"characterPool" duration interval fontSize "truthPool"` | 中央大字持续换字。`truthPool` 非空时其字符也混入换字池,并在主字上以真话暖色(`truthAccentColor`)显示——真假字符交替闪烁即"争夺"。命令不等待,节奏由紧随其后的同长度 `wait` 控制;结束后停在最后一帧。 | | `expression_actor_truth_flash` | `"text" holdDuration fontSize alpha jitter` | 真话闪现(等待):谎话大字之间,暖色完整真话带细微抖动挣扎浮现一拍。不推进谎话冲击等级、不累积记忆损伤;"被掐断"由紧随其后的 `expression_glitch_pulse` 表现。 | | `expression_actor_word_hit` | `"text" holdDuration` | 单字或短词独占屏幕重击,并等待该拍完成。 |