三个让 Localization
原地爆炸 的坑
从崩溃时序、切换闪烁、状态污染三个真实场景出发,
看清 Unity Localization 几个没人告诉你的隐藏契约。
本辑目录
不要在 Completed 里切语言
ArgumentException · 同 key 冲突
切换语言时的闪烁
StringTable 与 AssetTable 异步竞速
退出时别访问 SelectedLocale
ResetState 触发系统语言污染
不要在 InitializationOperation
的 Completed 回调中
调用 SetSelectedLocale
为什么崩?
Completed 在 Complete() 内部同步触发,调用栈尚未展开。
调用栈还在 Addressables 完成逻辑里,SetSelectedLocale 又启动了新的异步操作链 —— 撞 key 就崩。
两种修复方式
核心思路:把语言选择搬出 init 流程,或等 init 真正结束。
IStartupLocaleSelector
把语言选择烘焙进初始化流程,零冗余。
[Serializable]
public class SavedLocaleSelector
: IStartupLocaleSelector
{
public Locale GetStartupLocale(
ILocalesProvider available)
{
var code = LoadFromSave();
if (string.IsNullOrEmpty(code))
return null;
return available.GetLocale(
new LocaleIdentifier(code));
}
}
→ Inspector 放到 Startup Locale Selectors 列表第一位
协程 yield 等 init 结束
yield 让 Complete() 完全返回后再切。
IEnumerator ApplySavedLocale()
{
var op = LocalizationSettings
.InitializationOperation;
if (!op.IsDone)
yield return op;
// 此时 Complete() 已真正结束
var target = LocalizationSettings
.AvailableLocales
.GetLocale(
new LocaleIdentifier(code));
if (target != null)
LocalizationSettings
.SelectedLocale = target;
}
Completed 回调本身是安全的同步委托调用,
但 不要在其中启动新的 Addressables 异步操作链。
普通同步 Unity API(SetActive、设置文本、读取已加载的数据)在 Completed 里都没问题。
限制仅在于:不要启动会改变 Addressables 资源状态的操作 —— SetSelectedLocale 内部会这么做,所以是禁区。
切换语言时
文本和字体
不是同时更新
为什么闪?
SetSelectedLocale 释放旧表后,StringTable 和 AssetTable 走两条独立的异步加载链。
肉眼可见的"先变字体后变文本"(或反过来)—— 切回 A 语言时同样问题。
让切换瞬间完成
思路:表已经在内存里 → 切语言就是改指针,不会有加载延迟。
Preload All Tables
在 LocalizationSettings.asset 里勾选所有语言的 Preload,让所有 StringTable 和 AssetTable 在 init 时全部加载并常驻内存。
→ 适合语言数量不多(2-5 种)的项目
代码持有 Table 引用
在代码里持久持有 LocalizedStringTable 等引用,订阅 TableChanged 事件 —— 系统会为新语言重新加载表并通知你,而不是彻底释放。
退出游戏时
不要访问
SelectedLocale
触发链:ResetState 把缓存清空
退出 Play Mode 时 ResetState() 清空缓存,再次访问 SelectedLocale 会触发完整的重新初始化管线。
现在所有坑都准备好了 —— 接下来 SelectActiveLocale 会遍历 Selectors 找语言 → 下一页。
Selector 链掉链子,系统语言兜底
Domain 销毁中,自定义 Selector 拿不到上下文,3 个里 2 个返回 null,兜底的那个成了替罪羊。
玩家精心选的语言被静默覆盖 —— 下一次启动看到的就是 ja,而且你还不知道为什么。
核心原则:用字符串缓存语言代码
退出时读缓存,绝不碰 Localization 系统。
// Model 层 — 纯字段,不碰 LocalizationSettings
string _languageCode = "zh-CN";
public string CurrentLanguageCode => _languageCode;
// Service 层 — 通过事件同步,退出时先取消订阅再保存
void OnInitialize()
{
LoadGameSettings();
LocalizationSettings.SelectedLocaleChanged
+= OnSelectedLocaleChanged;
Application.quitting += OnApplicationQuitting;
}
void OnSelectedLocaleChanged(Locale locale)
{
if (locale != null)
_languageCode = locale.Identifier.Code;
}
void OnApplicationQuitting()
{
// 先断开事件订阅,阻止 Localization 清理时的回调覆盖
LocalizationSettings.SelectedLocaleChanged
-= OnSelectedLocaleChanged;
SaveGameSettings(); // 再保存
}
关键:先取消订阅,再保存 —— 顺序反了会被 Localization 清理时的回调污染。
三句话总结
语言选择走 IStartupLocaleSelector,或 yield 等 init 完全结束。
解法是让所有表常驻内存(Preload),或代码持有引用。
断开 Localization 事件订阅后再保存,避免系统语言污染存档。
感谢观看
原文:Localization 踩坑日志 ①
Unity 2022.3.62f3 · Localization 1.5.3 · Addressables 1.22.3