从挥手到转头:【游戏大师】用 AIUI 绝对方向传感器在 Rokid Glasses 上做头部准星切水果
一、项目简介:解决什么问题,目标用户是谁
游戏大师是一个跑在 Rokid Glasses 一代(带显示)上的全屏体感切水果游戏。最开始的想法很朴素:没有配套戒指,也不想让玩家去装额外的视觉插件,能不能直接靠眼镜本身的能力,让"想玩一会儿"变成一次真的能玩的交互?
目标用户就是眼镜的佩戴者——通勤、休息或在家时想随手玩两把的人。他们不想掏手机、不想戴戒指、不想配置摄像头权限,只希望说一句"打开游戏大师",然后直接玩。
这个项目最有意思的转折发生在控制方式上。v0.5.x 时期,游戏同时支持"双手挥动"和"头部准星"两种控制;但 v0.6.0(2026-11-06)起,我们删掉了整套相机与掌检测链路,只保留头部准星控制。原因很现实:让用户为了用手势识别而去安装本机视觉组件 APK、处理相机授权,太麻烦。头控只用 AIUI 自带的绝对方向传感器,不打开摄像头、不装插件、不申请相机权限,单独导入 AIX 就能完整游玩。所以本文的主线是头部控制,手势识别作为诚实的技术沿革在后面单独说明。
当前版本(v0.6.0)共六种玩法:自由练习、经典挑战、趣味挑战、9 关闯关、每日挑战、放松模式。练习与放松不计分;经典保留 60 秒每果 10 分;趣味加入金果、冰冻、炸弹与连击;闯关有九关和星级;每日挑战使用同一天的统一种子。眼镜界面还能直接看云榜、改昵称、查看本机成长与称号。
这里没有为了"AI"额外调用大语言模型。AIUI 负责眼镜上的界面、状态机和平台交互,官方的绝对方向传感器负责姿态输入,业务规则负责把它们组合成可操作的游戏。
二、场景与用户意图:什么时候唤醒,智能体如何承接
用户围绕的是几种具体意图:开始玩、换一种玩法、停下来、看看自己排第几、改成自己的名字。智能体以状态响应意图,而不是一次性弹出一个大页面。状态包括:等待输入、准备倒计时、游玩、暂停、结果、榜单、语音候选确认。
头部控制接入了"开始玩"这条意图:当用户选择一种玩法并首次开始时,如果还没有做过方向校准,界面会引导完成三步校准;校准完成后,轻轻转头就能让准星划过水果。相机或姿态数据尚未就绪时,不先把倒计时跑掉;识别出的昵称也不直接保存,而是让人确认。
意图的承接要诚实:静止、过期读数或中断都不生成刀轨;暂停、退出和后台切换都会停止姿态订阅。系统不会假装"已经识别到了头部动作"。
三、方案设计:AIUI 智能体定义、页面/卡片、状态流转与能力调用
当前(v0.6.0)的眼镜程序只有 AIUI 的 AIX 一个部分,核心能力调用关系是:
头部输入路径:
AIUI AbsoluteOrientationSensor → 四元数([x,y,z,w])→ 三步校准得到的本机方向剖面(rightAxis/upAxis)→ 相对旋转投影到水平/垂直分量 → 单准星(0.5±范围)→ 与历史手势完全相同的移动线段碰撞规则。
联网路径:
AIUI → 同源排行榜 API → Worker 校验事件 → Cloudflare D1 保存成绩;网页由 Netlify 提供静态资源和精确 API 转发。
头控不采集摄像头图像,也不用本机视觉组件;应用只声明 RECORD_AUDIO(用于语音改名)。历史视觉服务(v0.5.x 及更早)只监听眼镜本机 127.0.0.1,图像仅在眼镜内存中处理,不保存、不上传。语音识别使用 Rokid 平台能力,可能依赖联网服务,不能把"视觉离线"扩大成"全部音频也离线"。
页面是一个全屏的单色 HUD(480×352)。六种玩法、九关和云榜不能把所有信息硬塞一屏:玩法与关卡分页,榜单每屏四名,个人最好另列;金果、冰冻、炸弹、禁果使用"金/冰/炸/禁"文字或形状标记,不依赖颜色。
四、AIUI 实现:项目结构、Ink 页面与关键 API
项目结构(仓库根):
- pages/index/index.ink:眼镜全屏游戏页面(状态机 + HUD 渲染)
- utils/head-input.js:头部姿态读取、相对旋转、三步校准、看门狗
- utils/head-blade.js:把头部采样点变成单准星与移动线段刀轨
- utils/(游戏逻辑、计分、成长、本机榜等)
- cloud/:Worker + D1 排行榜后端
- deploy/:Netlify 静态站与 Cloudflare 后端发布
4.1 读取真实头部姿态
AIUI 提供 AbsoluteOrientationSensor,reading 事件给出设备朝向的四元数 [x,y,z,w] 和 timestamp;首次读数为 null,需要检查 hasReading;stop 之后保留末值,应靠 activated 判断活动状态。Android 后端基于 TYPE_ROTATION_VECTOR / TYPE_GAME_ROTATION_VECTOR,但宿主 InkView 必须注册能力;不保证精确频率。
代码里这样启动(节选自 utils/head-input.js):
if (typeof env.AbsoluteOrientationSensor !== ‘function’)
throw error(‘unsupported’,‘当前眼镜未提供头部姿态接口,请更新系统后重试’);
s.sensor = new env.AbsoluteOrientationSensor({ frequency: 30 });
s.sensor.addEventListener(‘reading’, s.read);
s.sensor.addEventListener(‘error’, s.error);
s.sensor.start();
四元数先做归一化与范数校验(范数在 0.5–2 之间才接受),丢弃重复或倒退的时间戳,保证每一帧都是"当前"的姿态,而不是积压的旧值。
4.2 从四元数到准星:相对旋转与方向剖面
直接拿绝对四元数当坐标没有意义——不同人、不同佩戴角度下"平视"对应的四元数都不一样。项目用"参考姿态 + 相对旋转"的方式:以开局第一帧作为参考,把后续姿态相对参考做旋转,再投影到本机校准出来的两个轴(rightAxis 水平、upAxis 垂直)上。
// 返回相对参考姿态的三轴旋转角度(度)
function relativeHeadRotation(reference, current) {
const a = quaternion(reference), b = quaternion(current);
let q = multiply([-a[0],-a[1],-a[2],a[3]], b); // 参考的逆 × 当前
if (q[3] < 0) q = q.map(v => -v); // q 与 -q 等价,统一到正 w
const n = Math.hypot(q[0],q[1],q[2]);
const angle = 2 * Math.atan2(n, clamp(q[3],-1,1)) * 180 / Math.PI;
return q.slice(0,3).map(v => v * angle / n);
}
投影时只取水平、垂直两个分量,再做带连续性的平滑与死区:
const horizontal = dot(rotation, s.profile.rightAxis);
const vertical = dot(rotation, s.profile.upAxis);
// 连续帧指数平滑(时间常数约 35ms),小角度死区抑制静止抖动
const point = { id:‘head’, x: clamp(.5 + h/32, 0, 1), y: clamp(.5 - v/24, 0, 1), … };
系数 32 与 24 意味着:水平约 ±16 度、垂直约 ±12 度即可覆盖整个游戏画面。也就是说,玩家不需要大幅度甩头,小幅度的转头就能把准星送到 HUD 边缘。
4.3 三步校准:把"平视"定义成本机坐标轴
不同设备的传感器安装、不同人的佩戴角度都不一样,不能假设手机那套轴向。首次使用(或佩戴位置改变后)走三步校准,得到一份本机方向剖面:
- 平视确认:记录参考四元数;
- 轻向右转确认:相对参考旋转角须在 5°–35° 之间(太小或太大都拒绝),存为 rightAxis(单位向量);
- 回正后轻抬头确认:取相对旋转在"垂直于 rightAxis"方向的分量,要求正交比例 ≥ 0.65,存为 upAxis。
剖面校验要求 rightAxis、upAxis 都是单位向量且互相正交(点积 < 0.05),版本标记为 1。校准完成前,开始游戏会直接报错"请先完成头部方向校准",不会用错误的轴向硬算坐标。
4.4 平滑、死区与看门狗
为避免 IMU 抖动把准星抖飞,代码做了两层处理:连续帧(间隔 ≤ 250ms 且位移 ≤ 8+dt·0.5 度)用指数平滑(时间常数约 35ms);同时加了一个小角度死区(< 0.22 度不更新),抑制静止时的抖动,且不会在游戏中偷偷平移参考点。静止不制造切割——这一点和手势的"准星停在水果上不一直切"一致。
健壮性靠看门狗:启动后 5 秒没收到任何姿态读数报"没有收到头部姿态";运行中超过 1.5 秒没新读数进入 searching、超过 250ms 视为过期并清空准星。取消头控后停止订阅传感器,不再占用资源。
4.5 单准星刀轨:移动线段,不 extrapolate
utils/head-blade.js 把头部采样点(已映射到 480×352)转成"点 + 移动线段"。只有当相邻两帧距离 ≥ 3 像素且时间间隔 ≤ 250ms 时才生成线段;停顿或重新居中会让锚点重置。它明确不外延(never extrapolates):慢速移动时,刀轨以最后锚点累积,而不是凭空画一条穿过屏幕的线。这条规则与历史手势版本的碰撞规则共用,保证两种输入"切到同一颗水果"的判定一致。
五、交互与体验:重要界面、操作路径与反馈
- 三步校准界面:依次提示"平视确认 → 轻向右转确认 → 回正轻抬头确认",幅度不合适会即时报错,不会默默接受错误校准。
- 头部准星切果:水果从全屏不同位置落下,没有固定路线。轻轻转头让准星移动穿过水果即可切开;准星静止不会切果。
- 暂停与重新居中:单击或双击暂停;暂停、退出、后台都会停止姿态订阅。继续时重新以首帧真实姿态居中,并倒数 3 秒,沿用本局挑战。
- 单色屏的形状/文字标记:金果、冰冻、炸弹、禁果用"金/冰/炸/禁"和形状区分,不依赖颜色识别。
- 分榜与昵称:眼镜榜固定为头控榜(历史挥手成绩保留不删);昵称可从首页"我的昵称 → 语音输入"修改,挥手/头控、全部/七天榜统一显示新昵称。改昵称后旧成绩仍保留。
六、调试与验证:AIUI Studio 预览与真机调试
- 真机头控读数:在 RG_glasses(Android 12 / API 32,Ink 0.19.0-rc.20261013133404)上实测,头部姿态约 50.5Hz(30 秒 246→1764 帧),取消后停止采样,头控期间相机保持关闭。
- 校准顺序与幅度校验:captureCalibration 强制按 center→right→up 顺序,幅度 5°–35°、正交比例 ≥ 0.65,否则拒绝;这一步的回归用真实旋转数值重放,而不是只测"点了按钮"。
- 头控榜分榜(历史 v0.5.x):眼镜榜增加"挥手切 / 头部控制"切换,control=hand|head 进入 API 请求、缓存键和地址;头控独立榜、URL、七天筛选、分享保留、深链刷新均通过验收(portable + cloud 共 22/22 项,运行时错误 0)。旧响应省略 control 时只兼容挥手榜,不会把头榜成绩混进手榜。
- v0.5.0 曾在真机实际启动头控放松模式:随机大水果、头部准星、不限时无积分界面可见,暂停后 IMU 采样停止;该会话使用真实传感器,没有注入合成输入,也没有上传云端分数。
- v0.6.0 构建校验:verify_aix.py 新增"纯头控"断言——禁止 CAMERA 权限、禁止相机/掌检测模块文件、禁止页面残留旧链路标识符,报告字段 headOnlyVerified:true。测试用 cameras.length===0 断言头控用例(三步校准后开局、单准星慢移切果、暂停继续重居中)全部通过。
需要诚实标注的边界:以上多为静态校验、内存模拟与单点真机读数,不替代六种玩法的完整佩戴验收,也不代表长时间温升与续航评估。随文附真机首页、校准页与云榜截图(属真机截图,非模拟输入)。公开演示入口用于查看榜单与网页试玩,不会遥控眼镜相机。
七、成果与复盘:可运行成果、问题与迭代
可运行成果:当前交付包为 游戏大师.aix(v0.6.0),纯头控、六种玩法;公开排行榜(Netlify + Cloudflare D1)已上线,眼镜首页可直接看云榜。演示入口:https://euphonious-biscuit-a02b36.netlify.app/?platform=glasses#leaderboard ;网页试玩 /play/ 使用当前电脑或手机的摄像头,不会遥控眼镜相机。
遇到并解决的问题:
- 手势识别路径的教训(历史):最早尝试在 Ink 中直接跑模型,WASM 真机约 30 秒/帧;改用 Android Camera2 + LiteRT/XNNPACK 后单次推理约 112ms、连续约 6–7fps。这条链路在 v0.6.0 已整体移除,原因是"让用户装插件太麻烦",头控成为唯一控制方式。
- 单击语音取消(历史):原始触摸释放后 535–604ms 才到的迟到 Enter 会误触"取消录音"。修复把原始触摸与语义单击区分开,只在系统确认单击(Enter/bindtap)时同步开麦,并用专用回声窗口覆盖迟到事件。
- 头控替代手势的取舍:删掉相机链路后,包体积从约 2.47MB 降到约 117KB,权限只剩 RECORD_AUDIO,安装与启动大幅简化。
第三方与写作说明:
- 历史手势使用的掌检测模型来自 Google / MediaPipe 官方 palm_detection_lite.tflite,原模型未重新训练;模型、LiteRT 与相关库的来源、摘要及许可证随项目保留,按对应开源许可使用。头控使用的是 AIUI 官方 AbsoluteOrientationSensor,属于平台能力,不混入任何第三方姿态模型。
- 网页试玩使用 MediaPipe Hand Landmarker,是独立的浏览器链路,不能与眼镜的掌检测能力混同。
- 项目没有调用 OpenAI 或其他云端大模型 API 作为游戏运行依赖。
- 开发过程使用 AI 辅助代码与测试生成、资料整理和本文初稿;人工需求、用户实际操作反馈、模型参考对照、自动化测试和真机检查共同约束实现。AI 生成内容不自动等于事实,作者发表前仍需核对源码、实测证据、引用与本人贡献,并完成必要修改。这段说明不能替代活动对原创、实质开发与本人核验的要求。
后续迭代:继续在真机上完成六种玩法的逐项佩戴验收;评估长时间温升与续航;视固件能力决定是否重新提供"发送到手机"等便捷入口。
官方参考资料(核查于 2026-11-06)
[1] AIUI 绝对方向传感器:https://github.com/yodaos-project/AIUI/blob/main/documentation/3-api/device/absolute-orientation-sensor.md
[2] AIUI 页面能力(orientationSensor / onHeadGesture):https://github.com/yodaos-project/AIUI/blob/main/documentation/3-api/framework/page.md
[3] AIUI 陀螺仪:https://github.com/yodaos-project/AIUI/blob/main/documentation/3-api/device/gyroscope.md
[4] Android Camera2 与 camera 前台服务(历史手势链路):https://developer.android.com/media/camera/camera2
[5] LiteRT Android(历史手势链路):https://developers.google.com/edge/litert/android
[6] MediaPipe palm 检测配置与原始模型(历史手势链路):https://github.com/google-ai-edge/mediapipe/blob/master/mediapipe/modules/palm_detection/palm_detection_cpu.pbtxt
[7] 本文对应征文公告:https://forum.rokid.com/post/detail/3754
更多推荐



所有评论(0)