多轮审查和修复
This commit is contained in:
89
Assets/Scripts/Support/Accessibility/AccessibilityManager.cs
Normal file
89
Assets/Scripts/Support/Accessibility/AccessibilityManager.cs
Normal file
@@ -0,0 +1,89 @@
|
||||
using UnityEngine;
|
||||
using BaseGames.Core.Events;
|
||||
|
||||
namespace BaseGames.Support.Accessibility
|
||||
{
|
||||
/// <summary>
|
||||
/// 无障碍功能管理器(架构 16_SupportingModules §6.1)。
|
||||
/// 响应设置变更,广播色盲模式事件;提供 CanPlayScreenShake() 供 FeedbackSystem 查询。
|
||||
/// </summary>
|
||||
public class AccessibilityManager : MonoBehaviour
|
||||
{
|
||||
[Header("设置资产")]
|
||||
[SerializeField] private AccessibilitySettingsSO _settings;
|
||||
|
||||
[Header("事件频道(输出)")]
|
||||
[SerializeField] private ColorblindModeEventChannelSO _onColorblindModeChanged;
|
||||
[SerializeField] private BoolEventChannelSO _onScreenShakeChanged;
|
||||
|
||||
private static AccessibilityManager _instance;
|
||||
|
||||
// ── 静态查询接口(供 FeedbackSystem 使用) ───────────────────────────────
|
||||
public static bool CanPlayScreenShake()
|
||||
=> _instance == null || (_instance._settings != null && _instance._settings.ScreenShake);
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (_instance != null && _instance != this)
|
||||
{
|
||||
Debug.LogWarning("[AccessibilityManager] 已存在实例,请确保本组件仅放置在 Persistent 场景中。", this);
|
||||
Destroy(this);
|
||||
return;
|
||||
}
|
||||
_instance = this;
|
||||
if (_settings != null)
|
||||
_settings.Load();
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
// 初始化时广播当前色盲模式
|
||||
if (_settings != null)
|
||||
_onColorblindModeChanged?.Raise(_settings.ColorblindMode);
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (_instance == this) _instance = null;
|
||||
}
|
||||
|
||||
/// <summary>应用新的设置并持久化。</summary>
|
||||
public void Apply(AccessibilitySettingsSO newSettings)
|
||||
{
|
||||
_settings.ScreenShake = newSettings.ScreenShake;
|
||||
_settings.ReducedFlash = newSettings.ReducedFlash;
|
||||
_settings.LargeText = newSettings.LargeText;
|
||||
_settings.HighContrast = newSettings.HighContrast;
|
||||
_settings.UIScale = newSettings.UIScale;
|
||||
|
||||
bool colorblindChanged = _settings.ColorblindMode != newSettings.ColorblindMode;
|
||||
_settings.ColorblindMode = newSettings.ColorblindMode;
|
||||
|
||||
_settings.Save();
|
||||
|
||||
_onScreenShakeChanged?.Raise(_settings.ScreenShake);
|
||||
if (colorblindChanged)
|
||||
_onColorblindModeChanged?.Raise(_settings.ColorblindMode);
|
||||
}
|
||||
|
||||
/// <summary>直接设置色盲模式。</summary>
|
||||
public void SetColorblindMode(ColorblindMode mode)
|
||||
{
|
||||
if (_settings == null) return;
|
||||
_settings.ColorblindMode = mode;
|
||||
_settings.Save();
|
||||
_onColorblindModeChanged?.Raise(mode);
|
||||
}
|
||||
|
||||
/// <summary>直接设置屏幕抖动开关。</summary>
|
||||
public void SetScreenShake(bool enabled)
|
||||
{
|
||||
if (_settings == null) return;
|
||||
_settings.ScreenShake = enabled;
|
||||
_settings.Save();
|
||||
_onScreenShakeChanged?.Raise(enabled);
|
||||
}
|
||||
|
||||
public AccessibilitySettingsSO Settings => _settings;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: db1f332f741502f4385b7d639582a8cf
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,46 @@
|
||||
using UnityEngine;
|
||||
using BaseGames.Core.Events;
|
||||
|
||||
/// <summary>
|
||||
/// 无障碍设置数据 SO(架构 16_SupportingModules §6)。
|
||||
/// 包含屏幕抖动、闪光减弱、色盲模式等辅助功能开关。
|
||||
/// </summary>
|
||||
[CreateAssetMenu(menuName = "Settings/AccessibilitySettings", fileName = "SET_Accessibility")]
|
||||
public class AccessibilitySettingsSO : ScriptableObject
|
||||
{
|
||||
private const string PrefsPrefix = "accessibility_";
|
||||
|
||||
[Header("屏幕体感")]
|
||||
public bool ScreenShake = true;
|
||||
public bool ReducedFlash = false;
|
||||
|
||||
[Header("色盲模式")]
|
||||
public ColorblindMode ColorblindMode = ColorblindMode.None;
|
||||
|
||||
[Header("文字与 UI")]
|
||||
public bool LargeText = false;
|
||||
public bool HighContrast = false;
|
||||
public float UIScale = 1f;
|
||||
|
||||
// ── 持久化 ──────────────────────────────────────────────────────────────
|
||||
public void Save()
|
||||
{
|
||||
PlayerPrefs.SetInt (PrefsPrefix + "ScreenShake", ScreenShake ? 1 : 0);
|
||||
PlayerPrefs.SetInt (PrefsPrefix + "ReducedFlash", ReducedFlash ? 1 : 0);
|
||||
PlayerPrefs.SetInt (PrefsPrefix + "ColorblindMode",(int)ColorblindMode);
|
||||
PlayerPrefs.SetInt (PrefsPrefix + "LargeText", LargeText ? 1 : 0);
|
||||
PlayerPrefs.SetInt (PrefsPrefix + "HighContrast", HighContrast ? 1 : 0);
|
||||
PlayerPrefs.SetFloat (PrefsPrefix + "UIScale", UIScale);
|
||||
PlayerPrefs.Save();
|
||||
}
|
||||
|
||||
public void Load()
|
||||
{
|
||||
ScreenShake = PlayerPrefs.GetInt (PrefsPrefix + "ScreenShake", 1) == 1;
|
||||
ReducedFlash = PlayerPrefs.GetInt (PrefsPrefix + "ReducedFlash", 0) == 1;
|
||||
ColorblindMode = (ColorblindMode)PlayerPrefs.GetInt(PrefsPrefix + "ColorblindMode", 0);
|
||||
LargeText = PlayerPrefs.GetInt (PrefsPrefix + "LargeText", 0) == 1;
|
||||
HighContrast = PlayerPrefs.GetInt (PrefsPrefix + "HighContrast", 0) == 1;
|
||||
UIScale = PlayerPrefs.GetFloat(PrefsPrefix + "UIScale", 1f);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a1cbca9ad14985d469becb19e074f8a5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
137
Assets/Scripts/Support/Accessibility/ColorBlindFilter.cs
Normal file
137
Assets/Scripts/Support/Accessibility/ColorBlindFilter.cs
Normal file
@@ -0,0 +1,137 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
using UnityEngine.Rendering.Universal;
|
||||
using BaseGames.Core.Events;
|
||||
|
||||
/// <summary>
|
||||
/// 色盲滤镜 URP Renderer Feature(架构 16_SupportingModules §6.2)。
|
||||
/// 通过颜色矩阵将画面转换到对应色盲友好的色彩空间。
|
||||
/// 配置:在 URP Renderer Asset 的 Renderer Features 列表中添加此 Feature。
|
||||
/// 订阅 EVT_ColorBlindModeChanged 事件切换模式。
|
||||
/// </summary>
|
||||
public class ColorBlindFilter : ScriptableRendererFeature
|
||||
{
|
||||
[System.Serializable]
|
||||
public class Settings
|
||||
{
|
||||
public ColorblindMode Mode = ColorblindMode.None;
|
||||
public float Strength = 1f;
|
||||
public RenderPassEvent PassEvent = RenderPassEvent.AfterRenderingPostProcessing;
|
||||
}
|
||||
|
||||
[SerializeField] public Settings settings = new();
|
||||
|
||||
[Header("事件频道")]
|
||||
[SerializeField] private ColorblindModeEventChannelSO _onColorblindModeChanged;
|
||||
|
||||
private ColorBlindRenderPass _pass;
|
||||
private readonly CompositeDisposable _subs = new();
|
||||
|
||||
public override void Create()
|
||||
{
|
||||
_pass = new ColorBlindRenderPass(settings);
|
||||
_pass.renderPassEvent = settings.PassEvent;
|
||||
}
|
||||
|
||||
public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
|
||||
{
|
||||
if (settings.Mode == ColorblindMode.None) return;
|
||||
renderer.EnqueuePass(_pass);
|
||||
}
|
||||
|
||||
private void OnEnable() => _onColorblindModeChanged?.Subscribe(SetMode).AddTo(_subs);
|
||||
private void OnDisable() => _subs.Clear();
|
||||
|
||||
/// <summary>切换色盲模式(由 AccessibilityManager 通过事件调用)。</summary>
|
||||
public void SetMode(ColorblindMode mode)
|
||||
{
|
||||
settings.Mode = mode;
|
||||
_pass?.UpdateSettings(settings);
|
||||
}
|
||||
|
||||
// ── 内部 RenderPass ───────────────────────────────────────────────────────
|
||||
private class ColorBlindRenderPass : ScriptableRenderPass
|
||||
{
|
||||
private Settings _settings;
|
||||
private Material _material;
|
||||
|
||||
private static readonly int ColorMatrixId = Shader.PropertyToID("_ColorMatrix");
|
||||
|
||||
public ColorBlindRenderPass(Settings settings)
|
||||
{
|
||||
_settings = settings;
|
||||
_material = CoreUtils.CreateEngineMaterial(Shader.Find("Hidden/ColorBlindFilter"));
|
||||
}
|
||||
|
||||
public void UpdateSettings(Settings s)
|
||||
{
|
||||
_settings = s;
|
||||
}
|
||||
|
||||
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
|
||||
{
|
||||
if (_material == null || _settings.Mode == ColorblindMode.None) return;
|
||||
|
||||
var matrix = GetColorMatrix(_settings.Mode, _settings.Strength);
|
||||
_material.SetMatrix(ColorMatrixId, matrix);
|
||||
|
||||
CommandBuffer cmd = CommandBufferPool.Get("ColorBlindFilter");
|
||||
// Blit with material applies the color matrix shader
|
||||
Blit(cmd, ref renderingData, _material);
|
||||
context.ExecuteCommandBuffer(cmd);
|
||||
CommandBufferPool.Release(cmd);
|
||||
}
|
||||
|
||||
/// <summary>返回指定色盲类型的颜色变换矩阵。</summary>
|
||||
private static Matrix4x4 GetColorMatrix(ColorblindMode mode, float strength)
|
||||
{
|
||||
// 基于 Brettel/Viénot 等人的色盲模拟矩阵
|
||||
Matrix4x4 identity = Matrix4x4.identity;
|
||||
Matrix4x4 sim = mode switch
|
||||
{
|
||||
ColorblindMode.Protanopia => Protanopia,
|
||||
ColorblindMode.Deuteranopia => Deuteranopia,
|
||||
ColorblindMode.Tritanopia => Tritanopia,
|
||||
ColorblindMode.Achromatopsia=> Achromatopsia,
|
||||
_ => identity,
|
||||
};
|
||||
|
||||
// 按强度插值
|
||||
if (Mathf.Approximately(strength, 1f)) return sim;
|
||||
return Lerp(identity, sim, strength);
|
||||
}
|
||||
|
||||
private static Matrix4x4 Lerp(Matrix4x4 a, Matrix4x4 b, float t)
|
||||
{
|
||||
var result = new Matrix4x4();
|
||||
for (int i = 0; i < 16; i++)
|
||||
result[i] = Mathf.Lerp(a[i], b[i], t);
|
||||
return result;
|
||||
}
|
||||
|
||||
// ── 预定义色盲矩阵 ───────────────────────────────────────────────────
|
||||
private static readonly Matrix4x4 Protanopia = new(
|
||||
new Vector4(0.567f, 0.433f, 0.000f, 0),
|
||||
new Vector4(0.558f, 0.442f, 0.000f, 0),
|
||||
new Vector4(0.000f, 0.242f, 0.758f, 0),
|
||||
new Vector4(0, 0, 0, 1));
|
||||
|
||||
private static readonly Matrix4x4 Deuteranopia = new(
|
||||
new Vector4(0.625f, 0.375f, 0.000f, 0),
|
||||
new Vector4(0.700f, 0.300f, 0.000f, 0),
|
||||
new Vector4(0.000f, 0.300f, 0.700f, 0),
|
||||
new Vector4(0, 0, 0, 1));
|
||||
|
||||
private static readonly Matrix4x4 Tritanopia = new(
|
||||
new Vector4(0.950f, 0.050f, 0.000f, 0),
|
||||
new Vector4(0.000f, 0.433f, 0.567f, 0),
|
||||
new Vector4(0.000f, 0.475f, 0.525f, 0),
|
||||
new Vector4(0, 0, 0, 1));
|
||||
|
||||
private static readonly Matrix4x4 Achromatopsia = new(
|
||||
new Vector4(0.299f, 0.587f, 0.114f, 0),
|
||||
new Vector4(0.299f, 0.587f, 0.114f, 0),
|
||||
new Vector4(0.299f, 0.587f, 0.114f, 0),
|
||||
new Vector4(0, 0, 0, 1));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9fff57f1963a7e74e8000da706ddafce
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user