UI相关优化补充

This commit is contained in:
2026-05-25 13:21:41 +08:00
parent 3c812cfb41
commit a1f9122153
54 changed files with 2008 additions and 112 deletions

View File

@@ -3,6 +3,17 @@ using BaseGames.Core.Events;
namespace BaseGames.Core
{
/// <summary>
/// 色盲滤镜模式。运行时由后期处理(如 URP Volume读取并切换对应的 LUT/Shader。
/// </summary>
public enum ColorblindMode
{
None = 0,
Protanopia = 1,
Deuteranopia = 2,
Tritanopia = 3,
}
/// <summary>
/// 游戏全局设置数据(运行时值)。
/// </summary>
@@ -21,6 +32,16 @@ namespace BaseGames.Core
public string Language = "zh-CN";
public bool ShowSpeedrunTimer = false;
// ── 可访问性 ────────────────────────────────────────────────────────
[Tooltip("UI 整体缩放系数0.8 ~ 1.5),通过 CanvasScaler 应用。")]
public float UIScale = 1f;
[Tooltip("色盲滤镜模式。")]
public ColorblindMode ColorblindMode = ColorblindMode.None;
[Tooltip("镜头/UI 震动开关;关闭后受击晃动、命中冲击等屏幕震动被屏蔽。")]
public bool ScreenShakeEnabled = true;
}
/// <summary>
@@ -46,6 +67,11 @@ namespace BaseGames.Core
[Header("Speedrun")]
public bool ShowSpeedrunTimer = false;
[Header("Accessibility")]
[Range(0.8f, 1.5f)] public float DefaultUIScale = 1f;
public ColorblindMode DefaultColorblindMode = ColorblindMode.None;
public bool DefaultScreenShakeEnabled = true;
/// <summary>将 SO 默认值填入 GlobalSettingsData。</summary>
public GlobalSettingsData CreateDefault() => new GlobalSettingsData
{
@@ -58,6 +84,9 @@ namespace BaseGames.Core
FullScreen = DefaultFullScreen,
Language = DefaultLanguage,
ShowSpeedrunTimer = ShowSpeedrunTimer,
UIScale = DefaultUIScale,
ColorblindMode = DefaultColorblindMode,
ScreenShakeEnabled = DefaultScreenShakeEnabled,
};
}
}

View File

@@ -17,6 +17,12 @@ namespace BaseGames.Core
void SetVSync(bool enabled);
void SetTargetFrameRate(int fps);
void SetLanguage(string localeCode);
// ── 可访问性 ────────────────────────────────────────────────────────
void SetUIScale(float scale);
void SetColorblindMode(ColorblindMode mode);
void SetScreenShakeEnabled(bool enabled);
void Save();
}
}

View File

