feat: 本地化字体兼容

This commit is contained in:
2026-08-02 01:22:40 +08:00
parent 3bff294130
commit 3adb04ebb3
56 changed files with 2233 additions and 99 deletions
@@ -15,7 +15,7 @@ MonoBehaviour:
m_DefaultGroup: 09546aca93a801441859af2a811fe53b m_DefaultGroup: 09546aca93a801441859af2a811fe53b
m_currentHash: m_currentHash:
serializedVersion: 2 serializedVersion: 2
Hash: ae2a22805ec7ff55174c38995aa3e54d Hash: 00000000000000000000000000000000
m_OptimizeCatalogSize: 0 m_OptimizeCatalogSize: 0
m_BuildRemoteCatalog: 0 m_BuildRemoteCatalog: 0
m_BundleLocalCatalog: 0 m_BundleLocalCatalog: 0
@@ -71,6 +71,7 @@ MonoBehaviour:
- {fileID: 11400000, guid: 089fc86f714d6a94bafea3b117fbd639, type: 2} - {fileID: 11400000, guid: 089fc86f714d6a94bafea3b117fbd639, type: 2}
- {fileID: 11400000, guid: 1c592abe924a856439461f3785ec53da, type: 2} - {fileID: 11400000, guid: 1c592abe924a856439461f3785ec53da, type: 2}
- {fileID: 11400000, guid: 26fd03652560e7f4abec092229fc38f2, type: 2} - {fileID: 11400000, guid: 26fd03652560e7f4abec092229fc38f2, type: 2}
- {fileID: 11400000, guid: 1051221ba75fcea47b67648acedc5383, type: 2}
m_BuildSettings: m_BuildSettings:
m_CompileScriptsInVirtualMode: 0 m_CompileScriptsInVirtualMode: 0
m_CleanupStreamingAssetsAfterBuilds: 1 m_CleanupStreamingAssetsAfterBuilds: 1
@@ -121,6 +122,7 @@ MonoBehaviour:
- Locale-pt-BR - Locale-pt-BR
- Locale-ru - Locale-ru
- Locale-es - Locale-es
- Typography
m_SchemaTemplates: [] m_SchemaTemplates: []
m_GroupTemplateObjects: m_GroupTemplateObjects:
- {fileID: 11400000, guid: 0c158052680a3e94184c450d00291514, type: 2} - {fileID: 11400000, guid: 0c158052680a3e94184c450d00291514, type: 2}
@@ -0,0 +1,45 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: e5d17a21594effb4e9591490b009e7aa, type: 3}
m_Name: Shared_Typography_BundledAssetGroupSchema
m_EditorClassIdentifier:
m_Group: {fileID: 11400000, guid: 1051221ba75fcea47b67648acedc5383, type: 2}
m_InternalBundleIdMode: 1
m_Compression: 1
m_IncludeAddressInCatalog: 1
m_IncludeGUIDInCatalog: 1
m_IncludeLabelsInCatalog: 1
m_InternalIdNamingMode: 0
m_CacheClearBehavior: 0
m_IncludeInBuild: 1
m_BundledAssetProviderType:
m_AssemblyName: Unity.ResourceManager, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
m_ClassName: UnityEngine.ResourceManagement.ResourceProviders.BundledAssetProvider
m_ForceUniqueProvider: 0
m_UseAssetBundleCache: 1
m_UseAssetBundleCrc: 1
m_UseAssetBundleCrcForCachedBundles: 1
m_UseUWRForLocalBundles: 0
m_Timeout: 0
m_ChunkedTransfer: 0
m_RedirectLimit: -1
m_RetryCount: 0
m_BuildPath:
m_Id: 2ae6a7549097a9b4caefe44b61b1fcc5
m_LoadPath:
m_Id: 631ec5c677ab8084fb9be9b9ee39c6ef
m_BundleMode: 0
m_AssetBundleProviderType:
m_AssemblyName: Unity.ResourceManager, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
m_ClassName: UnityEngine.ResourceManagement.ResourceProviders.AssetBundleProvider
m_BundleNaming: 0
m_AssetLoadMode: 0
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: b39cbcc4de75ad6468842c36c7e5138d
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,16 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 5834b5087d578d24c926ce20cd31e6d6, type: 3}
m_Name: Shared_Typography_ContentUpdateGroupSchema
m_EditorClassIdentifier:
m_Group: {fileID: 11400000, guid: 1051221ba75fcea47b67648acedc5383, type: 2}
m_StaticContent: 0
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 6bd7e9bcac1e24042b79a54f47f52106
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,49 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: bbb281ee3bf0b054c82ac2347e9e782c, type: 3}
m_Name: Shared_Typography
m_EditorClassIdentifier:
m_GroupName: Shared_Typography
m_Data:
m_SerializedData: []
m_GUID: 7663edc0b4aef67468c672b641025c85
m_SerializeEntries:
- m_GUID: e41dce1e1d6e0564b95054b5c8b8bb4c
m_Address: Typography/Profile/ZhHans
m_ReadOnly: 0
m_SerializedLabels:
- Typography
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 67cd590df91846b4d81698d68426e616
m_Address: Typography/Profile/Japanese
m_ReadOnly: 0
m_SerializedLabels:
- Typography
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: d53382c947b5edc4a88216c5fcf37b38
m_Address: Typography/Profile/Latin
m_ReadOnly: 0
m_SerializedLabels:
- Typography
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: f82150d4b378dc441884eb47d4f67b7c
m_Address: Typography/Profile/Russian
m_ReadOnly: 0
m_SerializedLabels:
- Typography
FlaggedDuringContentUpdateRestriction: 0
m_ReadOnly: 0
m_Settings: {fileID: 11400000, guid: 77169ce22e430f64fb36c771815a4a7b, type: 2}
m_SchemaSet:
m_Schemas:
- {fileID: 11400000, guid: b39cbcc4de75ad6468842c36c7e5138d, type: 2}
- {fileID: 11400000, guid: 6bd7e9bcac1e24042b79a54f47f52106, type: 2}
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 1051221ba75fcea47b67648acedc5383
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,182 @@
using System;
using System.Collections.Generic;
using System.Linq;
using AibisDream.Framework;
using AibisDream.EditorTools;
using AibisDream.UI;
using NUnit.Framework;
using TMPro;
using UnityEditor;
using UnityEditor.AddressableAssets;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace AibisDream.SystemEditor.Tests
{
public sealed class TypographyContractTests
{
private const string ProfileFolder = "Assets/GameContent/Common/Typography/Profiles";
[TestCase(LocaleKind.Cn, TypographyProfileKind.ZhHans)]
[TestCase(LocaleKind.Ja, TypographyProfileKind.Japanese)]
[TestCase(LocaleKind.En, TypographyProfileKind.Latin)]
[TestCase(LocaleKind.Es, TypographyProfileKind.Latin)]
[TestCase(LocaleKind.PtBr, TypographyProfileKind.Latin)]
[TestCase(LocaleKind.Ru, TypographyProfileKind.Russian)]
public void LocaleMapping_UsesExpectedProfile(LocaleKind locale, TypographyProfileKind expected)
{
Assert.That(TypographyLocaleMapping.GetProfileKind(locale), Is.EqualTo(expected));
Assert.That(
TypographyLocaleMapping.GetProfileKey(locale),
Is.EqualTo($"Typography/Profile/{expected}"));
}
[Test]
public void Resolve_MissingRoleFallsBackToBody()
{
TMP_FontAsset font = AssetDatabase.LoadAssetAtPath<TMP_FontAsset>(
"Assets/Font/Assets/ChillBitmap_16px SDF.asset");
var profile = ScriptableObject.CreateInstance<TypographyProfile>();
try
{
var body = new TypographyStyle(TypographyRole.Body, font);
profile.Configure(TypographyProfileKind.Latin, new[] { body });
Assert.That(profile.Resolve(TypographyRole.Dialogue), Is.SameAs(body));
}
finally
{
UnityEngine.Object.DestroyImmediate(profile);
}
}
[Test]
public void Binding_ReapplyUsesCapturedBaselineWithoutAccumulatingMetrics()
{
TMP_FontAsset font = AssetDatabase.LoadAssetAtPath<TMP_FontAsset>(
"Assets/Font/Assets/ChillBitmap_16px SDF.asset");
var profile = ScriptableObject.CreateInstance<TypographyProfile>();
var go = new GameObject("TypographyMetricContract", typeof(RectTransform));
try
{
TextMeshProUGUI text = go.AddComponent<TextMeshProUGUI>();
text.fontSize = 40f;
text.lineSpacing = 3f;
text.characterSpacing = 2f;
LocalizedTypography binding = go.AddComponent<LocalizedTypography>();
profile.Configure(
TypographyProfileKind.Latin,
new[]
{
new TypographyStyle(
TypographyRole.Body,
font,
fontSizeMultiplier: 0.75f,
lineSpacingOffset: 1f,
characterSpacingOffset: 2f)
});
binding.ApplyProfile(profile);
binding.ApplyProfile(profile);
Assert.That(text.fontSize, Is.EqualTo(30f));
Assert.That(text.lineSpacing, Is.EqualTo(4f));
Assert.That(text.characterSpacing, Is.EqualTo(4f));
}
finally
{
UnityEngine.Object.DestroyImmediate(go);
UnityEngine.Object.DestroyImmediate(profile);
}
}
[Test]
public void FirstRoundProfiles_AreCompleteAndAddressableByConvention()
{
foreach (TypographyProfileKind kind in Enum.GetValues(typeof(TypographyProfileKind)))
{
TypographyProfile profile = AssetDatabase.LoadAssetAtPath<TypographyProfile>(
$"{ProfileFolder}/{kind}.asset");
Assert.That(profile, Is.Not.Null, kind.ToString());
Assert.That(profile.ProfileKind, Is.EqualTo(kind));
Assert.That(profile.GetValidationErrors(), Is.Empty, kind.ToString());
Assert.That(
Enum.GetValues(typeof(TypographyRole)).Cast<TypographyRole>()
.All(role => profile.Resolve(role) != null),
Is.True,
kind.ToString());
string guid = AssetDatabase.AssetPathToGUID($"{ProfileFolder}/{kind}.asset");
var entry = AddressableAssetSettingsDefaultObject.Settings.FindAssetEntry(guid);
Assert.That(entry, Is.Not.Null, kind.ToString());
Assert.That(entry.address, Is.EqualTo($"Typography/Profile/{kind}"));
}
}
[Test]
public void TerminalPrefabs_BindEveryTextExactlyOnce()
{
foreach (string path in TerminalTypographyMigration.PrefabPaths)
{
GameObject prefab = AssetDatabase.LoadAssetAtPath<GameObject>(path);
Assert.That(prefab, Is.Not.Null, path);
TMP_Text[] texts = prefab.GetComponentsInChildren<TMP_Text>(true);
Assert.That(texts, Is.Not.Empty, path);
foreach (TMP_Text text in texts)
{
LocalizedTypography[] bindings = text.GetComponents<LocalizedTypography>();
Assert.That(bindings, Has.Length.EqualTo(1), $"{path}: {text.name}");
Assert.That(bindings[0].Role, Is.EqualTo(TypographyRole.Terminal),
$"{path}: {text.name}");
}
}
}
[Test]
public void PersistenceTerminalComposition_InheritsTypographyBindings()
{
Scene scene = SceneManager.GetSceneByPath(TerminalTypographyMigration.PersistenceScenePath);
bool openedForTest = !scene.IsValid() || !scene.isLoaded;
if (openedForTest)
{
scene = EditorSceneManager.OpenScene(
TerminalTypographyMigration.PersistenceScenePath,
OpenSceneMode.Additive);
}
try
{
Component[] hosts = scene.GetRootGameObjects()
.SelectMany(root => root.GetComponentsInChildren<Component>(true))
.Where(component => component is TerminalPanel || component is InGameTerminalPanel)
.ToArray();
Assert.That(hosts, Has.Length.EqualTo(2));
var violations = new List<string>();
foreach (Component host in hosts)
{
foreach (TMP_Text text in host.GetComponentsInChildren<TMP_Text>(true))
{
LocalizedTypography[] bindings = text.GetComponents<LocalizedTypography>();
if (bindings.Length != 1)
{
violations.Add($"{host.name}: {text.name} has {bindings.Length} bindings");
continue;
}
if (bindings[0].Role != TypographyRole.Terminal)
violations.Add($"{host.name}: {text.name} uses {bindings[0].Role}");
}
}
Assert.That(violations, Is.Empty, string.Join("\n", violations));
}
finally
{
if (openedForTest)
EditorSceneManager.CloseScene(scene, true);
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 895c6fa68b9b4ed4286362a258247f4a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 1ccca9c8cbbc67e47be376cc9db7ce55
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,229 @@
using System;
using System.Collections.Generic;
using AibisDream.Framework;
using AibisDream.UI;
using TMPro;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace AibisDream.EditorTools
{
public static class TerminalTypographyMigration
{
public const string PrefabFolder = "Assets/GameContent/Feature_MainUI/Prefabs";
public const string PersistenceScenePath = "Assets/Scenes/Persistence.unity";
// Sources must be migrated before their consumers so existing added-component
// overrides can be replaced by the component inherited from the source prefab.
public static readonly IReadOnlyList<string> PrefabPaths = new[]
{
$"{PrefabFolder}/Common/Select Item.prefab",
$"{PrefabFolder}/Common/Slider Item.prefab",
$"{PrefabFolder}/Common/TerminalButton.prefab",
$"{PrefabFolder}/Common/TerminalChapterCard.prefab",
$"{PrefabFolder}/Common/TerminalHeader.prefab",
$"{PrefabFolder}/Common/TerminalMenuItem.prefab",
$"{PrefabFolder}/Common/TerminalOptionItem.prefab",
$"{PrefabFolder}/Common/TerminalRecordItem.prefab",
$"{PrefabFolder}/Common/TerminalSliderItem.prefab",
$"{PrefabFolder}/Common/TerminalChapterPanel.prefab",
$"{PrefabFolder}/TerminalChapterPanel.prefab",
$"{PrefabFolder}/TerminalLoadingPanel.prefab",
$"{PrefabFolder}/TerminalRecordPanel.prefab",
$"{PrefabFolder}/TerminalSettingPanel.prefab",
$"{PrefabFolder}/TerminalSettingPanelMini.prefab",
$"{PrefabFolder}/TerminalStartPanel.prefab"
};
[MenuItem("Tools/AIBIS Dream/Typography/Migrate Terminal Prefabs")]
public static void MigrateTerminalPrefabs()
{
int textCount = 0;
int addedCount = 0;
int removedOverrideCount = 0;
foreach (string path in PrefabPaths)
{
MigrationResult result = MigratePrefab(path);
textCount += result.TextCount;
addedCount += result.AddedCount;
removedOverrideCount += result.RemovedOverrideCount;
}
int sceneBindingCount = MigratePersistenceScene();
AssetDatabase.SaveAssets();
Debug.Log(
$"[TerminalTypographyMigration] Migrated {PrefabPaths.Count} prefabs and " +
$"{textCount} TMP texts. Added {addedCount} bindings; removed " +
$"{removedOverrideCount} redundant nested overrides; added " +
$"{sceneBindingCount} scene-owned bindings.");
}
public static void MigrateFromCommandLine()
{
try
{
MigrateTerminalPrefabs();
}
catch (Exception exception)
{
Debug.LogException(exception);
throw;
}
}
private static MigrationResult MigratePrefab(string path)
{
GameObject root = PrefabUtility.LoadPrefabContents(path);
try
{
TMP_Text[] texts = root.GetComponentsInChildren<TMP_Text>(true);
if (texts.Length == 0)
throw new InvalidOperationException($"Terminal prefab contains no TMP text: {path}");
int addedCount = 0;
int removedOverrideCount = 0;
foreach (TMP_Text text in texts)
{
int removedForText = RemoveRedundantNestedOverrides(text);
removedOverrideCount += removedForText;
LocalizedTypography[] bindings = text.GetComponents<LocalizedTypography>();
if (bindings.Length == 0)
{
// Saving the removed override makes the source component visible
// again when this consumer prefab is next loaded.
if (removedForText > 0)
continue;
LocalizedTypography binding = text.gameObject.AddComponent<LocalizedTypography>();
binding.ConfigureForEditor(TypographyRole.Terminal);
addedCount++;
}
else if (bindings.Length == 1)
{
ConfigureIfOwnedByCurrentPrefab(bindings[0]);
}
else
{
throw new InvalidOperationException(
$"TMP text has multiple typography bindings after cleanup: {path} / {text.name}");
}
}
PrefabUtility.SaveAsPrefabAsset(root, path);
return new MigrationResult(texts.Length, addedCount, removedOverrideCount);
}
finally
{
PrefabUtility.UnloadPrefabContents(root);
}
}
private static int MigratePersistenceScene()
{
Scene scene = SceneManager.GetSceneByPath(PersistenceScenePath);
bool openedForMigration = !scene.IsValid() || !scene.isLoaded;
if (openedForMigration)
scene = EditorSceneManager.OpenScene(PersistenceScenePath, OpenSceneMode.Additive);
try
{
int addedCount = 0;
var visited = new HashSet<TMP_Text>();
foreach (GameObject root in scene.GetRootGameObjects())
{
Component[] hosts = root.GetComponentsInChildren<Component>(true);
foreach (Component host in hosts)
{
if (host is not TerminalPanel && host is not InGameTerminalPanel)
continue;
foreach (TMP_Text text in host.GetComponentsInChildren<TMP_Text>(true))
{
if (!visited.Add(text))
continue;
LocalizedTypography[] bindings = text.GetComponents<LocalizedTypography>();
if (bindings.Length == 1)
continue;
if (bindings.Length > 1)
{
throw new InvalidOperationException(
$"Scene TMP text has multiple typography bindings: {text.name}");
}
if (PrefabUtility.IsPartOfPrefabInstance(text))
{
throw new InvalidOperationException(
$"Terminal prefab instance is missing its source binding: {text.name}");
}
LocalizedTypography binding = text.gameObject.AddComponent<LocalizedTypography>();
binding.ConfigureForEditor(TypographyRole.Terminal);
addedCount++;
}
}
}
if (addedCount > 0)
{
EditorSceneManager.MarkSceneDirty(scene);
EditorSceneManager.SaveScene(scene);
}
return addedCount;
}
finally
{
if (openedForMigration)
EditorSceneManager.CloseScene(scene, true);
}
}
private static int RemoveRedundantNestedOverrides(TMP_Text text)
{
GameObject sourceObject = PrefabUtility.GetCorrespondingObjectFromSource(text.gameObject);
if (sourceObject == null || sourceObject.GetComponent<LocalizedTypography>() == null)
return 0;
int removed = 0;
LocalizedTypography[] bindings = text.GetComponents<LocalizedTypography>();
foreach (LocalizedTypography binding in bindings)
{
if (!PrefabUtility.IsAddedComponentOverride(binding))
continue;
UnityEngine.Object.DestroyImmediate(binding, true);
removed++;
}
return removed;
}
private static void ConfigureIfOwnedByCurrentPrefab(LocalizedTypography binding)
{
if (PrefabUtility.IsPartOfPrefabInstance(binding) &&
PrefabUtility.GetCorrespondingObjectFromSource(binding) != null)
{
return;
}
binding.ConfigureForEditor(TypographyRole.Terminal);
}
private readonly struct MigrationResult
{
public MigrationResult(int textCount, int addedCount, int removedOverrideCount)
{
TextCount = textCount;
AddedCount = addedCount;
RemovedOverrideCount = removedOverrideCount;
}
public int TextCount { get; }
public int AddedCount { get; }
public int RemovedOverrideCount { get; }
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 72a6797e130a7f643bd42bf648543ec2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,169 @@
using System;
using System.Collections.Generic;
using AibisDream.Framework;
using TMPro;
using UnityEditor;
using UnityEditor.AddressableAssets;
using UnityEditor.AddressableAssets.Settings;
using UnityEditor.AddressableAssets.Settings.GroupSchemas;
using UnityEngine;
namespace AibisDream.EditorTools
{
public static class TypographyFirstRoundBuilder
{
private const string GroupName = "Shared_Typography";
private const string ProfileFolder = "Assets/GameContent/Common/Typography/Profiles";
private const string WenQuanYiPath = "Assets/Font/Assets/WenQuanYi Bitmap Song 14px SDF.asset";
private const string ChillBitmapPath = "Assets/Font/Assets/ChillBitmap_16px SDF.asset";
private const string ArkPixelPath = "Assets/Font/TTF/ark-pixel-16px-proportional-latin SDF.asset";
[MenuItem("Tools/AIBIS Dream/Typography/Rebuild First Round Assets")]
public static void BuildFirstRoundAssets()
{
EnsureFolder(ProfileFolder);
TMP_FontAsset wenQuanYi = LoadRequiredFont(WenQuanYiPath);
TMP_FontAsset chillBitmap = LoadRequiredFont(ChillBitmapPath);
TMP_FontAsset arkPixel = LoadRequiredFont(ArkPixelPath);
AddressableAssetSettings settings = AddressableAssetSettingsDefaultObject.Settings;
if (settings == null)
throw new InvalidOperationException("AddressableAssetSettings 不存在。");
AddressableAssetGroup group = GetOrCreateGroup(settings);
CreateOrUpdateProfile(
settings,
group,
TypographyProfileKind.ZhHans,
wenQuanYi,
new[] { chillBitmap });
CreateOrUpdateProfile(
settings,
group,
TypographyProfileKind.Japanese,
chillBitmap,
new[] { wenQuanYi });
CreateOrUpdateProfile(
settings,
group,
TypographyProfileKind.Latin,
arkPixel,
new[] { chillBitmap });
CreateOrUpdateProfile(
settings,
group,
TypographyProfileKind.Russian,
arkPixel,
new[] { chillBitmap });
TerminalTypographyMigration.MigrateTerminalPrefabs();
EditorUtility.SetDirty(settings);
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
Debug.Log("[TypographyFirstRoundBuilder] Profile、Addressables 和首轮试点绑定已更新。");
}
public static void BuildFromCommandLine()
{
try
{
BuildFirstRoundAssets();
}
catch (Exception exception)
{
Debug.LogException(exception);
throw;
}
}
private static TMP_FontAsset LoadRequiredFont(string path)
{
TMP_FontAsset font = AssetDatabase.LoadAssetAtPath<TMP_FontAsset>(path);
if (font == null)
throw new InvalidOperationException($"找不到字体资产:{path}");
return font;
}
private static AddressableAssetGroup GetOrCreateGroup(AddressableAssetSettings settings)
{
AddressableAssetGroup group = settings.FindGroup(GroupName);
if (group == null)
{
group = settings.CreateGroup(
GroupName,
false,
false,
true,
null,
typeof(BundledAssetGroupSchema),
typeof(ContentUpdateGroupSchema));
}
BundledAssetGroupSchema bundled = group.GetSchema<BundledAssetGroupSchema>();
if (bundled != null)
{
bundled.IncludeInBuild = true;
bundled.BundleMode = BundledAssetGroupSchema.BundlePackingMode.PackTogether;
EditorUtility.SetDirty(bundled);
}
ContentUpdateGroupSchema update = group.GetSchema<ContentUpdateGroupSchema>();
if (update != null)
{
update.StaticContent = false;
EditorUtility.SetDirty(update);
}
return group;
}
private static void CreateOrUpdateProfile(
AddressableAssetSettings settings,
AddressableAssetGroup group,
TypographyProfileKind kind,
TMP_FontAsset font,
IReadOnlyCollection<TMP_FontAsset> fallbacks)
{
string path = $"{ProfileFolder}/{kind}.asset";
TypographyProfile profile = AssetDatabase.LoadAssetAtPath<TypographyProfile>(path);
if (profile == null)
{
profile = ScriptableObject.CreateInstance<TypographyProfile>();
AssetDatabase.CreateAsset(profile, path);
}
var styles = new List<TypographyStyle>();
foreach (TypographyRole role in Enum.GetValues(typeof(TypographyRole)))
{
styles.Add(new TypographyStyle(
role,
font,
fallbackFonts: fallbacks,
fontSizeMultiplier: 1f));
}
profile.Configure(kind, styles);
EditorUtility.SetDirty(profile);
string guid = AssetDatabase.AssetPathToGUID(path);
AddressableAssetEntry entry = settings.CreateOrMoveEntry(guid, group, false, false);
entry.address = $"Typography/Profile/{kind}";
entry.SetLabel("Typography", true, true, false);
}
private static void EnsureFolder(string path)
{
string[] segments = path.Split('/');
string current = segments[0];
for (int i = 1; i < segments.Length; i++)
{
string next = $"{current}/{segments[i]}";
if (!AssetDatabase.IsValidFolder(next))
AssetDatabase.CreateFolder(current, segments[i]);
current = next;
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a84a91ac052fc3444aa281c7d6a4f29e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 07d6e97bcfca9a1438f18cb05bb5d75a
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: b6b0068deeac87d4b9557c7d5d38a29d
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,53 @@
%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: 5d25e769249077945915a5c2c793b0e6, type: 3}
m_Name: Japanese
m_EditorClassIdentifier:
profileKind: 1
styles:
- role: 0
font: {fileID: 11400000, guid: 1625bd86098eb0843b1a69eb1b3354e4, type: 2}
materialPreset: {fileID: 0}
fallbackFonts: []
fontSizeMultiplier: 1
lineSpacingOffset: 0
characterSpacingOffset: 0
- role: 1
font: {fileID: 11400000, guid: c7fc4cbba4b38a04c8985d4dbce12a73, type: 2}
materialPreset: {fileID: 0}
fallbackFonts: []
fontSizeMultiplier: 1
lineSpacingOffset: 0
characterSpacingOffset: 0
- role: 2
font: {fileID: 11400000, guid: 1625bd86098eb0843b1a69eb1b3354e4, type: 2}
materialPreset: {fileID: 0}
fallbackFonts: []
fontSizeMultiplier: 1
lineSpacingOffset: 0
characterSpacingOffset: 0
- role: 3
font: {fileID: 11400000, guid: 1625bd86098eb0843b1a69eb1b3354e4, type: 2}
materialPreset: {fileID: 0}
fallbackFonts:
- {fileID: 11400000, guid: f242fb3dde1933640859f1c54591c9c8, type: 2}
fontSizeMultiplier: 1
lineSpacingOffset: 0
characterSpacingOffset: 0
- role: 4
font: {fileID: 11400000, guid: 1625bd86098eb0843b1a69eb1b3354e4, type: 2}
materialPreset: {fileID: 0}
fallbackFonts:
- {fileID: 11400000, guid: f242fb3dde1933640859f1c54591c9c8, type: 2}
fontSizeMultiplier: 1
lineSpacingOffset: 0
characterSpacingOffset: 0
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 67cd590df91846b4d81698d68426e616
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,56 @@
%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: 5d25e769249077945915a5c2c793b0e6, type: 3}
m_Name: Latin
m_EditorClassIdentifier:
profileKind: 2
styles:
- role: 0
font: {fileID: 11400000, guid: 1625bd86098eb0843b1a69eb1b3354e4, type: 2}
materialPreset: {fileID: 0}
fallbackFonts:
- {fileID: 11400000, guid: c7fc4cbba4b38a04c8985d4dbce12a73, type: 2}
fontSizeMultiplier: 1
lineSpacingOffset: 0
characterSpacingOffset: 0
- role: 1
font: {fileID: 11400000, guid: c7fc4cbba4b38a04c8985d4dbce12a73, type: 2}
materialPreset: {fileID: 0}
fallbackFonts:
- {fileID: 11400000, guid: 1625bd86098eb0843b1a69eb1b3354e4, type: 2}
fontSizeMultiplier: 1
lineSpacingOffset: 0
characterSpacingOffset: 0
- role: 2
font: {fileID: 11400000, guid: 1625bd86098eb0843b1a69eb1b3354e4, type: 2}
materialPreset: {fileID: 0}
fallbackFonts:
- {fileID: 11400000, guid: c7fc4cbba4b38a04c8985d4dbce12a73, type: 2}
fontSizeMultiplier: 1
lineSpacingOffset: 0
characterSpacingOffset: 0
- role: 3
font: {fileID: 11400000, guid: 1625bd86098eb0843b1a69eb1b3354e4, type: 2}
materialPreset: {fileID: 0}
fallbackFonts:
- {fileID: 11400000, guid: c7fc4cbba4b38a04c8985d4dbce12a73, type: 2}
fontSizeMultiplier: 1
lineSpacingOffset: 0
characterSpacingOffset: 0
- role: 4
font: {fileID: 11400000, guid: 1625bd86098eb0843b1a69eb1b3354e4, type: 2}
materialPreset: {fileID: 0}
fallbackFonts:
- {fileID: 11400000, guid: c7fc4cbba4b38a04c8985d4dbce12a73, type: 2}
fontSizeMultiplier: 1
lineSpacingOffset: 0
characterSpacingOffset: 0
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: d53382c947b5edc4a88216c5fcf37b38
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,54 @@
%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: 5d25e769249077945915a5c2c793b0e6, type: 3}
m_Name: Russian
m_EditorClassIdentifier:
profileKind: 3
styles:
- role: 0
font: {fileID: 11400000, guid: 1625bd86098eb0843b1a69eb1b3354e4, type: 2}
materialPreset: {fileID: 0}
fallbackFonts: []
fontSizeMultiplier: 1
lineSpacingOffset: 0
characterSpacingOffset: 0
- role: 1
font: {fileID: 11400000, guid: c7fc4cbba4b38a04c8985d4dbce12a73, type: 2}
materialPreset: {fileID: 0}
fallbackFonts: []
fontSizeMultiplier: 1
lineSpacingOffset: 0
characterSpacingOffset: 0
- role: 2
font: {fileID: 11400000, guid: 1625bd86098eb0843b1a69eb1b3354e4, type: 2}
materialPreset: {fileID: 0}
fallbackFonts:
- {fileID: 11400000, guid: c7fc4cbba4b38a04c8985d4dbce12a73, type: 2}
fontSizeMultiplier: 1
lineSpacingOffset: 0
characterSpacingOffset: 0
- role: 3
font: {fileID: 11400000, guid: 1625bd86098eb0843b1a69eb1b3354e4, type: 2}
materialPreset: {fileID: 0}
fallbackFonts:
- {fileID: 11400000, guid: c7fc4cbba4b38a04c8985d4dbce12a73, type: 2}
fontSizeMultiplier: 1
lineSpacingOffset: 0
characterSpacingOffset: 0
- role: 4
font: {fileID: 11400000, guid: 1625bd86098eb0843b1a69eb1b3354e4, type: 2}
materialPreset: {fileID: 0}
fallbackFonts:
- {fileID: 11400000, guid: c7fc4cbba4b38a04c8985d4dbce12a73, type: 2}
fontSizeMultiplier: 1
lineSpacingOffset: 0
characterSpacingOffset: 0
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: f82150d4b378dc441884eb47d4f67b7c
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,56 @@
%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: 5d25e769249077945915a5c2c793b0e6, type: 3}
m_Name: ZhHans
m_EditorClassIdentifier:
profileKind: 0
styles:
- role: 0
font: {fileID: 11400000, guid: c7fc4cbba4b38a04c8985d4dbce12a73, type: 2}
materialPreset: {fileID: 0}
fallbackFonts:
- {fileID: 11400000, guid: c7fc4cbba4b38a04c8985d4dbce12a73, type: 2}
fontSizeMultiplier: 1
lineSpacingOffset: 0
characterSpacingOffset: 0
- role: 1
font: {fileID: 11400000, guid: c7fc4cbba4b38a04c8985d4dbce12a73, type: 2}
materialPreset: {fileID: 0}
fallbackFonts:
- {fileID: 11400000, guid: c7fc4cbba4b38a04c8985d4dbce12a73, type: 2}
fontSizeMultiplier: 1
lineSpacingOffset: 0
characterSpacingOffset: 0
- role: 2
font: {fileID: 11400000, guid: 1625bd86098eb0843b1a69eb1b3354e4, type: 2}
materialPreset: {fileID: 0}
fallbackFonts:
- {fileID: 11400000, guid: c7fc4cbba4b38a04c8985d4dbce12a73, type: 2}
fontSizeMultiplier: 1
lineSpacingOffset: 0
characterSpacingOffset: 0
- role: 3
font: {fileID: 11400000, guid: 1625bd86098eb0843b1a69eb1b3354e4, type: 2}
materialPreset: {fileID: 0}
fallbackFonts:
- {fileID: 11400000, guid: c7fc4cbba4b38a04c8985d4dbce12a73, type: 2}
fontSizeMultiplier: 1
lineSpacingOffset: 0
characterSpacingOffset: 0
- role: 4
font: {fileID: 11400000, guid: 1625bd86098eb0843b1a69eb1b3354e4, type: 2}
materialPreset: {fileID: 0}
fallbackFonts:
- {fileID: 11400000, guid: c7fc4cbba4b38a04c8985d4dbce12a73, type: 2}
fontSizeMultiplier: 1
lineSpacingOffset: 0
characterSpacingOffset: 0
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: e41dce1e1d6e0564b95054b5c8b8bb4c
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -1,5 +1,6 @@
using System.Collections; using System.Collections;
using System; using System;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using AibisDream.Utility; using AibisDream.Utility;
using UnityEngine; using UnityEngine;
@@ -11,22 +12,14 @@ using UnityEditor;
namespace AibisDream.Framework namespace AibisDream.Framework
{ {
/// <summary>
/// csv 本地化列类别。集中声明"哪些 Locale 归到哪一列",csv 数据类按此枚举取字段。
/// </summary>
public enum LocaleKind
{
Cn,
En,
Ja,
Ru,
Es,
PtBr
}
public static class LocalizationKit public static class LocalizationKit
{ {
private const string LocalizationPrefix = "l10n."; private const string LocalizationPrefix = "l10n.";
private static Task<bool> currentLanguageSwitchTask;
private static int languageSwitchVersion;
private static bool managedLocaleChange;
private static bool typographyLoaderConfigured;
private static bool localeChangedSubscribed;
public static string CurrentLanguageCode { get; private set; } = "zh-Hans"; public static string CurrentLanguageCode { get; private set; } = "zh-Hans";
public static Locale CurrentLocale => LocalizationSettings.SelectedLocale; public static Locale CurrentLocale => LocalizationSettings.SelectedLocale;
@@ -78,8 +71,14 @@ namespace AibisDream.Framework
{ {
#if UNITY_EDITOR #if UNITY_EDITOR
EditorApplication.playModeStateChanged += OnPlayModeChanged; EditorApplication.playModeStateChanged += OnPlayModeChanged;
if (EditorApplication.isPlayingOrWillChangePlaymode)
{
EnsureTypographyLoaderConfigured();
SubscribeLocaleChanged();
RefreshCurrentLanguageCodeAsync();
}
#else #else
LocalizationSettings.SelectedLocaleChanged += OnLocaleChanged; SubscribeLocaleChanged();
RefreshCurrentLanguageCodeAsync(); RefreshCurrentLanguageCodeAsync();
#endif #endif
} }
@@ -88,6 +87,14 @@ namespace AibisDream.Framework
{ {
UpdateCurrentLanguageCode(locale); UpdateCurrentLanguageCode(locale);
EnumEventSystem.Global.Send(SettingChangeEvent.LocaleChanged, locale); EnumEventSystem.Global.Send(SettingChangeEvent.LocaleChanged, locale);
if (!managedLocaleChange && locale != null)
{
EnsureTypographyLoaderConfigured();
LocaleKind localeKind = GetLocaleKind(locale.Identifier.Code);
if (!TypographyService.IsActiveFor(localeKind))
currentLanguageSwitchTask = TypographyService.LoadAndActivateAsync(localeKind);
}
} }
private static void UpdateCurrentLanguageCode(Locale locale) private static void UpdateCurrentLanguageCode(Locale locale)
@@ -109,32 +116,95 @@ namespace AibisDream.Framework
{ {
if (playMode == PlayModeStateChange.EnteredPlayMode) if (playMode == PlayModeStateChange.EnteredPlayMode)
{ {
LocalizationSettings.SelectedLocaleChanged += OnLocaleChanged; EnsureTypographyLoaderConfigured();
SubscribeLocaleChanged();
RefreshCurrentLanguageCodeAsync(); RefreshCurrentLanguageCodeAsync();
} }
else if (playMode == PlayModeStateChange.ExitingPlayMode) else if (playMode == PlayModeStateChange.ExitingPlayMode)
{ {
LocalizationSettings.SelectedLocaleChanged -= OnLocaleChanged; UnsubscribeLocaleChanged();
TypographyService.ResetForTests();
typographyLoaderConfigured = false;
currentLanguageSwitchTask = null;
} }
} }
#endif #endif
public static async void SwitchLanguage(string localeCode) public static void SwitchLanguage(string localeCode)
{
_ = SwitchLanguageAsync(localeCode);
}
public static Task<bool> SwitchLanguageAsync(string localeCode)
{
int requestVersion = Interlocked.Increment(ref languageSwitchVersion);
currentLanguageSwitchTask = SwitchLanguageCoreAsync(localeCode, requestVersion);
return currentLanguageSwitchTask;
}
private static async Task<bool> SwitchLanguageCoreAsync(string localeCode, int requestVersion)
{ {
// 等待本地化系统初始化完成
await LocalizationSettings.InitializationOperation.Task; await LocalizationSettings.InitializationOperation.Task;
var selectedLocale = var selectedLocale =
LocalizationSettings.AvailableLocales.Locales.Find(locale => locale.Identifier.Code == localeCode); LocalizationSettings.AvailableLocales.Locales.Find(locale => locale.Identifier.Code == localeCode);
if (!selectedLocale)
{
Debug.LogWarning($"Locale with code {localeCode} not found.");
return false;
}
if (selectedLocale) EnsureTypographyLoaderConfigured();
bool typographyReady = await TypographyService.LoadAndActivateAsync(GetLocaleKind(localeCode));
if (requestVersion != languageSwitchVersion)
return false;
managedLocaleChange = true;
try
{ {
LocalizationSettings.SelectedLocale = selectedLocale; LocalizationSettings.SelectedLocale = selectedLocale;
} }
else finally
{ {
Debug.LogWarning($"Locale with code {localeCode} not found."); managedLocaleChange = false;
} }
await Task.Yield();
Canvas.ForceUpdateCanvases();
if (!typographyReady)
{
Debug.LogWarning(
$"[LocalizationKit] Locale 已切换为 {localeCode},但字体 Profile 加载失败,保留上一套字体。");
}
return true;
}
private static void EnsureTypographyLoaderConfigured()
{
if (typographyLoaderConfigured)
return;
TypographyService.ConfigureLoader(new ResourceSystemTypographyProfileLoader());
typographyLoaderConfigured = true;
}
private static void SubscribeLocaleChanged()
{
if (localeChangedSubscribed)
return;
LocalizationSettings.SelectedLocaleChanged += OnLocaleChanged;
localeChangedSubscribed = true;
}
private static void UnsubscribeLocaleChanged()
{
if (!localeChangedSubscribed)
return;
LocalizationSettings.SelectedLocaleChanged -= OnLocaleChanged;
localeChangedSubscribed = false;
} }
/// <summary> /// <summary>
@@ -250,6 +320,24 @@ namespace AibisDream.Framework
public static IEnumerator WaitUntilReady() public static IEnumerator WaitUntilReady()
{ {
yield return LocalizationSettings.InitializationOperation; yield return LocalizationSettings.InitializationOperation;
Task<bool> languageTask;
do
{
languageTask = currentLanguageSwitchTask;
while (languageTask != null && !languageTask.IsCompleted)
yield return null;
} while (languageTask != currentLanguageSwitchTask);
EnsureTypographyLoaderConfigured();
if (!TypographyService.IsReady)
{
Task<bool> typographyTask = TypographyService.LoadAndActivateAsync(
GetLocaleKind(LocalizationSettings.SelectedLocale?.Identifier.Code));
while (!typographyTask.IsCompleted)
yield return null;
}
yield return null; yield return null;
} }
@@ -0,0 +1,33 @@
using System.Threading.Tasks;
using UnityEngine.ResourceManagement.AsyncOperations;
namespace AibisDream.Framework
{
internal sealed class ResourceSystemTypographyProfileLoader : ITypographyProfileLoader
{
public async Task<TypographyProfile> LoadAsync(string key)
{
AsyncOperationHandle<TypographyProfile> handle =
ResourceSystem.LoadPersistentAsync<TypographyProfile>(key);
try
{
await handle.Task;
if (handle.Status == AsyncOperationStatus.Succeeded && handle.Result != null)
return handle.Result;
ResourceSystem.EarlyReleasePersistent(key);
return null;
}
catch
{
ResourceSystem.EarlyReleasePersistent(key);
throw;
}
}
public void Release(string key)
{
ResourceSystem.EarlyReleasePersistent(key);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c9dd51810ef0cc94398175f3597a978e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -140,6 +140,15 @@ namespace AibisDream.Framework
public static void EarlyRelease<T>(AsyncOperationHandle<T> handle) public static void EarlyRelease<T>(AsyncOperationHandle<T> handle)
=> ActiveLoader.EarlyRelease(handle); => ActiveLoader.EarlyRelease(handle);
/// <summary>
/// 提前释放持久化 Loader 中指定 Key 的资源。
/// </summary>
public static void EarlyReleasePersistent(string key)
{
if (_persistentLoader != null)
_persistentLoader.EarlyRelease(key);
}
#endregion #endregion
} }
} }
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 2964c7221d2d9d44aaa0da470729509b
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,16 @@
{
"name": "AibisDream.Typography.Runtime",
"rootNamespace": "AibisDream.Framework",
"references": [
"Unity.TextMeshPro"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 118b0de59b1eb6e438aec021b8e3f39b
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,10 @@
using System.Threading.Tasks;
namespace AibisDream.Framework
{
public interface ITypographyProfileLoader
{
Task<TypographyProfile> LoadAsync(string key);
void Release(string key);
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0de13fe1f11da524abe267418428556b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+40
View File
@@ -0,0 +1,40 @@
using System;
namespace AibisDream.Framework
{
/// <summary>
/// 项目支持的本地化语言类别。多个 Locale 可以共享同一套排版资源。
/// </summary>
public enum LocaleKind
{
Cn,
En,
Ja,
Ru,
Es,
PtBr
}
public enum TypographyProfileKind
{
ZhHans,
Japanese,
Latin,
Russian
}
public static class TypographyLocaleMapping
{
public static TypographyProfileKind GetProfileKind(LocaleKind localeKind) => localeKind switch
{
LocaleKind.Cn => TypographyProfileKind.ZhHans,
LocaleKind.Ja => TypographyProfileKind.Japanese,
LocaleKind.Ru => TypographyProfileKind.Russian,
LocaleKind.En or LocaleKind.Es or LocaleKind.PtBr => TypographyProfileKind.Latin,
_ => throw new ArgumentOutOfRangeException(nameof(localeKind), localeKind, null)
};
public static string GetProfileKey(LocaleKind localeKind) =>
$"Typography/Profile/{GetProfileKind(localeKind)}";
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 65c00ba464255484193f0350427b5e11
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,89 @@
using TMPro;
using UnityEngine;
namespace AibisDream.Framework
{
[DisallowMultipleComponent]
[RequireComponent(typeof(TMP_Text))]
public sealed class LocalizedTypography : MonoBehaviour
{
[SerializeField] private TypographyRole role = TypographyRole.Body;
[SerializeField] private bool applyMetrics = true;
[SerializeField, HideInInspector] private bool baselineCaptured;
[SerializeField, HideInInspector] private float baseFontSize;
[SerializeField, HideInInspector] private float baseLineSpacing;
[SerializeField, HideInInspector] private float baseCharacterSpacing;
private TMP_Text target;
public TypographyRole Role => role;
public TMP_Text Target => target != null ? target : target = GetComponent<TMP_Text>();
private void Reset()
{
target = GetComponent<TMP_Text>();
CaptureBaseline();
}
private void Awake()
{
target = GetComponent<TMP_Text>();
// Prefab variants can override TMP metrics without overriding the
// baseline serialized by the source prefab. Capture the fully
// resolved instance values before the first profile is applied.
CaptureBaseline();
}
private void OnEnable()
{
if (!baselineCaptured)
CaptureBaseline();
TypographyService.Register(this);
}
private void OnDisable()
{
TypographyService.Unregister(this);
}
public void ApplyProfile(TypographyProfile profile)
{
TMP_Text text = Target;
TypographyStyle style = profile != null ? profile.Resolve(role) : null;
if (text == null || style == null || !style.IsUsable)
return;
if (!baselineCaptured)
CaptureBaseline();
TypographyService.ApplyStyle(
text,
style,
applyMetrics,
baseFontSize,
baseLineSpacing,
baseCharacterSpacing);
}
public void CaptureBaseline()
{
TMP_Text text = Target;
if (text == null)
return;
baseFontSize = text.fontSize;
baseLineSpacing = text.lineSpacing;
baseCharacterSpacing = text.characterSpacing;
baselineCaptured = true;
}
#if UNITY_EDITOR
public void ConfigureForEditor(TypographyRole configuredRole, bool shouldApplyMetrics = true)
{
role = configuredRole;
applyMetrics = shouldApplyMetrics;
CaptureBaseline();
UnityEditor.EditorUtility.SetDirty(this);
}
#endif
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9fe642db0d3d2b54fa6d1afaf8c85e4a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,84 @@
using System.Collections.Generic;
using TMPro;
using UnityEngine;
namespace AibisDream.Framework
{
[CreateAssetMenu(fileName = "TypographyProfile", menuName = "AIBIS Dream/Typography/Profile")]
public sealed class TypographyProfile : ScriptableObject
{
[SerializeField] private TypographyProfileKind profileKind;
[SerializeField] private List<TypographyStyle> styles = new();
public TypographyProfileKind ProfileKind => profileKind;
public IReadOnlyList<TypographyStyle> Styles => styles;
public TypographyStyle Resolve(TypographyRole role)
{
TypographyStyle body = null;
if (styles == null)
return null;
foreach (TypographyStyle style in styles)
{
if (style == null || !style.IsUsable)
continue;
if (style.Role == role)
return style;
if (style.Role == TypographyRole.Body)
body = style;
}
return body;
}
public IEnumerable<string> GetValidationErrors()
{
if (styles == null || styles.Count == 0)
{
yield return $"{name}: 没有配置任何 TypographyStyle。";
yield break;
}
var seenRoles = new HashSet<TypographyRole>();
foreach (TypographyStyle style in styles)
{
if (style == null)
{
yield return $"{name}: 存在空的 TypographyStyle。";
continue;
}
if (!seenRoles.Add(style.Role))
yield return $"{name}: Role {style.Role} 重复配置。";
if (!style.IsUsable)
yield return $"{name}: Role {style.Role} 没有字体。";
}
if (!seenRoles.Contains(TypographyRole.Body))
yield return $"{name}: 缺少 Body 回退样式。";
}
public void Configure(TypographyProfileKind kind, IEnumerable<TypographyStyle> configuredStyles)
{
profileKind = kind;
styles = configuredStyles != null
? new List<TypographyStyle>(configuredStyles)
: new List<TypographyStyle>();
}
internal void ApplyFallbacks()
{
if (styles == null)
return;
var configuredFonts = new HashSet<TMP_FontAsset>();
foreach (TypographyStyle style in styles)
{
if (style?.Font != null && configuredFonts.Add(style.Font))
style.ApplyFallbacks();
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5d25e769249077945915a5c2c793b0e6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,11 @@
namespace AibisDream.Framework
{
public enum TypographyRole
{
Body,
Dialogue,
Terminal,
WorldText,
MiniGame
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e847b9aaee116be4c8d8d135ae0defb9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,179 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using TMPro;
using UnityEngine;
namespace AibisDream.Framework
{
public static class TypographyService
{
private static readonly HashSet<LocalizedTypography> Bindings = new();
private static ITypographyProfileLoader loader;
private static int switchVersion;
private static string currentProfileKey;
private static string latestRequestedProfileKey;
private static TypographyProfile currentProfile;
public static TypographyProfile CurrentProfile => currentProfile;
public static bool IsReady => currentProfile != null;
public static string CurrentProfileKey => currentProfileKey;
public static void ConfigureLoader(ITypographyProfileLoader profileLoader)
{
loader = profileLoader ?? throw new ArgumentNullException(nameof(profileLoader));
}
public static bool IsActiveFor(LocaleKind localeKind) =>
currentProfile != null &&
string.Equals(
currentProfileKey,
TypographyLocaleMapping.GetProfileKey(localeKind),
StringComparison.Ordinal);
public static TypographyStyle Resolve(TypographyRole role) => currentProfile?.Resolve(role);
public static bool Apply(TMP_Text text, TypographyRole role, bool applyMetrics = true)
{
if (text == null)
return false;
TypographyStyle style = Resolve(role);
if (style == null || !style.IsUsable)
return false;
ApplyStyle(text, style, applyMetrics, text.fontSize, text.lineSpacing, text.characterSpacing);
return true;
}
public static void Register(LocalizedTypography binding)
{
if (binding == null)
return;
Bindings.Add(binding);
if (currentProfile != null)
binding.ApplyProfile(currentProfile);
}
public static void Unregister(LocalizedTypography binding)
{
if (binding != null)
Bindings.Remove(binding);
}
public static async Task<bool> LoadAndActivateAsync(LocaleKind localeKind)
{
int requestVersion = ++switchVersion;
string profileKey = TypographyLocaleMapping.GetProfileKey(localeKind);
latestRequestedProfileKey = profileKey;
if (IsActiveFor(localeKind))
return true;
if (loader == null)
{
Debug.LogError("[TypographyService] 尚未配置 ITypographyProfileLoader。");
return false;
}
TypographyProfile loadedProfile;
try
{
loadedProfile = await loader.LoadAsync(profileKey);
}
catch (Exception exception)
{
Debug.LogError($"[TypographyService] 加载字体 Profile 失败:{profileKey}\n{exception}");
return false;
}
if (requestVersion != switchVersion)
{
if (!string.Equals(profileKey, currentProfileKey, StringComparison.Ordinal) &&
!string.Equals(profileKey, latestRequestedProfileKey, StringComparison.Ordinal))
{
loader.Release(profileKey);
}
return false;
}
if (loadedProfile == null)
{
Debug.LogError($"[TypographyService] 字体 Profile 不存在或加载失败:{profileKey}");
return false;
}
string previousKey = currentProfileKey;
loadedProfile.ApplyFallbacks();
currentProfile = loadedProfile;
currentProfileKey = profileKey;
RefreshBindings();
Canvas.ForceUpdateCanvases();
await Task.Yield();
if (!string.IsNullOrEmpty(previousKey) &&
!string.Equals(previousKey, profileKey, StringComparison.Ordinal))
{
loader.Release(previousKey);
}
return true;
}
internal static void ApplyStyle(
TMP_Text text,
TypographyStyle style,
bool applyMetrics,
float baseFontSize,
float baseLineSpacing,
float baseCharacterSpacing)
{
text.font = style.Font;
text.fontSharedMaterial = style.MaterialPreset != null
? style.MaterialPreset
: style.Font.material;
if (applyMetrics)
{
text.fontSize = baseFontSize * style.FontSizeMultiplier;
text.lineSpacing = baseLineSpacing + style.LineSpacingOffset;
text.characterSpacing = baseCharacterSpacing + style.CharacterSpacingOffset;
}
text.SetAllDirty();
if (text.gameObject.activeInHierarchy)
text.ForceMeshUpdate();
}
private static void RefreshBindings()
{
if (Bindings.Count == 0)
return;
var stale = new List<LocalizedTypography>();
foreach (LocalizedTypography binding in Bindings)
{
if (binding == null)
stale.Add(binding);
else
binding.ApplyProfile(currentProfile);
}
foreach (LocalizedTypography binding in stale)
Bindings.Remove(binding);
}
#if UNITY_EDITOR
public static void ResetForTests()
{
if (loader != null && !string.IsNullOrEmpty(currentProfileKey))
loader.Release(currentProfileKey);
loader = null;
currentProfile = null;
currentProfileKey = null;
latestRequestedProfileKey = null;
switchVersion++;
Bindings.Clear();
}
#endif
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9c15158900e46184da7832703b02d0a1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,65 @@
using System;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
namespace AibisDream.Framework
{
[Serializable]
public sealed class TypographyStyle
{
[SerializeField] private TypographyRole role;
[SerializeField] private TMP_FontAsset font;
[SerializeField] private Material materialPreset;
[SerializeField] private List<TMP_FontAsset> fallbackFonts = new();
[SerializeField, Min(0.01f)] private float fontSizeMultiplier = 1f;
[SerializeField] private float lineSpacingOffset;
[SerializeField] private float characterSpacingOffset;
public TypographyStyle(
TypographyRole role,
TMP_FontAsset font,
Material materialPreset = null,
IEnumerable<TMP_FontAsset> fallbackFonts = null,
float fontSizeMultiplier = 1f,
float lineSpacingOffset = 0f,
float characterSpacingOffset = 0f)
{
this.role = role;
this.font = font;
this.materialPreset = materialPreset;
this.fallbackFonts = fallbackFonts != null
? new List<TMP_FontAsset>(fallbackFonts)
: new List<TMP_FontAsset>();
this.fontSizeMultiplier = Mathf.Max(0.01f, fontSizeMultiplier);
this.lineSpacingOffset = lineSpacingOffset;
this.characterSpacingOffset = characterSpacingOffset;
}
public TypographyRole Role => role;
public TMP_FontAsset Font => font;
public Material MaterialPreset => materialPreset;
public IReadOnlyList<TMP_FontAsset> FallbackFonts => fallbackFonts;
public float FontSizeMultiplier => Mathf.Max(0.01f, fontSizeMultiplier);
public float LineSpacingOffset => lineSpacingOffset;
public float CharacterSpacingOffset => characterSpacingOffset;
public bool IsUsable => font != null;
internal void ApplyFallbacks()
{
if (font == null)
return;
font.fallbackFontAssetTable ??= new List<TMP_FontAsset>();
font.fallbackFontAssetTable.Clear();
if (fallbackFonts == null)
return;
foreach (TMP_FontAsset fallback in fallbackFonts)
{
if (fallback != null && fallback != font && !font.fallbackFontAssetTable.Contains(fallback))
font.fallbackFontAssetTable.Add(fallback);
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 50206284a91c06448b41453dbf51dc44
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -83,7 +83,7 @@ namespace AibisDream.UI
private void PrepareContent() private void PrepareContent()
{ {
_owner?.SetHeader("terminal_header_chapter", "REPAIR_STATION_07 - AUTH_VERIFIED"); _owner?.SetHeader("terminal_header_chapter", "AUTH_VERIFIED");
LoadChapters(); LoadChapters();
RefreshViews(); RefreshViews();
} }
@@ -74,7 +74,7 @@ namespace AibisDream.UI
public void Show() public void Show()
{ {
_owner?.SetHeader("termnial_header_record", "REPAIR_STATION_07 - AUTH_VERIFIED"); _owner?.SetHeader("termnial_header_record", "AUTH_VERIFIED");
if (panelAnimator != null) if (panelAnimator != null)
{ {
panelAnimator.Show(onComplete: () => StartCoroutine(DrawRecordsWhenReady())); panelAnimator.Show(onComplete: () => StartCoroutine(DrawRecordsWhenReady()));
@@ -38,7 +38,7 @@ namespace AibisDream.UI
new UIFormOption { prop = "ja-JP", label = "日本語" }, new UIFormOption { prop = "ja-JP", label = "日本語" },
new UIFormOption { prop = "ru", label = "Русский" }, new UIFormOption { prop = "ru", label = "Русский" },
new UIFormOption { prop = "es", label = "Español" }, new UIFormOption { prop = "es", label = "Español" },
new UIFormOption { prop = "pt-BR", label = "Português (Brasil)" }, new UIFormOption { prop = "pt-BR", label = "Português" },
}; };
private static readonly UIFormOption[] TextSpeedOptions = private static readonly UIFormOption[] TextSpeedOptions =
@@ -115,7 +115,7 @@ namespace AibisDream.UI
private void PrepareContent() private void PrepareContent()
{ {
_owner?.SetHeader("terminal_header_setting", "REPAIR_STATION_07 - AUTH_VERIFIED"); _owner?.SetHeader("terminal_header_setting", "AUTH_VERIFIED");
InitControls(); InitControls();
RefreshControls(); RefreshControls();
RefreshFooterButtons(); RefreshFooterButtons();
@@ -49,11 +49,11 @@ namespace AibisDream.UI
if (panelAnimator != null) if (panelAnimator != null)
{ {
panelAnimator.Show(prepare: () => _owner?.SetHeader("termnial_header_start", "REPAIR_STATION_07 - AUTH_VERIFIED")); panelAnimator.Show(prepare: () => _owner?.SetHeader("termnial_header_start", "AUTH_VERIFIED"));
} }
else else
{ {
_owner?.SetHeader("termnial_header_start", "REPAIR_STATION_07 - AUTH_VERIFIED"); _owner?.SetHeader("termnial_header_start", "AUTH_VERIFIED");
gameObject.SetActive(true); gameObject.SetActive(true);
} }
} }
+70 -71
View File
@@ -1,77 +1,76 @@
通过key索引配置,中文名,英文名,日文名,俄文名,西班牙文名,巴西葡萄牙文名,"显示位置,Default使用默认,Screen插线屏幕,OldBox基本对话框",人物角色(Player/MainActor/Aside,名称显示时的颜色,头像Pic路径,语音资源路径,气泡序号(默认0 通过key索引配置,中文名,英文名,日文名,俄文名,西班牙文名,巴西葡萄牙文名,"显示位置,Default使用默认,Screen插线屏幕,OldBox基本对话框",人物角色(Player/MainActor/Aside,名称显示时的颜色,头像Pic路径,语音资源路径,气泡序号(默认0
Key,Cn,En,Ja,Ru,Es,PtBr,DialogViewType,Role,NameColor,HeadPicPath,VoicePath,BubbleIdx Key,Cn,En,Ja,Ru,Es,PtBr,DialogViewType,Role,NameColor,HeadPicPath,VoicePath,BubbleIdx
jg,警告,Warning,Warning,,,,Default,MainActor,#7ae622,,VO/voice_DN, jg,警告,Warning,警告,Предупреждение,Advertencia,Aviso,Default,MainActor,#7ae622,,VO/voice_DN,
,,Door,,,,,Default,MainActor,#7ae622,,, ,,Door,ドア,Дверь,Puerta,Porta,Default,MainActor,#7ae622,,,
矩形,黑暗,Darkness,,,,,Default,MainActor,#7ae622,,VO/voice_DN, 矩形,黑暗,Darkness,,Тьма,Oscuridad,Escuridão,Default,MainActor,#7ae622,,VO/voice_DN,
记忆,记忆,Memory,,,,,Default,MainActor,#7ae622,,, 记忆,记忆,Memory,記憶,Память,Memoria,Memória,Default,MainActor,#7ae622,,,
peipei,佩佩,Peipei,イペイ,,,,Default,MainActor,#00FFFF,peipei_head,VO/voice_PP, peipei,佩佩,PeiPei,,Пэйпэй,Peipei,Peipei,Default,MainActor,#00FFFF,peipei_head,VO/voice_PP,
乌鸦,乌鸦,Crow,,,,,Default,MainActor,#e6ddc1,,VO/voice_WYd, 乌鸦,乌鸦,Crow,カラス,Ворон,Cuervo,Corvo,Default,MainActor,#e6ddc1,,VO/voice_WYd,
wyd?,???,???,???,,,,Default,MainActor,#e6ddc1,,VO/voice_WYd, wyd?,???,???,???,???,???,???,Default,MainActor,#e6ddc1,,VO/voice_WYd,
马尾辫女孩,马尾辫女孩,Girl with ponytail,,,,,Default,MainActor,,,VO/voice_JJ, 马尾辫女孩,马尾辫女孩,Ponytail Girl,ポニーテールの女の子,Девочка с хвостиком,Chica de la coleta,Garota de Rabo de Cavalo,Default,MainActor,,,VO/voice_JJ,
pp,佩佩,Peipei,イペイ,,,,Default,MainActor,#00FFFF,peipei_head,VO/voice_PP, pp,佩佩,PeiPei,,Пэйпэй,Peipei,Peipei,Default,MainActor,#00FFFF,peipei_head,VO/voice_PP,
ppm,佩佩,Peipei,イペイ,,,,Default,MainActor,#00FFFF,peipei_head,VO/voice_PPm, ppm,佩佩,PeiPei,,Пэйпэй,Peipei,Peipei,Default,MainActor,#00FFFF,peipei_head,VO/voice_PPm,
ppd,女孩,Girl,,,,,Default,MainActor,,,VO/voice_PPd, ppd,女孩,Girl,女の子,Девочка,Niña,Garota,Default,MainActor,,,VO/voice_PPd,
ppd?,女孩,Girl,,,,,Default,MainActor,,,VO/voice_PPd, ppd?,女孩,Girl,女の子,Девочка,Niña,Garota,Default,MainActor,,,VO/voice_PPd,
me,路斯,Luth,ルース,,,,Default,Player,,me_head,VO/voice_ME, me,路斯,Ruth,ルース,Лус,Ruth,Lusi,Default,Player,,me_head,VO/voice_ME,
med,路斯,Luth,ルース,,,,Default,Player,,me_head,VO/voice_MEd, med,路斯,Ruth,ルース,Лус,Ruth,Lusi,Default,Player,,me_head,VO/voice_MEd,
mem,路斯,Luth,ルース,,,,Default,Player,,me_head,VO/voice_MEm, mem,路斯,Ruth,ルース,Лус,Ruth,Lusi,Default,Player,,me_head,VO/voice_MEm,
mecd,女孩,Young Girl,,,,,Default,MainActor,,,VO/voice_MECd, mecd,女孩,Girl,の子,Девочка,Niña,Garota,Default,MainActor,,,VO/voice_MECd,
mecd?,???,???,???,,,,Default,MainActor,,,VO/voice_MECd, mecd?,???,???,???,???,???,???,Default,MainActor,,,VO/voice_MECd,
bb,BB机,Beeper,,,,,Default,Player,,me_head,, bb,BB机,Pager,ポケベル,Пейджер,Bíper,Pager,Default,Player,,me_head,,
jj,姐姐,Sister,姉さん,,,,Default,MainActor,,me_head,VO/voice_JJ, jj,姐姐,Sister,姉さん,Сестра,Hermana mayor,Irmã,Default,MainActor,,me_head,VO/voice_JJ,
jjd,姐姐,Sister,姉さん,,,,Default,MainActor,,me_head,VO/voice_JJd, jjd,姐姐,Sister,姉さん,Сестра,Hermana mayor,Irmã,Default,MainActor,,me_head,VO/voice_JJd,
jjm,姐姐,Sister,姉さん,,,,Default,MainActor,,me_head,VO/voice_JJm, jjm,姐姐,Sister,姉さん,Сестра,Hermana mayor,Irmã,Default,MainActor,,me_head,VO/voice_JJm,
jj?,柔和女声,???,???,,,,Default,MainActor,,me_head,VO/voice_JJ, jj?,柔和女声,Soft Female Voice,柔らかな女の声,Нежный женский голос,Voz suave de mujer,Voz Feminina Suave,Default,MainActor,,me_head,VO/voice_JJ,
hs,火山,HuoShan,火山,,,,Default,MainActor,#FFA800,peipei_head,VO/voice_HS, hs,火山,Cole,火山,Вулкан,Huoshan,Vulcão,Default,MainActor,#FFA800,peipei_head,VO/voice_HS,
hs?,沙哑男声,???,???,,,,Default,MainActor,#FFA800,peipei_head,VO/voice_HS, hs?,沙哑男声,Hoarse Male Voice,しゃがれた男の声,Хриплый мужской голос,Voz ronca de hombre,Voz Masculina Rouca,Default,MainActor,#FFA800,peipei_head,VO/voice_HS,
hsm,火山,HuoShan,火山,,,,Default,MainActor,,,VO/voice_HSm, hsm,火山,Kazan,火山,Вулкан,Huoshan,Vulcão,Default,MainActor,,,VO/voice_HSm,
hsd,阿灿,Young Boy,少年,,,,Default,MainActor,,,VO/voice_HSd, hsd,阿灿,Acan,アーツァン,А-Цань,Acan,Acan,Default,MainActor,,,VO/voice_HSd,
hsd?,???,???,???,,,,Default,MainActor,#FFA800,peipei_head,VO/voice_HSd, hsd?,???,???,???,???,???,???,Default,MainActor,#FFA800,peipei_head,VO/voice_HSd,
yl,英理,Yingli,エリ,,,,Default,MainActor,#4000FF,peipei_head,VO/voice_YL, yl,英理,Yingli,エリ,Инь Ли,Yingli,Yingli,Default,MainActor,#4000FF,peipei_head,VO/voice_YL,
ql?,02号调酒机体,Bartender,,,,,Default,MainActor,#00FFFF,peipei_head,VO/voice_YL, ql?,02号调酒机体,Bartender Entity 02,02番バーテンダー機体,Юнит-бармен №02,Unidad barman 02,Unidade Bartender 02,Default,MainActor,#00FFFF,peipei_head,VO/voice_YL,
ql,奇琳,Kirin,チーリン,,,,Default,MainActor,#00FFFF,peipei_head,VO/voice_YL, ql,奇琳,Qilin,チーリン,Цилинь,Qilin,Qilin,Default,MainActor,#00FFFF,peipei_head,VO/voice_YL,
peipei_novoice,胆怯女声,Timid female voice,怯えた女声,,,,Default,Aside,#00FFFF,,VO/voice_PP,2 peipei_novoice,胆怯女声,Timid Female Voice,臆病な女の声,Робкий женский голос,Voz tímida de mujer,Voz Feminina Tímida,Default,Aside,#00FFFF,,VO/voice_PP,2
yingli_novoice,严厉女声,Stern female voice,厳しい女声,,,,Default,Aside,#FFCC00,,VO/voice_YL,1 yingli_novoice,严厉女声,Stern Female Voice,厳しい女,Строгий женский голос,Voz severa de mujer,Voz Feminina Severa,Default,Aside,#FFCC00,,VO/voice_YL,1
tutorial,教学,Tutorial,,,,,Default,Aside,,,, tutorial,教学,Tutorial,チュートリアル,Обучение,Tutorial,Tutorial,Default,Aside,,,,
docter,儿科医生,Doctor (in memory),医者(記憶の中),,,,Default,Aside,#FFCC00,,, docter,儿科医生,Pediatrician,小児科医,Педиатр,Pediatra,Pediatra,Default,Aside,#FFCC00,,,
peipei_child,童年佩佩,Peipei (child),イペイ(幼少期),,,,Default,Aside,#00FFFF,,VO/voice_PPm, peipei_child,童年佩佩,Peipei (child),(幼少期),Пэйпэй в детстве,Peipei (niña),PeiPei Criança,Default,Aside,#00FFFF,,VO/voice_PPm,
unknown,???,???,???,,,,Default,Aside,#FFCC00,,, unknown,???,???,???,???,???,???,Default,Aside,#FFCC00,,,
???,???,???,???,,,,Default,MainActor,#FFCC00,,, ???,???,???,???,???,???,???,Default,MainActor,#FFCC00,,,
zls?,???,???,???,,,,Default,MainActor,#FFCC00,,VO/voice_memCommon, zls?,???,???,???,???,???,???,Default,MainActor,#FFCC00,,VO/voice_memCommon,
zls,治疗师,Therapist,セラピスト,,,,Default,MainActor,#FFCC00,,VO/voice_memCommon, zls,治疗师,Therapist,セラピスト,Терапевт,Terapeuta,Terapeuta,Default,MainActor,#FFCC00,,VO/voice_memCommon,
shitou,石头,Stone,シートウ,,,,Default,MainActor,,,VO/voice_shitou, shitou,石头,Stone,ストーン,Шитоу,Piedra,Pedra,Default,MainActor,,,VO/voice_shitou,
Descent,Descent,Descent,,,,,Default,Aside,#FFCC00,,VO/voice_me, Descent,Descent,Descent,ディセント,Descent,Descent,Descent,Default,Aside,#FFCC00,,VO/voice_me,
e-assistant,电子助手,Electronic Assistant,,,,,Default,Aside,#FFCC00,,VO/voice_me, e-assistant,电子助手,Electronic Assistant,電子アシスタント,Электронный помощник,Asistente electrónico,Assistente Eletrônico,Default,Aside,#FFCC00,,VO/voice_me,
Gotashi_unknow,戴纯金雕塑头的男人,*Gotash,*ゴタシュ,,,,Default,Aside,#FFCC00,,, Gotashi_unknow,戴纯金雕塑头的男人,Man with a Solid Gold Sculpted Head,純金彫刻の頭部を被った男,Мужчина с головой-скульптурой из чистого золота,Hombre que lleva una cabeza de escultura de oro puro,Homem com Cabeça de Escultura de Ouro Maciço,Default,Aside,#FFCC00,,,
dn,无连接,No Connection,No Connection,,,,Screen,Aside,#FFBF5F,,VO/voice_DN, dn,无连接,Disconnected,未接続,Нет подключения,Sin conexión,Desconectado,Screen,Aside,#FFBF5F,,VO/voice_DN,
Linking,连接中……,Connecting...,Connecting...,,,,Screen,Aside,#E6A23C,,VO/voice_DN, Linking,连接中……,Connecting...,接続中……,Подключение...,Conectando...,Conectando...,Screen,Aside,#E6A23C,,VO/voice_DN,
Analysing,分析中,Analyzing...,Analyzing...,,,,Screen,Aside,#E6A23C,,VO/voice_DN, Analysing,分析中,Analyzing...,分析中,Анализ...,Analizando,Analisando,Screen,Aside,#E6A23C,,VO/voice_DN,
Error,错误警告,Error warning,Error warning,,,,Screen,Aside,#E6A23C,,VO/voice_DN, Error,错误警告,Error Warning,エラー警告,Предупреждение об ошибке,Advertencia de error,Aviso de Erro,Screen,Aside,#E6A23C,,VO/voice_DN,
UfModule,Unstable Fusion图像生成模块,Unstable Fusion Module,Unstable Fusion Module,,,,Screen,Aside,#F56C6C,,VO/voice_DN, UfModule,Unstable Fusion图像生成模块,Unstable Fusion Image Generation Module,Unstable Fusion画像生成モジュール,Модуль генерации изображений Unstable Fusion,Módulo de generación de imágenes Unstable Fusion,Módulo de Geração de Imagem Unstable Fusion,Screen,Aside,#F56C6C,,VO/voice_DN,
EyeModule,HS9型视觉模块,HS9 Visual Module,HS9 Visual Module,,,,Screen,Aside,#F56C6C,,VO/voice_DN, EyeModule,HS9型视觉模块,HS9 Vision Module,HS9型視覚モジュール,Модуль зрения HS9,Módulo visual modelo HS9,Módulo de Visão HS9,Screen,Aside,#F56C6C,,VO/voice_DN,
EmoModule,通用型情绪发生模块,Universal Emotion Generation Module,Universal Emotion Generation Module,,,,Screen,Aside,#F56C6C,,VO/voice_DN, EmoModule,通用型情绪发生模块,General-Purpose Emotion Generation Module,汎用型情緒発生モジュール,Универсальный модуль генерации эмоций,Módulo emocional genérico,Módulo de Geração de Emoção Genérico,Screen,Aside,#F56C6C,,VO/voice_DN,
MemModule,通用型记忆模块,Universal Memory Module,Universal Memory Module,,,,Screen,Aside,#F56C6C,,VO/voice_DN, MemModule,通用型记忆模块,Universal Memory Module,汎用型記憶モジュール,Универсальный модуль памяти,Módulo de memoria genérico,Módulo de Memória Genérico,Screen,Aside,#F56C6C,,VO/voice_DN,
LogicModule,MK1型逻辑处理模块,MK1 logic Processing Module,MK1 logic Processing Module,,,,Screen,Aside,#F56C6C,,VO/voice_DN, LogicModule,MK1型逻辑处理模块,Type MK1 Logic Processing Module,MK1型論理処理モジュール,Модуль обработки логики MK1,Módulo de procesamiento lógico modelo MK1,Módulo de Processamento Lógico MK1,Screen,Aside,#F56C6C,,VO/voice_DN,
SpeakerModule,MK1型表达模块,MK1 Expression Module,MK1 Expression Module,,,,Screen,Aside,#F56C6C,,VO/voice_DN, SpeakerModule,MK1型表达模块,Type MK1 Expression Module,MK1型表現モジュール,Модуль выражения MK1,Módulo de expresn modelo MK1,Módulo de Expressão MK1,Screen,Aside,#F56C6C,,VO/voice_DN,
SaleModule,销售语言拓展模块,Sales Language Extension Module,販売言語拡張モジュール,,,,Screen,Aside,#F56C6C,,VO/voice_DN, SaleModule,销售语言拓展模块,Sales Language Extension Module,販売言語拡張モジュール,Модуль расширения языка продаж,Módulo de expansión de lenguaje de ventas,Módulo de Expansão de Linguagem de Vendas,Screen,Aside,#F56C6C,,VO/voice_DN,
ChipModule,4R7型色觉晶圆训练槽,4R7 Color Perception Wafer Training Slot,4R7 Color Perception Wafer Training Slot,,,,Screen,Aside,#F56C6C,,VO/voice_DN, ChipModule,4R7型色觉晶圆训练槽,Type 4R7 Color Vision Wafer Training Slot,4R7型色覚ウェハートレーニングスロット,Тренировочный слот чипа цветовосприятия 4R7,Ranura de entrenamiento de oblea de visión cromática modelo 4R7,Slot de Treinamento do Chip de Visão Cromática 4R7,Screen,Aside,#F56C6C,,VO/voice_DN,
HivemindModule,未知型号<br>Hivemind通讯终端,Unknown type Hivemind communication terminal,Unknown type Hivemind communication terminal,,,,Screen,Aside,#F56C6C,,VO/voice_DN, HivemindModule,未知型号<br>Hivemind通讯终端,Unknown Model<br>Hivemind Communication Terminal,型番不明<br>ハイブマインド通信端末,Терминал связи «Коллективный разум»<br>неизвестной модели,Terminal de comunicación Hivemind<br>de modelo desconocido,Modelo Desconhecido<br>Terminal de Comunicação Hivemind,Screen,Aside,#F56C6C,,VO/voice_DN,
HeartModule,C03UR型特配涡轮增压动力模块,C03UR Special Turbocharged Power Module,C03UR Special Turbocharged Power Module,,,,Screen,Aside,#E6A23C,,VO/voice_DN, HeartModule,C03UR型特配涡轮增压动力模块,Type C03UR Custom Turbocharged Power Module,C03UR型特注ターボチャージャー動力モジュール,Специальный силовой модуль C03UR с турбонаддувом,Módulo de potencia turboalimentado especial modelo C03UR,Módulo de Potência Turbocompressor Personalizado C03UR,Screen,Aside,#E6A23C,,VO/voice_DN,
LungModule,MK1型气体处理模块,MK1 Gas Processing Module,MK1 Gas Processing Module,,,,Screen,Aside,#E6A23C,,VO/voice_DN, LungModule,MK1型气体处理模块,Type MK1 Gas Processing Module,MK1型気体処理モジュール,Модуль обработки газов MK1,Módulo de procesamiento de gas modelo MK1,Módulo de Processamento de Gás MK1,Screen,Aside,#E6A23C,,VO/voice_DN,
LiverModule,MK1型冷却液循环模块,MK1 Coolant Circulation Module,MK1 Coolant Circulation Module,,,,Screen,Aside,#E6A23C,,VO/voice_DN, LiverModule,MK1型冷却液循环模块,Type MK1 Coolant Circulation Module,MK1型冷却液循環モジュール,Модуль циркуляции хладагента MK1,Módulo de circulación de líquido refrigerante modelo MK1,Módulo de Circulação de Líquido de Arrefecimento MK1,Screen,Aside,#E6A23C,,VO/voice_DN,
DigestModule,MK1型物料处理模块,MK1 Material Processing Module,MK1 Material Processing Module,,,,Screen,Aside,#E6A23C,,VO/voice_DN, DigestModule,MK1型物料处理模块,Type MK1 Material Processing Module,MK1型原料処理モジュール,Модуль обработки материалов MK1,Módulo de procesamiento de material modelo MK1,Módulo de Processamento de Material MK1,Screen,Aside,#E6A23C,,VO/voice_DN,
CenterText,,,,,,,CenterText,Aside,,,, CenterText,,,,,,,CenterText,Aside,,,,
CenterOption,,,,,,,CenterOption,Aside,,,, CenterOption,,,,,,,CenterOption,Aside,,,,
Task,,,,,,,Task,Aside,,,, Task,,,,,,,Task,Aside,,,,
TempText,,,,,,,Default,Aside,,,, TempText,,,,,,,Default,Aside,,,,
gts,患者,???,???,,,,Default,MainActor,#A4CD36,,VO/voice_GTS, gts,患者,Patient,患者,Пациент,Paciente,Paciente,Default,MainActor,#A4CD36,,VO/voice_GTS,
gts?,患者,???,???,,,,Default,MainActor,#A4CD36,,VO/voice_GTS_broken, gts?,患者,Patient,患者,Пациент,Paciente,Paciente,Default,MainActor,#A4CD36,,VO/voice_GTS_broken,
gts??,患者??,???,???,,,,Default,MainActor,#E2C22B,,VO/voice_GTS, gts??,患者??,Patient??,患者??,Пациент??,¿¿Paciente??,Paciente??,Default,MainActor,#E2C22B,,VO/voice_GTS,
classmate,同学,Classmate,クラスメート,,,,Default,Aside,,,VO/voice_memCommon, classmate,同学,Classmate,同級生,Одноклассник,Compañero de clase,Colega,Default,Aside,,,VO/voice_memCommon,
jb,酒保,Bartender,バーテンダー,,,,Default,MainActor,,,VO/voice_memCommon, jb,酒保,Bartender,バーテンダー,Бармен,Cantinero,Bartender,Default,MainActor,,,VO/voice_memCommon,
host,主持人,Newscaster,ニュースキャスター,,,,Default,MainActor,,,VO/voice_memCommon, host,主持人,Host,司会者,Ведущий,Presentador,Apresentador,Default,MainActor,,,VO/voice_memCommon,
teacher,教师,Teacher,Teacher,,,,Default,MainActor,#FFCC00,,VO/voice_memCommon, teacher,教师,Teacher,教師,Учитель,Maestro,Professor,Default,MainActor,#FFCC00,,VO/voice_memCommon,
sellerA,销售员A,Seller A,Seller A,,,,Default,MainActor,#FFCC00,,VO/voice_memCommon, sellerA,销售员A,Salesperson A,販売員A,Продавец А,Vendedor A,Vendedor A,Default,MainActor,#FFCC00,,VO/voice_memCommon,
sellerB,销售员B,Seller B,Seller B,,,,Default,MainActor,#FFCC00,,VO/voice_memCommon, sellerB,销售员B,Salesperson B,販売員B,Продавец Б,Vendedor B,Vendedor B,Default,MainActor,#FFCC00,,VO/voice_memCommon,
sellerC,销售员C,Seller C,Seller C,,,,Default,MainActor,#FFCC00,,VO/voice_memCommon, sellerC,销售员C,Salesperson C,販売員C,Продавец В,Vendedor C,Vendedor C,Default,MainActor,#FFCC00,,VO/voice_memCommon,
1 通过key索引配置 中文名 英文名 日文名 俄文名 西班牙文名 巴西葡萄牙文名 显示位置,Default使用默认,Screen插线屏幕,OldBox基本对话框 人物角色(Player/MainActor/Aside) 名称显示时的颜色 头像Pic路径 语音资源路径 气泡序号(默认0)
2 Key Cn En Ja Ru Es PtBr DialogViewType Role NameColor HeadPicPath VoicePath BubbleIdx
3 jg 警告 Warning Warning 警告 Предупреждение Advertencia Aviso Default MainActor #7ae622 VO/voice_DN
4 Door ドア Дверь Puerta Porta Default MainActor #7ae622
5 矩形 黑暗 Darkness Тьма Oscuridad Escuridão Default MainActor #7ae622 VO/voice_DN
6 记忆 记忆 Memory 記憶 Память Memoria Memória Default MainActor #7ae622
7 peipei 佩佩 Peipei PeiPei ペイペイ ペペ Пэйпэй Peipei Peipei Default MainActor #00FFFF peipei_head VO/voice_PP
8 乌鸦 乌鸦 Crow カラス Ворон Cuervo Corvo Default MainActor #e6ddc1 VO/voice_WYd
9 wyd? ??? ??? ??? ??? ??? ??? ??? Default MainActor #e6ddc1 VO/voice_WYd
10 马尾辫女孩 马尾辫女孩 Girl with ponytail Ponytail Girl ポニーテールの女の子 Девочка с хвостиком Chica de la coleta Garota de Rabo de Cavalo Default MainActor VO/voice_JJ
11 pp 佩佩 Peipei PeiPei ペイペイ ペペ Пэйпэй Peipei Peipei Default MainActor #00FFFF peipei_head VO/voice_PP
12 ppm 佩佩 Peipei PeiPei ペイペイ ペペ Пэйпэй Peipei Peipei Default MainActor #00FFFF peipei_head VO/voice_PPm
13 ppd 女孩 Girl 女の子 Девочка Niña Garota Default MainActor VO/voice_PPd
14 ppd? 女孩 Girl 女の子 Девочка Niña Garota Default MainActor VO/voice_PPd
15 me 路斯 Luth Ruth ルース Лус Ruth Lusi Default Player me_head VO/voice_ME
16 med 路斯 Luth Ruth ルース Лус Ruth Lusi Default Player me_head VO/voice_MEd
17 mem 路斯 Luth Ruth ルース Лус Ruth Lusi Default Player me_head VO/voice_MEm
18 mecd 女孩 Young Girl Girl 少女 女の子 Девочка Niña Garota Default MainActor VO/voice_MECd
19 mecd? ??? ??? ??? ??? ??? ??? ??? Default MainActor VO/voice_MECd
20 bb BB机 Beeper Pager ポケベル Пейджер Bíper Pager Default Player me_head
21 jj 姐姐 Sister 姉さん Сестра Hermana mayor Irmã Default MainActor me_head VO/voice_JJ
22 jjd 姐姐 Sister 姉さん Сестра Hermana mayor Irmã Default MainActor me_head VO/voice_JJd
23 jjm 姐姐 Sister 姉さん Сестра Hermana mayor Irmã Default MainActor me_head VO/voice_JJm
24 jj? 柔和女声 ??? Soft Female Voice ??? 柔らかな女の声 Нежный женский голос Voz suave de mujer Voz Feminina Suave Default MainActor me_head VO/voice_JJ
25 hs 火山 HuoShan Cole 火山 Вулкан Huoshan Vulcão Default MainActor #FFA800 peipei_head VO/voice_HS
26 hs? 沙哑男声 ??? Hoarse Male Voice ??? しゃがれた男の声 Хриплый мужской голос Voz ronca de hombre Voz Masculina Rouca Default MainActor #FFA800 peipei_head VO/voice_HS
27 hsm 火山 HuoShan Kazan 火山 Вулкан Huoshan Vulcão Default MainActor VO/voice_HSm
28 hsd 阿灿 Young Boy Acan 少年 アーツァン А-Цань Acan Acan Default MainActor VO/voice_HSd
29 hsd? ??? ??? ??? ??? ??? ??? ??? Default MainActor #FFA800 peipei_head VO/voice_HSd
30 yl 英理 Yingli エリ Инь Ли Yingli Yingli Default MainActor #4000FF peipei_head VO/voice_YL
31 ql? 02号调酒机体 Bartender Bartender Entity 02 02番バーテンダー機体 Юнит-бармен №02 Unidad barman 02 Unidade Bartender 02 Default MainActor #00FFFF peipei_head VO/voice_YL
32 ql 奇琳 Kirin Qilin チーリン Цилинь Qilin Qilin Default MainActor #00FFFF peipei_head VO/voice_YL
33 peipei_novoice 胆怯女声 Timid female voice Timid Female Voice 怯えた女声 臆病な女の声 Робкий женский голос Voz tímida de mujer Voz Feminina Tímida Default Aside #00FFFF VO/voice_PP 2
34 yingli_novoice 严厉女声 Stern female voice Stern Female Voice 厳しい女声 厳しい女の声 Строгий женский голос Voz severa de mujer Voz Feminina Severa Default Aside #FFCC00 VO/voice_YL 1
35 tutorial 教学 Tutorial チュートリアル Обучение Tutorial Tutorial Default Aside
36 docter 儿科医生 Doctor (in memory) Pediatrician 医者(記憶の中) 小児科医 Педиатр Pediatra Pediatra Default Aside #FFCC00
37 peipei_child 童年佩佩 Peipei (child) ペイペイ(幼少期) ペペ(幼少期) Пэйпэй в детстве Peipei (niña) PeiPei Criança Default Aside #00FFFF VO/voice_PPm
38 unknown ??? ??? ??? ??? ??? ??? Default Aside #FFCC00
39 ??? ??? ??? ??? ??? ??? ??? Default MainActor #FFCC00
40 zls? ??? ??? ??? ??? ??? ??? Default MainActor #FFCC00 VO/voice_memCommon
41 zls 治疗师 Therapist セラピスト Терапевт Terapeuta Terapeuta Default MainActor #FFCC00 VO/voice_memCommon
42 shitou 石头 Stone シートウ ストーン Шитоу Piedra Pedra Default MainActor VO/voice_shitou
43 Descent Descent Descent ディセント Descent Descent Descent Default Aside #FFCC00 VO/voice_me
44 e-assistant 电子助手 Electronic Assistant 電子アシスタント Электронный помощник Asistente electrónico Assistente Eletrônico Default Aside #FFCC00 VO/voice_me
45 Gotashi_unknow 戴纯金雕塑头的男人 *Gotash Man with a Solid Gold Sculpted Head *ゴタシュ 純金彫刻の頭部を被った男 Мужчина с головой-скульптурой из чистого золота Hombre que lleva una cabeza de escultura de oro puro Homem com Cabeça de Escultura de Ouro Maciço Default Aside #FFCC00
46 dn 无连接 No Connection Disconnected No Connection 未接続 Нет подключения Sin conexión Desconectado Screen Aside #FFBF5F VO/voice_DN
47 Linking 连接中…… Connecting... Connecting... 接続中…… Подключение... Conectando... Conectando... Screen Aside #E6A23C VO/voice_DN
48 Analysing 分析中 Analyzing... Analyzing... 分析中 Анализ... Analizando Analisando Screen Aside #E6A23C VO/voice_DN
49 Error 错误警告 Error warning Error Warning Error warning エラー警告 Предупреждение об ошибке Advertencia de error Aviso de Erro Screen Aside #E6A23C VO/voice_DN
50 UfModule Unstable Fusion图像生成模块 Unstable Fusion Module Unstable Fusion Image Generation Module Unstable Fusion Module Unstable Fusion画像生成モジュール Модуль генерации изображений Unstable Fusion Módulo de generación de imágenes Unstable Fusion Módulo de Geração de Imagem Unstable Fusion Screen Aside #F56C6C VO/voice_DN
51 EyeModule HS9型视觉模块 HS9 Visual Module HS9 Vision Module HS9 Visual Module HS9型視覚モジュール Модуль зрения HS9 Módulo visual modelo HS9 Módulo de Visão HS9 Screen Aside #F56C6C VO/voice_DN
52 EmoModule 通用型情绪发生模块 Universal Emotion Generation Module General-Purpose Emotion Generation Module Universal Emotion Generation Module 汎用型情緒発生モジュール Универсальный модуль генерации эмоций Módulo emocional genérico Módulo de Geração de Emoção Genérico Screen Aside #F56C6C VO/voice_DN
53 MemModule 通用型记忆模块 Universal Memory Module Universal Memory Module 汎用型記憶モジュール Универсальный модуль памяти Módulo de memoria genérico Módulo de Memória Genérico Screen Aside #F56C6C VO/voice_DN
54 LogicModule MK1型逻辑处理模块 MK1 logic Processing Module Type MK1 Logic Processing Module MK1 logic Processing Module MK1型論理処理モジュール Модуль обработки логики MK1 Módulo de procesamiento lógico modelo MK1 Módulo de Processamento Lógico MK1 Screen Aside #F56C6C VO/voice_DN
55 SpeakerModule MK1型表达模块 MK1 Expression Module Type MK1 Expression Module MK1 Expression Module MK1型表現モジュール Модуль выражения MK1 Módulo de expresión modelo MK1 Módulo de Expressão MK1 Screen Aside #F56C6C VO/voice_DN
56 SaleModule 销售语言拓展模块 Sales Language Extension Module 販売言語拡張モジュール Модуль расширения языка продаж Módulo de expansión de lenguaje de ventas Módulo de Expansão de Linguagem de Vendas Screen Aside #F56C6C VO/voice_DN
57 ChipModule 4R7型色觉晶圆训练槽 4R7 Color Perception Wafer Training Slot Type 4R7 Color Vision Wafer Training Slot 4R7 Color Perception Wafer Training Slot 4R7型色覚ウェハートレーニングスロット Тренировочный слот чипа цветовосприятия 4R7 Ranura de entrenamiento de oblea de visión cromática modelo 4R7 Slot de Treinamento do Chip de Visão Cromática 4R7 Screen Aside #F56C6C VO/voice_DN
58 HivemindModule 未知型号<br>Hivemind通讯终端 Unknown type Hivemind communication terminal Unknown Model<br>Hivemind Communication Terminal Unknown type Hivemind communication terminal 型番不明<br>ハイブマインド通信端末 Терминал связи «Коллективный разум»<br>неизвестной модели Terminal de comunicación Hivemind<br>de modelo desconocido Modelo Desconhecido<br>Terminal de Comunicação Hivemind Screen Aside #F56C6C VO/voice_DN
59 HeartModule C03UR型特配涡轮增压动力模块 C03UR Special Turbocharged Power Module Type C03UR Custom Turbocharged Power Module C03UR Special Turbocharged Power Module C03UR型特注ターボチャージャー動力モジュール Специальный силовой модуль C03UR с турбонаддувом Módulo de potencia turboalimentado especial modelo C03UR Módulo de Potência Turbocompressor Personalizado C03UR Screen Aside #E6A23C VO/voice_DN
60 LungModule MK1型气体处理模块 MK1 Gas Processing Module Type MK1 Gas Processing Module MK1 Gas Processing Module MK1型気体処理モジュール Модуль обработки газов MK1 Módulo de procesamiento de gas modelo MK1 Módulo de Processamento de Gás MK1 Screen Aside #E6A23C VO/voice_DN
61 LiverModule MK1型冷却液循环模块 MK1 Coolant Circulation Module Type MK1 Coolant Circulation Module MK1 Coolant Circulation Module MK1型冷却液循環モジュール Модуль циркуляции хладагента MK1 Módulo de circulación de líquido refrigerante modelo MK1 Módulo de Circulação de Líquido de Arrefecimento MK1 Screen Aside #E6A23C VO/voice_DN
62 DigestModule MK1型物料处理模块 MK1 Material Processing Module Type MK1 Material Processing Module MK1 Material Processing Module MK1型原料処理モジュール Модуль обработки материалов MK1 Módulo de procesamiento de material modelo MK1 Módulo de Processamento de Material MK1 Screen Aside #E6A23C VO/voice_DN
63 CenterText CenterText Aside
64 CenterOption CenterOption Aside
65 Task Task Aside
66 TempText Default Aside
67 gts 患者 ??? Patient ??? 患者 Пациент Paciente Paciente Default MainActor #A4CD36 VO/voice_GTS
68 gts? 患者 ??? Patient ??? 患者 Пациент Paciente Paciente Default MainActor #A4CD36 VO/voice_GTS_broken
69 gts?? 患者?? ??? Patient?? ??? 患者?? Пациент?? ¿¿Paciente?? Paciente?? Default MainActor #E2C22B VO/voice_GTS
70 classmate 同学 Classmate クラスメート 同級生 Одноклассник Compañero de clase Colega Default Aside VO/voice_memCommon
71 jb 酒保 Bartender バーテンダー Бармен Cantinero Bartender Default MainActor VO/voice_memCommon
72 host 主持人 Newscaster Host ニュースキャスター 司会者 Ведущий Presentador Apresentador Default MainActor VO/voice_memCommon
73 teacher 教师 Teacher Teacher 教師 Учитель Maestro Professor Default MainActor #FFCC00 VO/voice_memCommon
74 sellerA 销售员A Seller A Salesperson A Seller A 販売員A Продавец А Vendedor A Vendedor A Default MainActor #FFCC00 VO/voice_memCommon
75 sellerB 销售员B Seller B Salesperson B Seller B 販売員B Продавец Б Vendedor B Vendedor B Default MainActor #FFCC00 VO/voice_memCommon
76 sellerC 销售员C Seller C Salesperson C Seller C 販売員C Продавец В Vendedor C Vendedor C Default MainActor #FFCC00 VO/voice_memCommon
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 480fa26171aa2e242bff1885e9f7df55
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: ad75debdd4433f642891c0cbc24a0bb5
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,22 @@
{
"name": "AibisDream.Typography.Tests.PlayMode",
"rootNamespace": "AibisDream.Typography.Tests.PlayMode",
"references": [
"AibisDream.Typography.Runtime",
"Unity.TextMeshPro"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": false,
"defineConstraints": [
"UNITY_INCLUDE_TESTS"
],
"versionDefines": [],
"noEngineReferences": false,
"optionalUnityReferences": [
"TestAssemblies"
]
}
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 4bd93b18a8a4497478c36873cabf5355
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,328 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Threading.Tasks;
using AibisDream.Framework;
using NUnit.Framework;
using TMPro;
using UnityEngine;
using UnityEngine.TestTools;
using Object = UnityEngine.Object;
namespace AibisDream.Typography.Tests.PlayMode
{
public sealed class TypographyServicePlayModeTests
{
private readonly List<Object> createdObjects = new();
[UnitySetUp]
public IEnumerator SetUp()
{
TypographyService.ResetForTests();
yield return null;
}
[UnityTearDown]
public IEnumerator TearDown()
{
TypographyService.ResetForTests();
foreach (Object created in createdObjects)
{
if (created != null)
Object.Destroy(created);
}
createdObjects.Clear();
yield return null;
}
[UnityTest]
public IEnumerator RuntimeCreatedText_AppliesCurrentProfile()
{
TypographyProfile latin = CreateProfile(TypographyProfileKind.Latin, 1.25f);
var loader = new ImmediateLoader(new Dictionary<string, TypographyProfile>
{
[TypographyLocaleMapping.GetProfileKey(LocaleKind.En)] = latin
});
TypographyService.ConfigureLoader(loader);
Task<bool> loadTask = TypographyService.LoadAndActivateAsync(LocaleKind.En);
yield return new WaitUntil(() => loadTask.IsCompleted);
Assert.That(loadTask.Result, Is.True);
var go = new GameObject("RuntimeTypographyText", typeof(RectTransform));
createdObjects.Add(go);
TextMeshProUGUI text = go.AddComponent<TextMeshProUGUI>();
text.fontSize = 20f;
Assert.That(TypographyService.Apply(text, TypographyRole.Body), Is.True);
Assert.That(text.fontSize, Is.EqualTo(25f));
Assert.That(text.font, Is.EqualTo(TMP_Settings.defaultFontAsset));
}
[UnityTest]
public IEnumerator RegisteredBinding_SwitchesProfilesWithoutMetricAccumulationAndReleasesOldKey()
{
TypographyProfile latin = CreateProfile(TypographyProfileKind.Latin, 0.5f);
TypographyProfile russian = CreateProfile(TypographyProfileKind.Russian, 0.75f);
var loader = new ImmediateLoader(new Dictionary<string, TypographyProfile>
{
[TypographyLocaleMapping.GetProfileKey(LocaleKind.En)] = latin,
[TypographyLocaleMapping.GetProfileKey(LocaleKind.Ru)] = russian
});
TypographyService.ConfigureLoader(loader);
var go = new GameObject("BoundTypographyText", typeof(RectTransform));
createdObjects.Add(go);
TextMeshProUGUI text = go.AddComponent<TextMeshProUGUI>();
text.fontSize = 40f;
go.AddComponent<LocalizedTypography>();
Task<bool> latinTask = TypographyService.LoadAndActivateAsync(LocaleKind.En);
yield return new WaitUntil(() => latinTask.IsCompleted);
Assert.That(text.fontSize, Is.EqualTo(20f));
Task<bool> russianTask = TypographyService.LoadAndActivateAsync(LocaleKind.Ru);
yield return new WaitUntil(() => russianTask.IsCompleted);
Assert.That(text.fontSize, Is.EqualTo(30f));
Assert.That(
loader.ReleasedKeys,
Does.Contain(TypographyLocaleMapping.GetProfileKey(LocaleKind.En)));
Task<bool> backTask = TypographyService.LoadAndActivateAsync(LocaleKind.En);
yield return new WaitUntil(() => backTask.IsCompleted);
Assert.That(text.fontSize, Is.EqualTo(20f));
}
[UnityTest]
public IEnumerator PrefabOverride_UsesResolvedInstanceMetricsAsBaseline()
{
TypographyProfile latin = CreateProfile(TypographyProfileKind.Latin, 1f);
var loader = new ImmediateLoader(new Dictionary<string, TypographyProfile>
{
[TypographyLocaleMapping.GetProfileKey(LocaleKind.En)] = latin
});
TypographyService.ConfigureLoader(loader);
Task<bool> loadTask = TypographyService.LoadAndActivateAsync(LocaleKind.En);
yield return new WaitUntil(() => loadTask.IsCompleted);
Assert.That(loadTask.Result, Is.True);
var go = new GameObject("PrefabOverrideTypographyText", typeof(RectTransform));
createdObjects.Add(go);
go.SetActive(false);
TextMeshProUGUI text = go.AddComponent<TextMeshProUGUI>();
LocalizedTypography binding = go.AddComponent<LocalizedTypography>();
const float sourceBaseline = 40f;
const float resolvedOverride = 36f;
SetPrivateField(binding, "baselineCaptured", true);
SetPrivateField(binding, "baseFontSize", sourceBaseline);
text.fontSize = resolvedOverride;
go.SetActive(true);
yield return null;
Assert.That(text.fontSize, Is.EqualTo(resolvedOverride));
}
[UnityTest]
public IEnumerator OverlappingLoads_LastRequestWins()
{
TypographyProfile latin = CreateProfile(TypographyProfileKind.Latin, 1f);
TypographyProfile japanese = CreateProfile(TypographyProfileKind.Japanese, 1f);
var loader = new DeferredLoader();
TypographyService.ConfigureLoader(loader);
Task<bool> latinTask = TypographyService.LoadAndActivateAsync(LocaleKind.En);
Task<bool> japaneseTask = TypographyService.LoadAndActivateAsync(LocaleKind.Ja);
loader.Complete(TypographyLocaleMapping.GetProfileKey(LocaleKind.Ja), japanese);
yield return new WaitUntil(() => japaneseTask.IsCompleted);
loader.Complete(TypographyLocaleMapping.GetProfileKey(LocaleKind.En), latin);
yield return new WaitUntil(() => latinTask.IsCompleted);
Assert.That(TypographyService.CurrentProfile, Is.SameAs(japanese));
Assert.That(
TypographyService.CurrentProfileKey,
Is.EqualTo(TypographyLocaleMapping.GetProfileKey(LocaleKind.Ja)));
Assert.That(latinTask.Result, Is.False);
}
[UnityTest]
public IEnumerator OverlappingLoads_ForSameProfileDoNotReleaseTheWinningHandle()
{
TypographyProfile latin = CreateProfile(TypographyProfileKind.Latin, 1f);
var loader = new DeferredLoader();
TypographyService.ConfigureLoader(loader);
Task<bool> firstTask = TypographyService.LoadAndActivateAsync(LocaleKind.En);
Task<bool> secondTask = TypographyService.LoadAndActivateAsync(LocaleKind.Es);
loader.Complete(TypographyLocaleMapping.GetProfileKey(LocaleKind.En), latin);
yield return new WaitUntil(() => firstTask.IsCompleted && secondTask.IsCompleted);
Assert.That(firstTask.Result, Is.False);
Assert.That(secondTask.Result, Is.True);
Assert.That(TypographyService.CurrentProfile, Is.SameAs(latin));
Assert.That(loader.ReleasedKeys, Is.Empty);
}
[UnityTest]
public IEnumerator MissingProfile_KeepsTheLastValidProfile()
{
TypographyProfile latin = CreateProfile(TypographyProfileKind.Latin, 1f);
var loader = new ImmediateLoader(new Dictionary<string, TypographyProfile>
{
[TypographyLocaleMapping.GetProfileKey(LocaleKind.En)] = latin
});
TypographyService.ConfigureLoader(loader);
Task<bool> validTask = TypographyService.LoadAndActivateAsync(LocaleKind.En);
yield return new WaitUntil(() => validTask.IsCompleted);
LogAssert.Expect(
LogType.Error,
"[TypographyService] 字体 Profile 不存在或加载失败:Typography/Profile/Japanese");
Task<bool> missingTask = TypographyService.LoadAndActivateAsync(LocaleKind.Ja);
yield return new WaitUntil(() => missingTask.IsCompleted);
Assert.That(validTask.Result, Is.True);
Assert.That(missingTask.Result, Is.False);
Assert.That(TypographyService.CurrentProfile, Is.SameAs(latin));
Assert.That(
TypographyService.CurrentProfileKey,
Is.EqualTo(TypographyLocaleMapping.GetProfileKey(LocaleKind.En)));
}
[UnityTest]
public IEnumerator LocalizationKit_SupportedLocalesLoadRealAddressableProfilesAndReleasePreviousHandles()
{
Type localizationKit = Type.GetType(
"AibisDream.Framework.LocalizationKit, Assembly-CSharp",
throwOnError: true);
Type resourceSystem = Type.GetType(
"AibisDream.Framework.ResourceSystem, Assembly-CSharp",
throwOnError: true);
MethodInfo switchLanguage = localizationKit.GetMethod(
"SwitchLanguageAsync",
BindingFlags.Public | BindingFlags.Static);
PropertyInfo currentLanguageCode = localizationKit.GetProperty(
"CurrentLanguageCode",
BindingFlags.Public | BindingFlags.Static);
PropertyInfo persistentLoader = resourceSystem.GetProperty(
"PersistentLoader",
BindingFlags.Public | BindingFlags.Static);
Assert.That(switchLanguage, Is.Not.Null);
Assert.That(currentLanguageCode, Is.Not.Null);
Assert.That(persistentLoader, Is.Not.Null);
string originalLocale = (string)currentLanguageCode.GetValue(null);
var localeCases = new[]
{
(Code: "zh-Hans", Kind: LocaleKind.Cn),
(Code: "en", Kind: LocaleKind.En),
(Code: "ja-JP", Kind: LocaleKind.Ja),
(Code: "ru", Kind: LocaleKind.Ru),
(Code: "es", Kind: LocaleKind.Es),
(Code: "pt-BR", Kind: LocaleKind.PtBr)
};
foreach (var localeCase in localeCases)
{
var switchTask = (Task<bool>)switchLanguage.Invoke(null, new object[] { localeCase.Code });
yield return new WaitUntil(() => switchTask.IsCompleted);
Assert.That(switchTask.IsFaulted, Is.False, localeCase.Code);
Assert.That(switchTask.Result, Is.True, localeCase.Code);
Assert.That(
TypographyService.CurrentProfileKey,
Is.EqualTo(TypographyLocaleMapping.GetProfileKey(localeCase.Kind)),
localeCase.Code);
object loaderInstance = persistentLoader.GetValue(null);
Assert.That(loaderInstance, Is.Not.Null, localeCase.Code);
int handleCount = (int)loaderInstance.GetType()
.GetProperty("TotalHandleCount", BindingFlags.Public | BindingFlags.Instance)
.GetValue(loaderInstance);
Assert.That(handleCount, Is.EqualTo(1), localeCase.Code);
}
var restoreTask = (Task<bool>)switchLanguage.Invoke(null, new object[] { originalLocale });
yield return new WaitUntil(() => restoreTask.IsCompleted);
Assert.That(restoreTask.Result, Is.True);
}
private TypographyProfile CreateProfile(TypographyProfileKind kind, float multiplier)
{
var profile = ScriptableObject.CreateInstance<TypographyProfile>();
createdObjects.Add(profile);
profile.Configure(
kind,
new[]
{
new TypographyStyle(
TypographyRole.Body,
TMP_Settings.defaultFontAsset,
fontSizeMultiplier: multiplier)
});
return profile;
}
private static void SetPrivateField<T>(LocalizedTypography binding, string fieldName, T value)
{
FieldInfo field = typeof(LocalizedTypography).GetField(
fieldName,
BindingFlags.Instance | BindingFlags.NonPublic);
Assert.That(field, Is.Not.Null, fieldName);
field.SetValue(binding, value);
}
private sealed class ImmediateLoader : ITypographyProfileLoader
{
private readonly IReadOnlyDictionary<string, TypographyProfile> profiles;
public ImmediateLoader(IReadOnlyDictionary<string, TypographyProfile> profiles)
{
this.profiles = profiles;
}
public List<string> ReleasedKeys { get; } = new();
public Task<TypographyProfile> LoadAsync(string key)
{
profiles.TryGetValue(key, out TypographyProfile profile);
return Task.FromResult(profile);
}
public void Release(string key)
{
ReleasedKeys.Add(key);
}
}
private sealed class DeferredLoader : ITypographyProfileLoader
{
private readonly Dictionary<string, TaskCompletionSource<TypographyProfile>> pending = new();
public List<string> ReleasedKeys { get; } = new();
public Task<TypographyProfile> LoadAsync(string key)
{
if (!pending.TryGetValue(key, out TaskCompletionSource<TypographyProfile> source))
{
source = new TaskCompletionSource<TypographyProfile>();
pending[key] = source;
}
return source.Task;
}
public void Complete(string key, TypographyProfile profile)
{
pending[key].SetResult(profile);
}
public void Release(string key)
{
ReleasedKeys.Add(key);
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9a0486afd10903e4f81fc8560089f9f5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: