AI 游戏无障碍工程 2026:从替代输入到可朗读语义
游戏无障碍长期被当作美术与策划的合规成本,而不是一条工程管线。本文把 AI 游戏无障碍拆成三条可独立落地的通道:用视觉追踪替代物理操作,用视觉语言模型离线生成可时间轴对齐的音频描述轨,以及把 UI 改造成屏幕阅读器可消费的节点树;并给出对比度与色觉缺陷的实测算例、无障碍 CI 门禁设计、端侧延迟预算与十二周落地路径。
约 61 分钟阅读18,113 字3 次阅读博主

游戏无障碍长期被当作美术与策划的合规成本,而不是一条工程管线。本文把 AI 游戏无障碍拆成三条可独立落地的通道:用视觉追踪替代物理操作,用视觉语言模型离线生成可时间轴对齐的音频描述轨,以及把 UI 改造成屏幕阅读器可消费的节点树;并给出对比度与色觉缺陷的实测算例、无障碍 CI 门禁设计、端侧延迟预算与十二周落地路径。

游戏行业的无障碍长期被当成「美术改一改、策划加个选项」的合规成本,而不是一条工程管线。原因不难理解:传统的无障碍工作,绝大多数落在人工枚举上——人工给每一个 FMV 写一段音频描述,人工给每一个菜单写一份屏幕朗读稿,人工把每一个红绿对立的图标改成红绿加形状,人工把一百多个界面的文字色一个一个量对比度。这些活儿有一个共同特征:规模与你的内容规模成正比,而收益却由一个极小的少数人群承担。于是它永远排在版本末尾,永远在发售前两周才开始,永远被压缩成「最小可用集」。
规模数字本身就说明问题。世界卫生组织的残障事实清单写得很直接:全球约有 13 亿人、即 16% 的人口正经历显著残障(An estimated 1.3 billion people – or 16% of the global population – experience a significant disability today)。Game Accessibility Guidelines 的「为什么重要」页把游戏场景的数字补得更狠:15% 的人口有残障,在休闲玩家中这一比例升到 20%(PopCap 数据);14% 的成年人阅读年龄低于 11 岁(NCES / BIS 数据);8% 的男性有红绿色觉缺陷(AAO 数据)。再加上情境性障碍——在吵闹的房间里玩、在正午的阳光下用手机玩、手臂骨折的这两周——「典型玩家」这个概念本身就不成立。
值得注意的是,这些数据里绝大多数人并不注册任何残障。他们不需要任何专业辅助技术,他们只需要你的游戏在设计上不把某些人挡在门外。这决定了无障碍工程的一个反直觉属性:它不是一个「为特定人群做的特殊功能」,而是把设计空间里的长尾重新纳入。
那么 AI 在这里到底能做什么、不能做什么?本文的立场是:AI 不能替你决定无障碍的边界,但它能把三类原本纯人力的工作从「按内容规模线性增长」改造成「按模态固定」。这三条通道分别是:
这三件事在 AI 之前都不是不能做,而是成本结构不允许做。本文接下来逐条拆解它们的工程形态、给出实测数据与预算,并指出每条通道上真正会翻车的地方。
谈落地之前,先要有一份可执行的规范清单。业内有两条主线:微软的 Xbox Accessibility Guidelines(XAG) 与由多家工作室、专家和学者协作产出的 Game Accessibility Guidelines(GAG)。二者都值得读,但用法完全不同:XAG 是逐条可验收的判定项,GAG 是按「覆盖人群数 / 影响程度 / 实现成本」三维度分级的实践清单。
XAG 的编号条目覆盖了从文本显示到时间限制的完整面向。我抓取的这些页面全部可访问且结构一致:101 文本显示、102 对比度、103 视觉与音频提示的冗余通道、104 字幕与说明、105 音频可访问性、106 屏幕朗读、107 输入、108 难度选项、112 UI 导航、113 焦点处理、114 UI 上下文、116 时间限制、121 可访问功能文档。
几条原文里的硬判定值得原样记住,因为它们直接决定了「什么算合规」:
Worlds, Tab, 1 of 3, selected。GAG 把条目按实现成本分成 Basic / Intermediate / Advanced 三级,分级依据原文写得很清楚:覆盖人数(Reach)、带来的影响(Impact)、实现成本(Value) 三者的平衡;再按能力维度分成 Motor(运动)、Cognitive(认知)、Vision(视觉)、Hearing(听觉)、Speech(语言)、General(通用) 六类。
我对三个分级页面做了逐条计数(这是本文的实测值,不是引用):Basic 30 条、Intermediate 61 条、Advanced 26 条,合计 117 条。分维度看,Basic 是 Motor 7 / Cognitive 6 / Vision 7 / Hearing 4 / Speech 1 / General 5;Intermediate 是 Motor 10 / Cognitive 16 / Vision 12 / Hearing 11 / Speech 4 / General 8;Advanced 是 Motor 4 / Cognitive 6 / Vision 9 / Hearing 2 / Speech 2 / General 3。
这个分布本身就说明了落地顺序:Basic 的 30 条是几乎所有游戏都该做、但经常没做的,比如「允许重映射/重配置按键」「交互元素足够大且间距充足」「UI 里所有区域都能用与玩法相同的输入方式访问」「避免闪烁画面与重复图案」「提供交互式教程」「不要让任何关键信息只靠固定颜色传达」「音效与音乐对关键对象和事件必须可区分」「提供分别控制音效/语音/背景音乐的音量」。Intermediate 里则藏着对 AI 最有价值的一类条目:例如「语音识别基于音量阈值(如 50%)而非词语」「实时文字与语音互转」「提供声呐式音频地图」「字幕按目标年龄段的合适语速裁剪呈现」「设置可保存到不同档案」「试玩参与者中须包含每一类相关障碍的代表」。
GAG 的网站与商店页面不是游戏本体,但 XAG 121 明确要求遵守 WCAG 2 Level AA,所以两条阈值必须背下来。SC 1.4.3 对比度(最小值):文本与文字图像的对比度至少 4.5:1;大号文本至少 3:1;大号文本的判定是 18pt 常规或 14pt 粗体,原文给出换算 1pt = 1.333px,即 18pt ≈ 23.994px、14pt ≈ 18.662px。SC 1.4.11 非文本对比度:用户界面组件与理解内容所必需的图形对象,对相邻颜色的对比度至少 3:1。
4.5:1 是给网页文本的,游戏 HUD 的处境更复杂:HUD 元素常常是半透明的、需要叠在动态画面上、还可能被粒子特效冲刷。所以真正该做的不是「把文字改成白色」,而是把「对比度」从一个美术决策变成一个可回归的、可被自动检查的量。第六节会给出我实测的一组数字,以及为什么这个检查必须进 CI。
这条通道最有说服力的起点,是一个具体的人和一场事故。谷歌在 2023 年 5 月 10 日发布 Project Gameface(博客原文标题是 A new hands-free AI-powered gaming mouse),其灵感来自四肢瘫痪的直播玩家 Lance Carr:Carr 患肌营养不良,肌肉持续衰弱,他用头动鼠标控制光标打游戏;2021 年他家中起火,直播拍下了全过程,他所有游戏设备连同那只昂贵的头动鼠标一起烧毁。谷歌随后与 Carr 共同设计了这个项目。
原文描述的功能形态非常具体:用单个摄像头捕捉头部动作与面部表情,由机器学习模型识别后驱动光标;抬眉即可点击与拖拽,张嘴即可移动光标。为了适配不同用户,团队引入了 gesture size(手势幅度) 的概念——用户可以自行设定需要多大强度的动作才算一次鼠标操作。原文同时写明该项目是开源的、代码当时以预览形式提供,并强调「相对便宜、易于构建和维护」。
Gameface 的 README 直接给出了模型选型:MediaPipe Face Landmark Detection API,配套三份模型卡——MediaPipe BlazeFace(短距离)、MediaPipe FaceMesh V2、MediaPipe Blendshape V2。项目同时提供 Windows 与 Android 两个平台的实现。
把 MediaPipe 官方 Face Landmarker 指南拉下来看,输出能力比「识别脸」要丰富得多:任务输出三维人脸关键点、blendshape 分数(表示面部表情的系数)、以及用于变换的变换矩阵。模型包内部是三段式流水线——人脸检测模型输出少量关键点,face mesh 模型补全整张脸并输出 478 个三维人脸关键点的估计,blendshape 预测模型再基于 mesh 输出 52 个 blendshape 分数。人脸检测用的是 BlazeFace 短距离模型,官方描述为「轻量且准确、为移动端 GPU 推理优化」。可配置项包括 min_face_detection_confidence(默认 0.5)、min_tracking_confidence(默认 0.5)、output_face_blendshapes(默认 false)、output_facial_transformation_matrixes。
有两个细节对做游戏输入的人极其重要,官方原文都写了:其一,*Left 与 *Right 两组 blendshape 对应的是被摄者本人的左右,而非观看者的左右,传入水平镜像的图像会让这些输出反转——这意味着一旦你的游戏把画面镜像给玩家做「自拍式」交互,输入映射会整体反号;其二,running_mode 有 IMAGE / VIDEO / LIVE_STREAM 三种,LIVE_STREAM 模式下必须设置 resultListener 以异步接收结果——这直接决定了你的输入管线必须按异步回调设计,不能假设每帧同步返回。
手部通道同理。MediaPipe Hand Landmarker 任务的输出包括:左右手 handedness、图像坐标系下的手部关键点、世界坐标系下的手部关键点,配置项 num_hands 默认 1、min_hand_detection_confidence 默认 0.5。手部关键点天然适合做手势,而 52 个 blendshape 天然适合做面部表情——抬眉、皱眉、张嘴在工程上就是几个 blendshape 的阈值判断,而不是一次完整的分类推理。
从我实测到的仓库状态看,这条通道的工程问题被严重低估了。google/project-gameface 仓库的实时状态是:632 星、90 fork、Apache-2.0、主语言 Java、创建于 2023-05-05、最后推送 2024-08-30,而且——该仓库已被官方标记为 archived(归档),仍有 41 个 open issue。一个被官方归档的方案配上 41 个未关闭的问题,基本说明了问题在哪:
第一,输入模型的延迟与抖动直接决定可玩性,而不是「能不能识别出来」。 玩家能接受 30ms 的延迟吗?不能。但一个面部关键点模型从摄像头帧到输出 478 点与 52 个 blendshape,端到端跑在移动 GPU 上,帧率与延迟都不是拍脑袋能定的。这里没有公开的、经过第三方验证的延迟分布数据,本文也不编;能确定的是工程约束:必须把「输入延迟」当作与「渲染延迟」同等量级的预算来管理,并且必须有一条可关闭的降级链——丢失追踪(tracking lost)时不能让光标乱飞,而应该让光标停在原地并给出明确提示。
第二,误触发的代价远高于漏触发。 游戏输入里,一次误触发可能是「误开地图」「误放技能」「误提交表单」。所以置信度阈值、gesture size 这类用户可调参数不是可选项而是必需品,Gameface 原文把 gesture size 写成核心特性,正是这个道理。
第三,也是最容易被忽略的一条:隐私边界必须写进产品设计,而不只是合规文档。 Gameface 的 README 里有一句写得很克制但很硬的划界:
This project is not intended for human life-critical decisions. Predicted face landmarks do not provide facial recognition or identification and do not store any unique face representation.
翻译成工程语言就是:输出的是关键点与表情系数,不是身份;不存储任何唯一的人脸表征。这三条约束(不做生命攸关决策、不提供人脸识别与身份判定、不存储唯一人脸表征)应当被逐字写进你的设计文档和隐私政策,因为一旦你在关键点之上再叠一层「记住这张脸」的逻辑,这条通道的性质就变了。
这条通道不是孤立的。XAG 107 要求 UI 支持数字与模拟两种导航方式,并要求所有键位与鼠标点击都可重映射;XAG 106 要求可交互元素提供文本替代。
两者的工程交集在于:当输入是「视觉追踪」而不是「物理设备」时,重映射与替代文本这两件事必须一起做。因为你的输入源数量变多了(物理手柄 + 键盘 + 视觉追踪),如果没有统一的「动作」抽象层,视觉通道会变成一个写死在代码里的特例。反过来,如果抽象得当,视觉通道只是又一个「动作产生器」。
一个务实的落地顺序建议是:先做菜单与设置界面的替代输入,再做玩法内的连续输入。理由是菜单是离散的、可确认的、允许出错的;玩法内的连续输入是实时的、不可撤销的。前者能让绝大多数残障玩家完成「开设置 → 调对比度 → 调音量分轨 → 开屏幕朗读 → 关震动」这条最关键的路径,而这条路径的价值远高于在动作游戏里用脸转视角。
XAG 111(音频描述)的目标是:FMV 或其他游戏内脚本化过场事件,必须提供一条额外的音轨,描述画面中发生的全部关键视觉信息与上下文,让看不见画面的玩家获得与明眼玩家等价的信息获取路径。
判定写得很清楚,但真正的成本在原文的「实施指南」里被摊开了:
把这段翻译成工程语言:XAG 111 要求的是一条与主音轨并行、按时间轴对齐、长度受间隙约束的描述轨。人工写这条轨的成本极高——每一条描述都要配一句文案、配一次配音、做一次本地化、做一次时间轴对齐。而且每新增一个 FMV 就要重做一遍。这就是 AI 在这条通道上的位置。
理论上可以,但我要先把能做与不能做分清楚。
能做的:视觉语言模型对单帧或短镜头生成描述性文字。开源侧已经可用:例如 MiniCPM-V 系列(Apache-2.0,GitHub 实测 26989 星,最后推送 2026-10-28),其 README 记载 MiniCPM-V 4.7 的 35B-A3B 是首个能在端侧流畅运行的开源 MoE 模型,在 iPhone 17 Pro Max 上以 3GB 内存达到 13+ tokens/s 的解码速度,并配套 DSpark 推测解码;MiniCPM-V 4.6 版本在 README 中被描述为 9B 参数、视觉理解能力超过 GPT-4o 与 Gemini 2.0 Pro,8 个 OpenCompass 基准平均 77.6;同仓库在 2026-10-28 还开源了 MiniCPM-OCR 套件。QwenLM/Qwen3-VL(Apache-2.0,实测 20131 星)同样是可选底座。
不能做的,也是必须拦住的:
这个架构里 AI 的位置是内容生产流水线的一环,而不是运行时组件——这与「AI 在游戏运行时做什么」的直觉相反,但恰恰是把成本结构从「按内容规模线性增长」改成「按模态固定」的地方。
XAG 103 的判定(关键视觉内容必须至少有一种其他感官表达)在过去几乎只能靠人工设计音效。现在有了另一条路:用视觉模型在运行时检测「关键视觉事件」,再触发对应的音频/触觉反馈。
但这条路的边界同样清晰:它只能覆盖「有明确视觉特征的事件」。被击中的方向、敌人位置、拾取提示——这些有视觉特征,模型可判。而「剧情张力」「这是Boss战」——这些没有可判的视觉特征,模型给了也是噪音。所以工程上正确的做法是:用模型处理可枚举的视觉事件,用人工设计处理不可枚举的叙事语义。
《刺客信条:英灵殿》的「奥丁之视」是这条思路的人工版本典范:可收集物同时有发光金色圆环(视觉)与空间音频方位提示(听觉)。模型版本要做的是把这件事从「设计师手工挑了 20 个可收集物」扩展到「设计师定义 20 类可检测特征,模型负责在运行时判定」。
需要明确划一条线:屏幕朗读与 AI 语音输入是两条不同的通道。XAG 106 关注的是「UI 文本如何被读出来」,这是输出方向;语音输入关注的是「玩家如何用声音指挥游戏」,那是输入方向。二者共享的是 ASR/TTS 底座,但工程约束完全不同(输出方向要求不漏、不错、有语义结构;输入方向要求不误触发、低延迟)。
本文聚焦输出方向与视觉替代输入,语音输入作为独立主题不在此展开——但如果你已经做过端侧 ASR,那么字幕轨是同一套采集链路的第二个消费者,这是成本最划算的一处复用。
先说结论:屏幕朗读支持本身几乎不需要 AI,它是纯粹的 API 工作。但它是前两条通道的放大器——没有语义层,前面两条通道产出的东西(描述文本、焦点顺序、状态变化)就无处安放。
这也是最容易做错的一层。多数团队的做法是「给每个按钮挂一段 tooltip」,结果屏幕阅读器读出来是「按钮、按钮、按钮、确定、取消、返回」——语义缺失不是文案问题,是数据模型问题。
Unity 6(6000.0)的 UnityEngine.Accessibility 模块提供了一组相当完整的 API,四个类值得记住:
AccessibilityNode 是屏幕阅读器可读、可聚焦、可执行动作的 UI 元素实例。它的属性构成了一个节点该有的全部语义:label(节点被聚焦时首先被读出的字符串)、role、state、value、hint、id、isActive、isFocused、allowsDirectInteraction、parent、children。有两个细节是设计级的:frame 是节点在屏幕坐标系中的矩形,文档明确建议设置 frameGetter 委托而不是直接赋值,因为「若节点的 frame 可能随时间变化,应当设置此委托以便必要时重新计算」;hint 的官方示例用途是「执行某动作后的结果」。事件侧有 focusChanged、selected、incremented、decremented、dismissed 五个。
AccessibilityHierarchy 是屏幕阅读器用来朗读与导航 UI 的层级数据模型。文档把用法讲得非常明确:必须通过 AssistiveSupport.activeHierarchy 把某个层级设为 active,屏幕阅读器才能工作;如果活动层级被修改,必须通过 AssistiveSupport.NotificationDispatcher.SendLayoutChanged 或 SendScreenChanged 通知屏幕阅读器(前者用于仅布局级变化,后者用于更明显的整屏变化);对层级的修改由 AddNode、Clear、InsertNode、MoveNode、RemoveNode 以及节点 frame 值的修改构成。
AccessibilitySettings 提供当前平台的无障碍设置,文档写明目前支持的平台是 RuntimePlatform.Android 与 RuntimePlatform.IPhonePlayer,并暴露三个静态属性:fontScale(系统设置里的字号缩放)、isBoldTextEnabled、isClosedCaptioningEnabled。对应三个主线程事件:fontScaleChanged、boldTextStatusChanged、closedCaptioningStatusChanged。
AssistiveSupport 是辅助技术支持的入口点,同样标注支持 Android 与 iPhonePlayer,Android 侧要求至少 API level 26。它提供 activeHierarchy、isScreenReaderEnabled、notificationDispatcher 三个静态属性,以及 nodeFocusChanged 与 screenReaderStatusChanged 两个主线程事件。
把这四个类串起来,Unity 侧的正确接法是:UI 层维护一棵自己的节点树 → 变更时增量同步进 AccessibilityHierarchy → 层级变化后调用 NotificationDispatcher 通知 → 同时监听 AssistiveSupport.screenReaderStatusChanged,在系统朗读器开关变化时重建/销毁活动层级。最后一步经常被漏掉,而它恰恰是「玩家在设置菜单里打开屏幕阅读器」这个真实路径的关键。
Godot 侧的对应物是 AccessibilityServer,官方类文档的描述只有一句:A server interface for screen reader support,并注明已在 Android、Linux、macOS 与 Windows 上实现。它的方法列表把 API 形态说得非常清楚:create_element(window_id, role) 创建元素、create_sub_element(parent_rid, role, insert_pos) 创建子元素、create_sub_text_edit_elements(parent_rid, shaped_text, min_height, insert_pos, is_last_line) 为文本编辑控件批量创建元素、element_get_meta / element_set_meta 读写元素自定义元数据、free_element、get_window_root、has_element、is_supported、set_window_focused、set_window_rect、update_add_action(id, action, callable) 绑定动作回调、update_add_child、update_add_custom_action。
它还提供了一整套角色枚举(ROLE_AUDIO、ROLE_BUTTON、ROLE_CELL、ROLE_CHECK_BOX、ROLE_CHECK_BUTTON、ROLE_COLOR_PICKER、ROLE_COLUMN_HEADER、ROLE_CONTAINER、ROLE_DEFAULT_BUTTON、ROLE_DIALOG、ROLE_IMAGE、ROLE_LINK、ROLE_LIST、ROLE_LIST_BOX、ROLE_LIST_BOX_OPTION、ROLE_LIST_ITEM、ROLE_MENU、ROLE_MENU_BAR、ROLE_MENU_ITEM、ROLE_MENU_ITEM_CHECK_BOX、ROLE_MENU_ITEM_RADIO、ROLE_MULTILINE_TEXT_FIELD、ROLE_PANEL、ROLE_PROGRESS_INDICATOR、ROLE_RADIO_BUTTON、ROLE_REGION、ROLE_ROW、ROLE_ROW_GROUP、ROLE_ROW_HEADER、ROLE_SCROLL_BAR、ROLE_SCROLL_VIEW、ROLE_SLIDER、ROLE_SPIN_BUTTON、ROLE_SPLITTER、ROLE_STATIC_TEXT、ROLE_TAB、ROLE_TABLE、ROLE_TAB_BAR,外加 POPUP_DIALOG/POPUP_LIST/POPUP_MENU/POPUP_TREE 等弹出类型),一整套动作枚举(ACTION_BLUR、ACTION_CLICK、ACTION_COLLAPSE、ACTION_CUSTOM、ACTION_DECREMENT、ACTION_EXPAND、ACTION_FOCUS、ACTION_HIDE_TOOLTIP、ACTION_INCREMENT、ACTION_REPLACE_SELECTED_TEXT、ACTION_SCROLL_* 系列、ACTION_SET_SCROLL_OFFSET、ACTION_SET_TEXT_SELECTION、ACTION_SET_VALUE、ACTION_SHOW_CONTEXT_MENU、ACTION_SHOW_TOOLTIP),实时性枚举(LIVE_ASSERTIVE、LIVE_OFF、LIVE_POLITE)与状态标记(FLAG_BUSY、FLAG_CLIPS_CHILDREN、FLAG_DISABLED、FLAG_HIDDEN、FLAG_MODAL、FLAG_MULTISELECTABLE、FLAG_READONLY、FLAG_REQUIRED、FLAG_TOUCH_PASSTHROUGH、FLAG_VISITED)。
这里有一个值得写进架构文档的跨引擎不对称:Unity 明确列出 iPhonePlayer 支持,而 Godot 的 AccessibilityServer 文档列出的是 Android、Linux、macOS、Windows,没有 iOS。同时我实测到 class_accessibilityserver.rst 在 godot-docs 的 master 分支存在,而在 4.3 与 4.2 分支上同一路径返回 404——也就是说 Godot 的这一整套屏幕阅读器接口是 4.4 之后才进入主线的。
结论很实际:如果你要做跨引擎的跨平台无障碍,语义层不能假设「引擎已经抽象好了」。你的 UI 层应该维护引擎无关的自己的语义树,再分别适配 Unity 的 AccessibilityHierarchy 与 Godot 的 AccessibilityServer;这层适配代码不多,但它是你能同时支持 iOS 与 PC 的唯一稳妥姿势。
语义层不需要大模型来「朗读」,但 AI 在两个地方非常有用:
第一,从 UI 资源反推语义树。 你的 UI 可能是美术在编辑器里拼的,控件类型与可读文本常常不对应屏幕阅读器的期望角色。用视觉/结构识别模型辅助标注,再由人工审核入库,可以把「补齐语义」从一次性大工程变成持续的小工程。
第二,把动态状态变化转成朗读事件。 XAG 114 要求上下文变化必须被播报(《Minecraft》的「joining realm」/「complete」),XAG 113 要求焦点指示器必须可见且不得移到屏幕外——后者意味着「焦点移出视口」是一个需要被检测并修正的事件。这些事件源分散在游戏逻辑各处,用 AI 辅助归并成统一的朗读事件队列,比人工在每个分支埋点可靠得多。
第六节开头要回到第二节末尾那个问题:HUD 的对比度不是美术决策,是一个可以被自动检查的数值。我用 WCAG 的相对亮度公式实算了一组典型游戏配色(前景 RGB 取自常见 HUD 色板,背景取深灰紫 #121218),结果如下:
| 前景色 | 对比度 | 判定(AA 4.5:1) |
|---|---|---|
纯白 #FFFFFF | 18.660 | 通过 |
游戏黄 #FFD700 | 13.303 | 通过 |
治疗绿 #30D158 | 9.229 | 通过 |
橙 #FF9F0A | 9.077 | 通过 |
浅灰 #B0B0B8 | 8.662 | 通过 |
警示红 #FF3B30 | 5.260 | 通过 |
蓝 #0A84FF | 5.116 | 通过 |
中灰 #808088 | 4.764 | 通过 |
暗灰 #55555C | 2.524 | 不通过 |
深灰 #3A3A40 | 1.652 | 不通过 |
十个常见 HUD 色里 8 个通过、2 个不通过。这个比例看着还行,但它掩盖了一个更麻烦的事实:对比度的真正变量不是前景色,而是背景。同一组前景色换背景,结论会翻转。我实算的对照:白字在深蓝 #1E3A5F 上是 11.503,在森林绿 #2D5016 上是 9.249,在深紫 #2A1B3D 上是 15.878,但在明亮天空 #87CEEB 上只有 1.742——完全不达标;红字在森林绿上只有 2.607,在深紫上是 4.476(仅够大号文本),在明亮天空上 2.037。
这组数字给出的工程结论非常直接:「文字色」不能是一个设计常量,它必须是一个随背景计算的运行时量。在明亮的场景里,前景色要重新解算;或者更实际的做法是——给关键信息加底板/描边/阴影,而不是靠调文字色。这正是 XAG 102 提到的《Fenyx Immortals Rising》用「白色箭头 + 黑色描边」来提示受击方向的做法:描边把对比度问题从「配色问题」变成了「几何问题」,后者在任何背景下都成立。
对 CI 的含义是:检查必须按场景采样,而不是按设计稿检查。在若干个代表性亮度/色调的场景截图上批量检测 UI 元素的实际对比度,而不是在设计软件里看色板。
上一节的数字全部是正常视觉下的。对约 8% 的男性(AAO 数据)而言,问题完全不同。我用 Machado 等人 2009 年的色觉缺陷模拟矩阵(severity 1.0)在线性 RGB 空间(这一点很关键:在 sRGB 空间直接套矩阵会得到完全错误的结果)实算了同样的几组配色:
| 配对 | 正常视觉 | 红色盲 protanopia | 绿色盲 deuteranopia | 蓝色盲 tritanopia |
|---|---|---|---|---|
友军绿 #30D158 vs 敌军红 #FF3B30 | 1.754 | 2.795 | 1.364 | 1.946 |
治疗绿 #30D158 vs 暗底 #121218 | 9.229 | 9.979 | 8.697 | 9.180 |
游戏黄 #FFD700 vs 暗底 #121218 | 13.303 | 12.391 | 13.889 | 12.307 |
蓝 #0A84FF vs 紫 #2A1B3D | 4.353 | 5.017 | 4.013 | 5.132 |
红 #FF3B30 vs 森林绿 #2D5016 | 2.607 | 1.726 | 3.222 | 2.365 |
三张表读下来,结论层次分明:
其一,靠亮度差建立的层级是稳的。 治疗绿对暗底在三种色觉下都保持 9.0 以上的对比度,游戏黄对暗底始终在 12 以上。凡是用「更亮/更暗」承载的信息,天然对色觉缺陷免疫。这是设计系统层面最值得利用的规律——把「谁更危险」这类关键语义锚定在亮度上,而不是锚定在色相上。
其二,靠色相对立的信息会塌陷,而且塌陷程度远超直觉。 友军绿对敌军红的正常对比度是 1.754,在绿色盲下降到 1.364——两者在模拟后都变成了近乎相同的黄褐色(#C2B162 与 #A99725),通道差值仅 112;而在红色盲下这两个色反而在亮度上分开了(对比度反而升到 2.795)。这解释了为什么「红绿不分的玩家其实还能玩」是个错觉:问题不是他们完全看不见,而是他们看到的东西在不同场景下不一致——正常视觉下靠色相区分的两类目标,在红色盲玩家眼中可能变成一大一小两个黄褐色块,反之亦然。
其三,最低可读性出现在非典型配色上。 蓝 #0A84FF 对紫 #2A1B3D 在正常视觉下是 4.353(已经低于 AA 的 4.5),在绿色盲下降到 4.013——从「基本可用」掉到「仅够大号文本」。
综合起来,GAG Basic 里的「不要让任何关键信息只靠固定颜色传达」在工程上要翻译成三件具体的事:加形状或图标冗余、加描边或底板、把亮度差拉进可测量的安全区。XAG 102 提到的「红绿用于瞄准图标或其他重要元素会让部分色觉障碍玩家难以辨认」正是这条的规范表述。
把上面两类检查加上语义层的静态检查,一个可落地的门禁长这样:
视觉类(截图 + 像素分析):在若干代表性场景截图上批量提取 UI 元素的实际渲染颜色,计算与相邻背景的对比度,低于 4.5:1 报警(低于 3:1 硬失败);对色觉缺陷矩阵做三通道重渲染,检出「仅靠色相区分」的成对元素。
语义类(UI 资源 + 运行时):每个可交互控件必须声明 role 与 label,缺失即失败;焦点顺序必须可静态遍历(用 XAG 113 的规则:焦点指示器不得移动到不可见或屏幕外元素);动态焦点变化必须触发 focusChanged 通知(对应 Unity 的 AccessibilityHierarchy 变更必须调 SendLayoutChanged / SendScreenChanged)。
内容类(资源与音轨):所有 FMV 必须有描述轨(或显式标记为「无描述」并说明原因,对应 XAG 111 的例外条款);所有语音必须可字幕化;字幕需满足 XAG 116 的时间限制与提前量规则;所有 UI 文本在 XAG 101 的八个维度上都有可调项。
能力类(能力探测):运行时探测屏幕阅读器是否开启(Unity 的 AssistiveSupport.isScreenReaderEnabled、Godot 的 AccessibilityServer.is_supported),据此决定是否构建并激活语义层;并把「屏幕阅读器开关变化」当作一个事件去响应(Unity 的 screenReaderStatusChanged)。
这四类里,前三类可以在 CI 里跑,第四类必须进运行时。把它们混在一起谈是很多团队失败的原因——CI 里能做的是「你声明了没有」,运行时能做的是「对方能不能用」,两者的失败模式与责任人完全不同。
值得强调的架构事实:这三条通道的运行期算力需求是完全不同量级的,把它们混在一个「AI 运行时」里设计是常见错误。
resultListener 接收结果。这条通道是唯一真正吃实时预算的。既然只有视觉替代输入是实时敏感的,那就给它一个明确的预算表。常见的做法是把「从摄像头曝光到游戏收到一次动作」的延迟拆成四段并各自设上限:采集(曝光到帧可用)、推理(模型前向)、滤波(关键点去抖与平滑)、以及游戏内响应(动作被消费到画面反馈)。
其中滤波段往往是最被忽略、也最容易优化的:人脸关键点序列天然带抖动,直接驱动光标会「飘」。但过度滤波又会引入延迟——这两者是对冲关系,不能同时优化。工程上的解法是分开处理不同信号:位置类信号(光标位置)重滤波以稳为先,可以接受几十毫秒的滞后;离散事件类信号(抬眉点击)轻滤波甚至不滤波,以保证响应感——因为点击是低频事件,偶尔误触的代价可以通过确认机制吸收,而光标飘移是持续的不适感。
这也解释了 GAG Advanced 里为什么会有「在两次输入之间提供 0.5 秒的冷却期(post acceptance delay)」这样的条目——对于使用开关设备(switch)或眼动追踪的玩家,0.5 秒的延迟不是缺陷而是必要的稳定性代价。同一条需求,对不同通道的最优值完全不同。
如果要自己训模型而不是直接用 MediaPipe,开源栈是成熟的:ONNX Runtime(MIT,实测 22182 星,最后推送 2026-10-28)提供跨平台高性能推理与训练加速;OpenCV(Apache-2.0,实测 91353 星)负责图像管线;MediaPipe 本身(Apache-2.0,实测 37433 星,最后推送 2026-10-27)提供了 Tasks 与 Framework 两层,Framework 层可在 C++、Android、iOS 上构建低层的端侧 ML 流水线;示例仓库 mediapipe-samples(Apache-2.0,实测 2875 星)给出各平台的上手代码。
建议的默认选择是:能用现成 Tasks 就别自己训。自己训的收益只在你有非常特殊的交互设计时才成立,而代价是模型生命周期、跨设备一致性、许可证与可复现性一整套负担。
一个按依赖顺序排的、我在多次类似工程里验证过可行的顺序:
第 1-2 周:建立基线与责任边界。 用 GAG 的 Basic 30 条做一次全量自查,产出一张「已满足/部分满足/不满足」的表;每一行都要有明确的负责人。这一步的唯一目的是把无障碍从「有人在做」变成「有一张可核对的表」。
第 2-4 周:做零成本高收益项。 全部来自 Basic:重映射、交互元素尺寸与间距、音量分轨、避免闪烁、默认字号、字幕默认开启、不可只靠颜色传达关键信息。XAG 112 的策略在这里就能用上——默认满足尽可能多的 XAG,让玩家不必先去找设置。
第 4-6 周:铺语义层。 定义引擎无关的语义树数据结构,接 Unity 的 AccessibilityHierarchy 与 Godot 的 AccessibilityServer,实现节点角色/状态/动作映射,完成 NotificationDispatcher 的通知路径,并处理系统朗读器开关变化。这一步做完,UI 层的基础无障碍就成立了。
第 6-9 周:上 CI 门禁。 先做对比度与色觉两项(第二节的实算脚本可以直接改造成检查器),再做语义缺失检查与内容覆盖检查。每一条门禁都必须能给出具体的失败位置与建议值,否则会被团队集体绕过。
第 9-11 周:做需要模型的两条通道。 视觉替代输入从「菜单与设置界面」开始做,验证延迟预算与误触发率,再评估是否进入玩法内连续输入。音频描述轨先在少量 FMV 上做离线生成管线,验证「描述不剧透、不超时、时轴对齐」三件事。
第 11-12 周:做外部验证。 按 GAG Advanced 的条目,邀请包含每一类相关障碍代表的试玩参与者。XAG 121 要求的购买前文档与独立无障碍页面同步上线。
失败模式一:把无障碍做成一个「模式开关」。 最常见的错误是把所有无障碍能力塞进一个「无障碍模式」开关,默认关闭。后果是:它不在默认体验里,永远被优先级砍掉,而且把「哪些设置与平台系统设置联动」这件事做成了 if-else 泥潭。XAG 112 的策略是更稳的形态——能力常驻、默认值合规、与平台设置联动。
失败模式二:语义层与视觉层脱节。 团队给 UI 做了漂亮的描边与高对比配色,但没做语义树;或者做了语义树,但节点的 frame 不随动画更新(AccessibilityNode.frameGetter 文档里特意强调了这一点)。结果屏幕阅读器能读出元素,却指不出位置——对盲玩家而言「读出来但定位不到」几乎等于「读不出来」。
失败模式三:把 AI 生成当成内容生产完成的标志。 音频描述由 VLM 生成、字幕由 ASR 生成、语义标注由模型推荐——这些都成立,但生成之后的验收是人工的、不可省的。描述是否剧透、字幕时轴是否对齐、标签是否正确,模型不知道,你才知道。AI 把「产出候选」的成本降到了接近零,同时把「验收」的重要性推到了最高——因为候选太多,人工验收就成了瓶颈。这是这套方法论里最反直觉、也最重要的一点。
边界一:AI 不能替代设计判断。 哪些信息是「关键」的、哪些描述会剧透、哪些视觉事件值得被听见过——这些是设计决策,不是分类问题。模型可以把候选铺满,但选择权必须在人手里。Gameface README 里那句「not intended for human life-critical decisions」虽然说的是无障碍场景,但它给出的原则是通用的:不要让模型独立决定对用户有重大影响的事。
边界二:合规不等于可用。 欧盟《无障碍法案》(European Accessibility Act)的官方页面把适用产品与服务列为:ATM、取票与登机机、智能手机、电视设备、数字电视服务、电话服务、电子图书、电子商务、银行服务,以及视听媒体服务。它带来的现实后果之一很直接:游戏内商店页、商品描述、退款流程这些「非玩法内容」也在合规范围内——很多团队只关注游戏本体,忽略了商店与运营界面。而合规通过只说明达到了标准线,不说明实际好用。
边界三:不可验证的数字不要写进结论。 本文给出的所有数值分两类:一类来自官方文档原文(XAG 与 WCAG 的阈值、WHO 与 GAG 引用的统计、API 的平台支持矩阵),一类是我自己用脚本实算的(对比度表、色觉模拟表、GAG 条目计数、GitHub 仓库实时状态)。本文没有给出视觉替代输入的端到端延迟分布——因为我找不到可公开验证的数据,任何数字都会是编的。这一条我特意留白,因为它恰恰是最该被实测的指标。
争议点:默认开启还是默认关闭? XAG 112 明确建议默认满足 XAG、以及在读不到平台设置时默认开启朗读。但实践中有反对意见:对听障之外的玩家,朗读声会造成干扰;对某些玩家,默认开启的字幕会遮挡关键信息。这个争议没有普适答案,工程上的处理是遵循平台层设置并提供显式的首次配置引导,而不是在游戏里替玩家决定。
未解问题一:跨引擎跨平台的无障碍抽象层缺位。 前面已经指出 Unity 与 Godot 在 iOS 支持上的不对称。这意味着一套面向多引擎多平台的团队,必须自己维护语义层适配,并接受它会长期滞后于引擎特性。
未解问题二:AI 生成内容的可回归性。 描述轨可以离线生成并存库,这解决了稳定性;但**「如何证明这一版描述优于上一版」仍没有好答案**。GAG 建议的「试玩参与者覆盖每一类障碍」是一种人工方案,成本高、周期长。这大概是整个方向上最欠缺工具的一环。
未解问题三:情境性障碍被系统性低估。 GAG 的统计里有「在嘈杂房间」「在强光下」「手臂骨折的两周」这类临时与情境障碍。它们不涉及任何辅助技术,只需要游戏本身不极端——但恰恰因为「不涉及残障」,它们几乎从不出现在任何合规清单里,却可能是比永久性残障更普遍的实际障碍。
游戏无障碍的三个真实瓶颈——替代输入的延迟、音频描述的内容成本、UI 语义层的缺失——分别对应三条可以用 AI 与现代引擎 API 拆解的通道:视觉追踪替代物理操作、离线生成并时间轴对齐的音频描述轨、以及把 UI 改造成可被屏幕朗读器消费的节点树;而把对比度与色觉缺陷做进 CI,是让这一切不再退化的唯一办法。
Conversation
0 条