- Implemented WorldStateFlagAttribute to mark string fields as world state flags. - Created NarrativeNPCEditor for custom inspector to visualize dialogue version activation states. - Developed WorldStateFlagDrawer to provide dropdown menu for known flags in the inspector. - Introduced ActorModule for managing DialogueActorSO assets, including viewing, creating, and deleting actors. - Added DialogueModule for managing DialogueSequenceSO assets with detailed previews and action bars. - Established QuestModule for managing QuestSO assets, including objectives and branches. - Implemented QuestManagerPostprocessor to automatically refresh QuestManager's quest list on asset changes.
234 lines
9.8 KiB
C#
234 lines
9.8 KiB
C#
// Assets/Scripts/Localization/LocalizationManager.cs
|
||
// 本地化管理器(运行时 JSON 文件驱动)。
|
||
//
|
||
// 数据格式(放在 Resources/Localization/{Language}/{TableName}.json):
|
||
// {
|
||
// "entries": [
|
||
// { "key": "ui_start", "value": "开始游戏" },
|
||
// { "key": "ui_settings", "value": "设置" }
|
||
// ]
|
||
// }
|
||
//
|
||
// 推荐用法(通过 ServiceLocator 获取 ILocalizationService 实例):
|
||
// ServiceLocator.GetOrDefault<ILocalizationService>()?.Get("ui_start")
|
||
// ServiceLocator.GetOrDefault<ILocalizationService>()?.SetLanguage(Language.English)
|
||
//
|
||
// 便捷静态方法(内部仍走 ServiceLocator,推荐在热路径之外使用):
|
||
// LocalizationManager.Get("ui_start")
|
||
// LocalizationManager.Get("dlg_hero", "Dialogue")
|
||
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using UnityEngine;
|
||
using BaseGames.Core;
|
||
using BaseGames.Core.Save;
|
||
|
||
namespace BaseGames.Localization
|
||
{
|
||
/// <summary>
|
||
/// 本地化管理器(MonoBehaviour,挂在 Persistent 场景)。
|
||
/// 实现 ILocalizationService + ISaveable,通过 ServiceLocator 注册。
|
||
/// 语言偏好持久化到 SaveData.Settings.Language,不使用 PlayerPrefs。
|
||
/// </summary>
|
||
public class LocalizationManager : MonoBehaviour, ILocalizationService, ISaveable
|
||
{
|
||
// 默认语言:回退链:当前语言 → 英语 → 直接返回 key
|
||
private Language _currentLanguage = Language.ChineseSimplified;
|
||
private readonly Language _fallbackLanguage = Language.English;
|
||
|
||
// 双层缓存:languageKey("ChineseSimplified/UI") → (key → value)
|
||
private readonly Dictionary<string, Dictionary<string, string>> _cache = new();
|
||
|
||
// ILocalizationService 实例事件
|
||
private event Action<Language> _onLanguageChanged;
|
||
event Action<Language> ILocalizationService.OnLanguageChanged
|
||
{
|
||
add => _onLanguageChanged += value;
|
||
remove => _onLanguageChanged -= value;
|
||
}
|
||
|
||
// ── 生命周期 ──────────────────────────────────────────────────────────
|
||
private void Awake()
|
||
{
|
||
if (ServiceLocator.GetOrDefault<ILocalizationService>() != null) { Destroy(gameObject); return; }
|
||
ServiceLocator.Register<ILocalizationService>(this);
|
||
}
|
||
|
||
private void OnEnable()
|
||
{
|
||
ServiceLocator.GetOrDefault<ISaveableRegistry>()?.Register(this);
|
||
}
|
||
|
||
private void OnDisable()
|
||
{
|
||
ServiceLocator.GetOrDefault<ISaveableRegistry>()?.Unregister(this);
|
||
}
|
||
|
||
private void OnDestroy()
|
||
{
|
||
ServiceLocator.Unregister<ILocalizationService>(this);
|
||
}
|
||
|
||
// ── ILocalizationService ──────────────────────────────────────────────
|
||
public Language CurrentLanguage => _currentLanguage;
|
||
|
||
/// <summary>切换游戏语言并通知所有订阅者刷新文本。</summary>
|
||
public void SetLanguage(Language language)
|
||
{
|
||
if (_currentLanguage == language) return;
|
||
_currentLanguage = language;
|
||
_onLanguageChanged?.Invoke(language);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取本地化字符串(显式接口实现)。
|
||
/// 查找顺序:当前语言 → 回退语言(English)→ 直接返回 key。
|
||
/// </summary>
|
||
string ILocalizationService.Get(string key, string table)
|
||
{
|
||
if (string.IsNullOrEmpty(key)) return string.Empty;
|
||
|
||
// 1. 尝试当前语言
|
||
if (TryGetFromTable(_currentLanguage, table, key, out string text))
|
||
return text;
|
||
|
||
// 2. 回退到 English
|
||
if (_currentLanguage != _fallbackLanguage &&
|
||
TryGetFromTable(_fallbackLanguage, table, key, out text))
|
||
return text;
|
||
|
||
// 3. 最终回退:原始 key
|
||
return key;
|
||
}
|
||
|
||
// ── ISaveable ─────────────────────────────────────────────────────────
|
||
public void OnSave(SaveData data)
|
||
{
|
||
if (data?.Settings == null) return;
|
||
data.Settings.Language = _currentLanguage.ToString();
|
||
}
|
||
|
||
public void OnLoad(SaveData data)
|
||
{
|
||
if (data?.Settings == null || string.IsNullOrEmpty(data.Settings.Language)) return;
|
||
if (Enum.TryParse<Language>(data.Settings.Language, out var lang))
|
||
SetLanguage(lang);
|
||
}
|
||
|
||
// ── 静态便捷方法 ─────────────────────────────────────────────────────────
|
||
/// <summary>
|
||
/// 静态快捷获取本地化字符串。委托给 ILocalizationService 实例;服务未注册时直接返回 key。
|
||
/// </summary>
|
||
public static string Get(string key, string table = "UI")
|
||
=> ServiceLocator.GetOrDefault<ILocalizationService>()?.Get(key, table) ?? key;
|
||
|
||
// ── 编辑器预览(不依赖 ServiceLocator 实例)────────────────────────────
|
||
#if UNITY_EDITOR
|
||
// 编辑器预览缓存:"{language}/{table}" → (key → value)
|
||
// 生命周期与编辑器进程相同;域重载时自动清空(static 字段随域重载重置)。
|
||
private static readonly System.Collections.Generic.Dictionary<
|
||
string,
|
||
System.Collections.Generic.Dictionary<string, string>> s_editorPreviewCache = new();
|
||
|
||
/// <summary>
|
||
/// 编辑器工具专用:不依赖运行时服务实例,直接从 Resources 读取本地化文本。
|
||
/// 结果缓存在静态字典中,同一编辑器会话内同一表只加载一次。
|
||
/// 找不到时返回 null(区别于运行时的 key 回退,便于调用方判断是否显示 key)。
|
||
/// </summary>
|
||
public static string GetEditorPreview(string key, string table = "UI",
|
||
Language language = Language.ChineseSimplified)
|
||
{
|
||
if (string.IsNullOrEmpty(key)) return null;
|
||
|
||
var dict = GetEditorTable(language, table)
|
||
?? GetEditorTable(Language.English, table); // 中文缺失时英文回退
|
||
if (dict == null) return null;
|
||
|
||
dict.TryGetValue(key, out var value);
|
||
return value; // 找不到 key 时返回 null
|
||
}
|
||
|
||
private static System.Collections.Generic.Dictionary<string, string> GetEditorTable(
|
||
Language language, string table)
|
||
{
|
||
string cacheKey = $"{language}/{table}";
|
||
if (s_editorPreviewCache.TryGetValue(cacheKey, out var cached))
|
||
return cached; // 已缓存(可能是 null 占位,表示文件不存在)
|
||
|
||
string path = $"Localization/{language}/{table}";
|
||
var asset = Resources.Load<TextAsset>(path);
|
||
if (asset == null)
|
||
{
|
||
s_editorPreviewCache[cacheKey] = null; // 记录"不存在",避免重复尝试
|
||
return null;
|
||
}
|
||
|
||
var parsed = JsonUtility.FromJson<StringTableJson>(asset.text);
|
||
if (parsed?.entries == null)
|
||
{
|
||
s_editorPreviewCache[cacheKey] = null;
|
||
return null;
|
||
}
|
||
|
||
var dict = new System.Collections.Generic.Dictionary<string, string>(
|
||
parsed.entries.Count, System.StringComparer.Ordinal);
|
||
foreach (var entry in parsed.entries)
|
||
if (!string.IsNullOrEmpty(entry.key))
|
||
dict[entry.key] = entry.value ?? string.Empty;
|
||
|
||
s_editorPreviewCache[cacheKey] = dict;
|
||
return dict;
|
||
}
|
||
#endif
|
||
private bool TryGetFromTable(Language language, string table, string key, out string value)
|
||
{
|
||
var cacheKey = $"{language}/{table}";
|
||
if (!_cache.TryGetValue(cacheKey, out var dict))
|
||
{
|
||
dict = LoadTable(language, table);
|
||
_cache[cacheKey] = dict; // 即使加载失败也存入空字典,避免每帧重试
|
||
}
|
||
if (dict != null && dict.TryGetValue(key, out value)) return true;
|
||
value = null;
|
||
return false;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从 Resources/Localization/{language}/{table}.json 加载字符串表。
|
||
/// 返回 null 表示文件不存在。
|
||
/// </summary>
|
||
private static Dictionary<string, string> LoadTable(Language language, string table)
|
||
{
|
||
string path = $"Localization/{language}/{table}";
|
||
var asset = Resources.Load<TextAsset>(path);
|
||
if (asset == null) return null;
|
||
|
||
var parsed = JsonUtility.FromJson<StringTableJson>(asset.text);
|
||
if (parsed?.entries == null) return null;
|
||
|
||
var dict = new Dictionary<string, string>(parsed.entries.Count, StringComparer.Ordinal);
|
||
foreach (var entry in parsed.entries)
|
||
if (!string.IsNullOrEmpty(entry.key))
|
||
dict[entry.key] = entry.value ?? string.Empty;
|
||
|
||
return dict;
|
||
}
|
||
|
||
// ── 序列化辅助类型 ────────────────────────────────────────────────────
|
||
|
||
[Serializable]
|
||
private class StringTableJson
|
||
{
|
||
public List<StringEntry> entries;
|
||
}
|
||
|
||
[Serializable]
|
||
private class StringEntry
|
||
{
|
||
public string key;
|
||
public string value;
|
||
}
|
||
}
|
||
}
|
||
|