This commit is contained in:
SoulliesOfficial
2026-07-24 03:43:11 -04:00
parent 48e7364981
commit fe00ecfcc7
90 changed files with 9610 additions and 461 deletions

View File

@@ -1,7 +1,10 @@
using Ichni.Menu;
using System.Collections;
using System.Collections.Generic;
using Ichni.Diagnostics;
using Sirenix.OdinInspector;
using UnityEngine;
using UnityEngine.Localization;
using UnityEngine.Localization.Settings;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
@@ -10,6 +13,7 @@ using UnityEngine.Serialization;
namespace Ichni
{
[DefaultExecutionOrder(-10000)]
public class SettingsManager : SerializedMonoBehaviour
{
private const string SettingsSaveFileName = "SettingsSave.json";
@@ -19,6 +23,21 @@ namespace Ichni
private const string BalancedQualityName = "Balanced";
private const string HighFidelityQualityName = "High Fidelity";
/// <summary>
/// 与设置界面 Dropdown 顺序严格一致的 Locale Code。
/// 不使用 AvailableLocales.Locales 的返回顺序,因为 Addressables Catalog 的条目顺序不属于稳定存档契约。
/// </summary>
private static readonly string[] SupportedLocaleCodes =
{
"zh-CN",
"en",
"zh-TW",
"ja",
"ko",
"vi-VN",
"th"
};
public static SettingsManager instance;
[FormerlySerializedAs("settingsPage")]
@@ -30,11 +49,17 @@ namespace Ichni
// 记录相机在玩家关闭后处理前的原始状态。重新开启时必须恢复原始值,不能无条件将所有相机改为开启。
private readonly Dictionary<int, bool> postProcessingCameraStates = new Dictionary<int, bool>();
// 语言切换请求统一由一个协程串行处理。协程运行期间的新请求只覆盖目标索引。
private Coroutine applyLanguageSettingsCoroutine;
private bool isLanguageRoutineRunning;
private int pendingLanguageIndex = -1;
private void Awake()
{
if (instance == null)
{
instance = this;
DontDestroyOnLoad(gameObject);
SceneManager.sceneLoaded += OnSceneLoaded;
RenderPipelineManager.beginCameraRendering += ApplyPostProcessingSettingBeforeCameraRender;
@@ -54,6 +79,14 @@ namespace Ichni
SceneManager.sceneLoaded -= OnSceneLoaded;
RenderPipelineManager.beginCameraRendering -= ApplyPostProcessingSettingBeforeCameraRender;
if (applyLanguageSettingsCoroutine != null)
{
StopCoroutine(applyLanguageSettingsCoroutine);
applyLanguageSettingsCoroutine = null;
}
isLanguageRoutineRunning = false;
instance = null;
}
@@ -82,9 +115,10 @@ namespace Ichni
{
settingsSaveData = CreateDefaultSettingsSaveData();
}
Debug.Log($"[SettingsManager] 已加载存档:{savePath},当前语言索引={settingsSaveData.languageIndex}");
ApplyAudioSettings();
ApplyGraphicSettings();
ApplyLanguageSettings();
}
/// <summary>
@@ -92,10 +126,10 @@ namespace Ichni
/// </summary>
public void ApplyAudioSettings()
{
AkSoundEngine.SetRTPCValue("MasterVolume", settingsSaveData.masterVolume);
AkSoundEngine.SetRTPCValue("MusicVolume", settingsSaveData.musicVolume);
AkSoundEngine.SetRTPCValue("SoundFXVolume", settingsSaveData.soundEffectVolume);
AkSoundEngine.SetRTPCValue("UIVolume", settingsSaveData.uiVolume);
AkUnitySoundEngine.SetRTPCValue("MasterVolume", settingsSaveData.masterVolume);
AkUnitySoundEngine.SetRTPCValue("MusicVolume", settingsSaveData.musicVolume);
AkUnitySoundEngine.SetRTPCValue("SoundFXVolume", settingsSaveData.soundEffectVolume);
AkUnitySoundEngine.SetRTPCValue("UIVolume", settingsSaveData.uiVolume);
ApplyInGameAudioEffectsSettings();
}
@@ -312,14 +346,143 @@ namespace Ichni
}
/// <summary>
/// 将当前语言索引应用到既有的 I2 与 Unity Localization 管线。
/// 语言将于 Localization 重构时改为稳定的 Locale ID本阶段只迁移数据承载方式
/// 将当前语言索引应用到 Unity Localization 管线。
/// 本方法只等待 Unity 官方 InitializationOperation并在其完成后应用玩家最新一次语言选择
/// </summary>
public void ApplyLanguageSettings()
{
I2.Loc.LocalizationManager.CurrentLanguage = MenuManager.instance.languageList[settingsSaveData.languageIndex];
LocalizationSettings.SelectedLocale = LocalizationSettings.AvailableLocales.Locales[settingsSaveData.languageIndex];
I2.Loc.LocalizationManager.UpdateSources();
if (settingsSaveData == null)
{
BuildHangTracer.Log("SETTINGS_MGR", "[ApplyLanguageSettings] settingsSaveData 为空,取消语言切换。");
return;
}
pendingLanguageIndex = settingsSaveData.languageIndex;
if (isLanguageRoutineRunning)
{
BuildHangTracer.Log(
"SETTINGS_MGR",
$"[ApplyLanguageSettings] 已有语言切换流程运行,更新待处理索引为 {pendingLanguageIndex}。");
return;
}
isLanguageRoutineRunning = true;
BuildHangTracer.Log(
"SETTINGS_MGR",
$"[ApplyLanguageSettings] 启动语言切换流程,目标索引={pendingLanguageIndex}。");
//LocalizationSettings.SelectedLocale = LocalizationSettings.AvailableLocales.GetLocale(SupportedLocaleCodes[pendingLanguageIndex]);
applyLanguageSettingsCoroutine = StartCoroutine(ApplyLanguageSettingsRoutine());
}
private IEnumerator ApplyLanguageSettingsRoutine()
{
// try/finally 确保协程正常结束、被 StopCoroutine 或发生异常时,运行状态都会被清除。
try
{
// 避开 Dropdown.onValueChanged 当前同步调用栈。
yield return null;
BuildHangTracer.Log(
"SETTINGS_COROUTINE",
$"[Coroutine] 等待 Unity Localization InitializationOperation。" +
$"CurrentState={LocalizationBootstrap.State}");
yield return LocalizationBootstrap.WaitForInitialization();
if (!LocalizationBootstrap.IsReady)
{
BuildHangTracer.Log(
"SETTINGS_COROUTINE",
$"[Coroutine] Unity Localization 初始化失败,取消语言切换。" +
$"Reason={LocalizationBootstrap.FailureReason}");
pendingLanguageIndex = -1;
yield break;
}
BuildHangTracer.Log(
"SETTINGS_COROUTINE",
$"[Coroutine] Unity Localization 已就绪。启动 Locale=" +
$"{LocalizationSettings.SelectedLocale?.Identifier.Code ?? "<null>"}");
// 处理流程运行期间收到的最新请求。
while (pendingLanguageIndex >= 0)
{
int requestedLanguageIndex = pendingLanguageIndex;
pendingLanguageIndex = -1;
if (requestedLanguageIndex < 0 ||
requestedLanguageIndex >= SupportedLocaleCodes.Length)
{
BuildHangTracer.Log(
"SETTINGS_COROUTINE",
$"[Coroutine] 语言索引无效。Index={requestedLanguageIndex}" +
$"SupportedCount={SupportedLocaleCodes.Length}。回退到简体中文。");
// 旧版本存档或损坏存档可能留下越界索引。将它修正为稳定的默认值,
// 避免首次启动时保持一个不受 SettingsSave 控制的 Locale。
requestedLanguageIndex = 0;
settingsSaveData.languageIndex = 0;
}
string targetLocaleCode = SupportedLocaleCodes[requestedLanguageIndex];
Locale targetLocale = LocalizationSettings.AvailableLocales.GetLocale(targetLocaleCode);
Locale currentLocale = LocalizationSettings.SelectedLocale;
BuildHangTracer.Log(
"SETTINGS_COROUTINE",
$"[Coroutine] 准备切换 Locale" +
$"{currentLocale?.Identifier.Code ?? "<null>"} -> " +
$"{targetLocale?.Identifier.Code ?? "<null>"}");
if (targetLocale == null)
{
BuildHangTracer.Log(
"SETTINGS_COROUTINE",
$"[Coroutine] Localization Settings 中不存在 Locale '{targetLocaleCode}',取消本次切换。");
continue;
}
if (currentLocale == targetLocale)
{
BuildHangTracer.Log("SETTINGS_COROUTINE", "[Coroutine] 目标 Locale 与当前 Locale 相同,跳过切换。");
continue;
}
LocalizationSettings.SelectedLocale = targetLocale;
// setter 会同步更新 SelectedLocale并通知 LocalizedString / LocalizedAsset 按需刷新。
// 初始化已在本协程开头完成,因此此处不会触发同步 Locale 加载。
yield return null;
Locale appliedLocale = LocalizationSettings.SelectedLocale;
if (appliedLocale == targetLocale)
{
BuildHangTracer.Log(
"SETTINGS_COROUTINE",
$"<<< [CRITICAL] SelectedLocale 已切换:{targetLocale.Identifier.Code}");
}
else
{
BuildHangTracer.Log(
"SETTINGS_COROUTINE",
$"[Coroutine] SelectedLocale 校验失败。Expected={targetLocale.Identifier.Code}" +
$"Actual={appliedLocale?.Identifier.Code ?? "<null>"}");
}
}
}
finally
{
applyLanguageSettingsCoroutine = null;
isLanguageRoutineRunning = false;
BuildHangTracer.Log(
"SETTINGS_COROUTINE",
$"[Coroutine] 语言切换流程结束。PendingIndex={pendingLanguageIndex}");
}
}
/// <summary>