@@ -0,0 +1,55 @@
using UnityEngine;
namespace BaseGames.Core
{
/// <summary>
/// 标记一个序列化字段必填。运行期 / Inspector 漏配时给出明确提示,
/// 减少策划"为什么没显示"的排查成本。
///
/// 用法:[SerializeField, RequiredField] private GameObject _root;
///
/// 表现:
/// - Inspector 中字段未赋值时显示红色 HelpBox 并加红框(见 Editor/RequiredFieldDrawer.cs
/// - 调用方在 OnValidate / Awake 中可调用 RequiredFieldValidator.ValidateAll(this) 触发运行期警告。
/// </summary>
[System.AttributeUsage(System.AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
public class RequiredFieldAttribute : PropertyAttribute
{
public readonly string Hint;
public RequiredFieldAttribute(string hint = null) { Hint = hint; }
}
/// <summary>运行期辅助:在 OnValidate / Awake 中调用,扫描自身被 [RequiredField] 标注的字段。</summary>
public static class RequiredFieldValidator
{
/// <summary>
/// 反射扫描 target 上所有 [RequiredField] 字段,未赋值时 Debug.LogWarning。
/// 建议仅在 OnValidate / Awake 中调用(运行时调用反射有性能开销)。
/// </summary>
public static void ValidateAll(Object target)
{
if (target == null) return;
var type = target.GetType();
var fields = type.GetFields(System.Reflection.BindingFlags.Instance
| System.Reflection.BindingFlags.Public
| System.Reflection.BindingFlags.NonPublic);
foreach (var f in fields)
{
var attr = (RequiredFieldAttribute)System.Attribute.GetCustomAttribute(f, typeof(RequiredFieldAttribute));
if (attr == null) continue;
var value = f.GetValue(target);
if (IsNullOrMissingRef(value))
{
var hint = string.IsNullOrEmpty(attr.Hint) ? "" : $"{attr.Hint}";
Debug.LogWarning($"[RequiredField] {type.Name}.{f.Name} 未赋值{hint}", target);
}
}
}
private static bool IsNullOrMissingRef(object value)
{
if (value is Object uo) return uo == null; // 含 Missing Reference 的"虚假 null"也算
return value == null;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ee796ace5d7a52643a001ca1968b6e28
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,3 +1,4 @@
using System;
using System.IO;
using UnityEngine;
@@ -5,6 +6,8 @@ namespace BaseGames.Core
{
/// <summary>
/// 全局设置管理器。从 GlobalSettingsSO 读取默认值,从文件加载用户覆盖。
/// 任何 Setter 调用 Save() 后会触发 <see cref="SettingsChanged"/> 静态事件,
/// 供 UIScaleApplier / ColorblindApplier / CameraShake 等订阅。
/// </summary>
[DefaultExecutionOrder(-800)]
public class SettingsManager : MonoBehaviour, ISettingsService
@@ -18,6 +21,9 @@ namespace BaseGames.Core
public GlobalSettingsData Current => _current;
/// <summary>设置变更后触发(用于 UIScaleApplier、色盲滤镜、Camera Shake 等订阅)。</summary>
public static event Action<GlobalSettingsData> SettingsChanged;
private void Awake()
{
ServiceLocator.Register<ISettingsService>(this);
@@ -28,6 +34,7 @@ namespace BaseGames.Core
{
_current = Load() ?? _defaultSettings?.CreateDefault() ?? new GlobalSettingsData();
Apply(_current);
SettingsChanged?.Invoke(_current);
}
private GlobalSettingsData Load()
@@ -55,6 +62,7 @@ namespace BaseGames.Core
{
Debug.LogWarning($"[SettingsManager] 设置保存失败: {e.Message}");
}
SettingsChanged?.Invoke(_current);
}
private void Apply(GlobalSettingsData data)
@@ -66,13 +74,13 @@ namespace BaseGames.Core
Screen.fullScreenMode = FullScreenMode.FullScreenWindow;
}
// ── 音量设置(调用 AudioManager────────────────────
// ── 音量设置(调用 AudioManager─────────────────────────────────────
public void SetMasterVolume(float v) { _current.MasterVolume = v; Save(); }
public void SetBGMVolume(float v) { _current.BGMVolume = v; Save(); }
public void SetSFXVolume(float v) { _current.SFXVolume = v; Save(); }
public void SetAmbientVolume(float v) { _current.AmbientVolume = v; Save(); }
// ── 画面设置 ──────────────────────────────────────────────────────
// ── 画面设置 ──────────────────────────────────────────────────────────
public void SetResolution(int w, int h, FullScreenMode mode)
{
Screen.SetResolution(w, h, mode);
@@ -98,6 +106,23 @@ namespace BaseGames.Core
Save();
}
// ── 可访问性 ──────────────────────────────────────────────────────────
public void SetUIScale(float scale)
{
_current.UIScale = Mathf.Clamp(scale, 0.5f, 2f);
Save();
}
public void SetColorblindMode(ColorblindMode mode)
{
_current.ColorblindMode = mode;
Save();
}
public void SetScreenShakeEnabled(bool enabled)
{
_current.ScreenShakeEnabled = enabled;
Save();
}
private void OnDestroy()
{
ServiceLocator.Unregister<ISettingsService>(this);