291 lines
10 KiB
C#
291 lines
10 KiB
C#
using System;
|
||
using Cielonos.MainGame.UI;
|
||
using SLSUtilities.General;
|
||
using SLSUtilities.UI;
|
||
using SLSUtilities.WwiseAssistance;
|
||
using UnityEngine;
|
||
using UnityEngine.Localization.Settings;
|
||
|
||
namespace Cielonos.Settings
|
||
{
|
||
/// <summary>
|
||
/// 游戏设置的 Singleton 管理器。
|
||
/// <para>
|
||
/// <b>数据流:</b><br/>
|
||
/// 字段初始化器提供编译期默认值 →
|
||
/// <see cref="Awake"/> 中通过 ES3 加载磁盘数据覆盖 →
|
||
/// <see cref="Start"/> 中调用 <see cref="ApplyAll"/> 推送到各子系统。
|
||
/// </para>
|
||
/// <para>
|
||
/// <b>扩展方式:</b>直接在对应 Settings 类(<see cref="GameplaySettings"/>
|
||
/// / <see cref="GraphicsSettings"/> / <see cref="SoundSettings"/>
|
||
/// / <see cref="ControlsSettings"/>)中添加带默认值的新字段。
|
||
/// ES3 反序列化时,旧存档缺失的字段自动获取字段初始化器的默认值。
|
||
/// 然后在对应的 Apply 方法中添加新字段的应用逻辑即可。
|
||
/// </para>
|
||
/// </summary>
|
||
public class GameSettingsManager : Singleton<GameSettingsManager>
|
||
{
|
||
// ──────────────────────── 持久化常量 ────────────────────────
|
||
|
||
private const string SaveFileName = "GameSettings.es3";
|
||
private const string KeyGameplay = "gameplay";
|
||
private const string KeyGraphics = "graphics";
|
||
private const string KeySound = "sound";
|
||
private const string KeyControls = "controls";
|
||
|
||
// ──────────────────────── Wwise RTPC 名称 ──────────────────
|
||
|
||
private const string RtpcMasterVolume = "MasterVolume";
|
||
private const string RtpcMusicVolume = "MusicVolume";
|
||
private const string RtpcSfxVolume = "SFXVolume";
|
||
|
||
// ──────────────────────── 运行时数据 ───────────────────────
|
||
|
||
public GameplaySettings gameplay = new();
|
||
public GraphicsSettings graphics = new();
|
||
public SoundSettings sound = new();
|
||
public ControlsSettings controls = new();
|
||
|
||
// ──────────────────────── 事件 ─────────────────────────────
|
||
|
||
/// <summary>Gameplay 设置被应用后触发。</summary>
|
||
public static event Action OnGameplaySettingsApplied;
|
||
|
||
/// <summary>Graphics 设置被应用后触发。</summary>
|
||
public static event Action OnGraphicsSettingsApplied;
|
||
|
||
/// <summary>Sound 设置被应用后触发。</summary>
|
||
public static event Action OnSoundSettingsApplied;
|
||
|
||
/// <summary>Controls 设置被应用后触发。</summary>
|
||
public static event Action OnControlsSettingsApplied;
|
||
|
||
// ──────────────────────── 生命周期 ─────────────────────────
|
||
|
||
protected override void Awake()
|
||
{
|
||
base.Awake();
|
||
Load();
|
||
}
|
||
|
||
private void Start()
|
||
{
|
||
ApplyAll();
|
||
}
|
||
|
||
// ──────────────────────── 持久化 ───────────────────────────
|
||
|
||
/// <summary>
|
||
/// 将所有设置分类保存到磁盘。每个分类使用独立的 ES3 Key,
|
||
/// 某个分类数据损坏时不影响其他分类。
|
||
/// </summary>
|
||
public void Save()
|
||
{
|
||
try
|
||
{
|
||
ES3.Save(KeyGameplay, gameplay, SaveFileName);
|
||
ES3.Save(KeyGraphics, graphics, SaveFileName);
|
||
ES3.Save(KeySound, sound, SaveFileName);
|
||
ES3.Save(KeyControls, controls, SaveFileName);
|
||
Debug.Log("[GameSettingsManager] Settings saved.");
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
Debug.LogError($"[GameSettingsManager] Save failed: {e.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从磁盘加载设置。每个分类独立加载,某个分类缺失或损坏时
|
||
/// 保留字段初始化器的默认值。
|
||
/// </summary>
|
||
public void Load()
|
||
{
|
||
try
|
||
{
|
||
if (!ES3.FileExists(SaveFileName)) return;
|
||
|
||
if (ES3.KeyExists(KeyGameplay, SaveFileName))
|
||
gameplay = ES3.Load<GameplaySettings>(KeyGameplay, SaveFileName);
|
||
|
||
if (ES3.KeyExists(KeyGraphics, SaveFileName))
|
||
graphics = ES3.Load<GraphicsSettings>(KeyGraphics, SaveFileName);
|
||
|
||
if (ES3.KeyExists(KeySound, SaveFileName))
|
||
sound = ES3.Load<SoundSettings>(KeySound, SaveFileName);
|
||
|
||
if (ES3.KeyExists(KeyControls, SaveFileName))
|
||
controls = ES3.Load<ControlsSettings>(KeyControls, SaveFileName);
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
Debug.LogError($"[GameSettingsManager] Load failed: {e.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 删除设置存档文件。
|
||
/// </summary>
|
||
public void DeleteSaveFile()
|
||
{
|
||
try
|
||
{
|
||
if (ES3.FileExists(SaveFileName))
|
||
{
|
||
ES3.DeleteFile(SaveFileName);
|
||
Debug.Log("[GameSettingsManager] Save file deleted.");
|
||
}
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
Debug.LogError($"[GameSettingsManager] Delete save failed: {e.Message}");
|
||
}
|
||
}
|
||
|
||
// ──────────────────────── Apply ────────────────────────────
|
||
|
||
/// <summary>
|
||
/// 应用所有分类的设置到对应系统。
|
||
/// </summary>
|
||
public void ApplyAll()
|
||
{
|
||
ApplyGameplay();
|
||
ApplyGraphics();
|
||
ApplySound();
|
||
ApplyControls();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 应用 Gameplay 设置。
|
||
/// </summary>
|
||
public void ApplyGameplay()
|
||
{
|
||
// Locale
|
||
var availableLocales = LocalizationSettings.AvailableLocales;
|
||
if (availableLocales != null)
|
||
{
|
||
var locale = availableLocales.GetLocale(gameplay.locale);
|
||
if (locale != null)
|
||
LocalizationSettings.SelectedLocale = locale;
|
||
}
|
||
|
||
// FPS 显示
|
||
if (PlayerCanvas.Instance != null)
|
||
PlayerCanvas.Instance.frameRateText.gameObject.SetActive(gameplay.showFPS);
|
||
|
||
OnGameplaySettingsApplied?.Invoke();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 应用 Graphics 设置。
|
||
/// </summary>
|
||
public void ApplyGraphics()
|
||
{
|
||
// 分辨率与全屏模式
|
||
if (graphics.resolutionWidth > 0 && graphics.resolutionHeight > 0)
|
||
{
|
||
Screen.SetResolution(
|
||
graphics.resolutionWidth,
|
||
graphics.resolutionHeight,
|
||
graphics.fullscreenMode
|
||
);
|
||
}
|
||
else
|
||
{
|
||
Screen.fullScreenMode = graphics.fullscreenMode;
|
||
}
|
||
|
||
// 画质预设
|
||
if (graphics.qualityLevel >= 0)
|
||
QualitySettings.SetQualityLevel(graphics.qualityLevel, applyExpensiveChanges: true);
|
||
|
||
// VSync
|
||
QualitySettings.vSyncCount = graphics.vSync ? 1 : 0;
|
||
|
||
// 帧率上限
|
||
Application.targetFrameRate = graphics.targetFrameRate;
|
||
|
||
OnGraphicsSettingsApplied?.Invoke();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 应用 Sound 设置(通过 Wwise RTPC)。
|
||
/// </summary>
|
||
public void ApplySound()
|
||
{
|
||
if (AudioManager.Instance == null) return;
|
||
|
||
AudioManager.Instance.SetRTPC(RtpcMasterVolume, sound.masterVolume);
|
||
AudioManager.Instance.SetRTPC(RtpcMusicVolume, sound.musicVolume);
|
||
AudioManager.Instance.SetRTPC(RtpcSfxVolume, sound.sfxVolume);
|
||
|
||
OnSoundSettingsApplied?.Invoke();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 应用 Controls 设置。
|
||
/// <para>
|
||
/// 按键绑定覆盖由 <c>PlayerInputSubcontroller</c> 消费:
|
||
/// 在其 <c>Initialize()</c> 中读取 <see cref="controls"/>.<see cref="ControlsSettings.bindingOverridesJson"/>
|
||
/// 并调用 <c>InputActionAsset.LoadBindingOverridesFromJson()</c>,
|
||
/// 然后重新初始化 <see cref="InputBindingResolver"/>。
|
||
/// </para>
|
||
/// </summary>
|
||
public void ApplyControls()
|
||
{
|
||
OnControlsSettingsApplied?.Invoke();
|
||
}
|
||
|
||
// ──────────────────────── Reset ────────────────────────────
|
||
|
||
/// <summary>
|
||
/// 重置所有设置为默认值并立即应用。
|
||
/// </summary>
|
||
public void ResetAllToDefault()
|
||
{
|
||
gameplay = new GameplaySettings();
|
||
graphics = new GraphicsSettings();
|
||
sound = new SoundSettings();
|
||
controls = new ControlsSettings();
|
||
ApplyAll();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 重置 Gameplay 设置为默认值并立即应用。
|
||
/// </summary>
|
||
public void ResetGameplayToDefault()
|
||
{
|
||
gameplay = new GameplaySettings();
|
||
ApplyGameplay();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 重置 Graphics 设置为默认值并立即应用。
|
||
/// </summary>
|
||
public void ResetGraphicsToDefault()
|
||
{
|
||
graphics = new GraphicsSettings();
|
||
ApplyGraphics();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 重置 Sound 设置为默认值并立即应用。
|
||
/// </summary>
|
||
public void ResetSoundToDefault()
|
||
{
|
||
sound = new SoundSettings();
|
||
ApplySound();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 重置 Controls 设置为默认值并立即应用。
|
||
/// </summary>
|
||
public void ResetControlsToDefault()
|
||
{
|
||
controls = new ControlsSettings();
|
||
ApplyControls();
|
||
}
|
||
}
|
||
}
|