UI相关优化补充
This commit is contained in:
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
55
Assets/_Game/Scripts/Core/RequiredFieldAttribute.cs
Normal file
55
Assets/_Game/Scripts/Core/RequiredFieldAttribute.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/_Game/Scripts/Core/RequiredFieldAttribute.cs.meta
Normal file
11
Assets/_Game/Scripts/Core/RequiredFieldAttribute.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ee796ace5d7a52643a001ca1968b6e28
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user