本项目是一个基于 JSAR(JavaScript Spatial Augmented Reality)框架和 Babylon.js 引擎开发的 3D 交互游戏,专为 Rokid AR 眼镜设计。游戏采用简洁的玩法:玩家通过手指移动(本地使用鼠标模拟)控制黄色球体,移动去接触随机生成的红色目标球,每次成功接触即可得分。
- JSAR: Rokid AR 空间计算框架
- Babylon.js: 3D 渲染引擎
- TypeScript: 开发语言
- XSML: 空间场景标记语言
项目使用 XSML 作为入口文件,定义了空间场景的基本结构:
<xsml version="1.0"> <head> <title>接球游戏</title> <script src="./lib/main.ts"></script> </head> <space> </space> </xsml>
XSML 提供了轻量级的场景声明方式,所有游戏逻辑都在 TypeScript 模块中实现。
游戏采用俯视视角,创建了一个 20x20 的网格地面作为游戏区域:
// 场景设置 scene.clearColor = new BABYLON.Color4(0.1, 0.1, 0.15, 1); // 光照 const light = new BABYLON.HemisphericLight('light', new BABYLON.Vector3(0, 1, 0), scene); light.intensity = 1.2; // 创建地面与网格线 const ground = BABYLON.MeshBuilder.CreateGround('ground', { width: GRID_SIZE * CELL_SIZE, height: GRID_SIZE * CELL_SIZE }, scene);
网格线的绘制增强了空间感知,帮助玩家更好地判断位置:
for (let i = 0; i <= GRID_SIZE; i++) { const lineH = BABYLON.MeshBuilder.CreateBox('lineH' + i, { width: GRID_SIZE * CELL_SIZE, height: 0.05, depth: 0.05 }, scene); lineH.position = new BABYLON.Vector3(0, 0.01, i * CELL_SIZE - GRID_SIZE * CELL_SIZE / 2); // ... 垂直网格线类似 }
黄色玩家球带有发光效果和呼吸动画:
const player = BABYLON.MeshBuilder.CreateSphere('player', { diameter: CELL_SIZE * 0.8 }, scene); const playerMat = new BABYLON.StandardMaterial('playerMat', scene); playerMat.diffuseColor = new BABYLON.Color3(1, 1, 0.3); playerMat.emissiveColor = new BABYLON.Color3(0.5, 0.5, 0); // 自发光 player.material = playerMat; // 呼吸动画 let pulseTime = 0; scene.onBeforeRenderObservable.add(() => { pulseTime += 0.05; const scale = 1 + Math.sin(pulseTime) * 0.1; player.scaling = new BABYLON.Vector3(scale, scale, scale); });
红色目标球随机生成在网格上:
function createTarget() { if (target) { target.dispose(); target = null; } // 随机网格坐标 targetGridX = Math.floor(Math.random() * GRID_SIZE); targetGridY = Math.floor(Math.random() * GRID_SIZE); target = BABYLON.MeshBuilder.CreateSphere('target_' + Date.now(), { diameter: CELL_SIZE * 0.8 }, scene); const targetMat = new BABYLON.StandardMaterial('targetMat_' + Date.now(), scene); targetMat.diffuseColor = new BABYLON.Color3(1, 0.2, 0.2); targetMat.emissiveColor = new BABYLON.Color3(0.5, 0, 0); target.material = targetMat; // 坐标转换:网格 -> 世界坐标 const targetX = targetGridX * CELL_SIZE - GRID_SIZE * CELL_SIZE / 2 + CELL_SIZE / 2; const targetZ = targetGridY * CELL_SIZE - GRID_SIZE * CELL_SIZE / 2 + CELL_SIZE / 2; target.position = new BABYLON.Vector3(targetX, CELL_SIZE / 2, targetZ); }
这是项目的核心技术突破。JSAR 的交互系统与传统 Babylon.js 的 ActionManager 不同,需要使用专门的输入事件 API。
关键发现:必须先调用 `watchInputEvent()` 启用输入监听:
try { (spatialDocument as any).watchInputEvent(); console.log('[OK] watchInputEvent() 调用成功'); } catch (error) { console.log('[ERROR] watchInputEvent() 调用失败:', error); }
动态坐标映射算法:
鼠标原始坐标需要映射到游戏网格,但不同设备的坐标范围不同。解决方案是动态追踪坐标范围:
let mouseEventCount = 0; let minMouseX = 999999, maxMouseX = 0; let minMouseY = 999999, maxMouseY = 0; spatialDocument.addEventListener('mouse', (event: any) => { if (!event.inputData) return; const mouseX = event.inputData.PositionX; const mouseY = event.inputData.PositionY; // 关键:过滤无效坐标 if (mouseX === 0 || mouseY === 0) { return; } // 记录坐标范围 minMouseX = Math.min(minMouseX, mouseX); maxMouseX = Math.max(maxMouseX, mouseX); minMouseY = Math.min(minMouseY, mouseY); maxMouseY = Math.max(maxMouseY, mouseY); const rangeX = maxMouseX - minMouseX; const rangeY = maxMouseY - minMouseY; // 只有足够的移动范围才更新位置 if (rangeX > 20 && rangeY > 20) { const normalizedX = (mouseX - minMouseX) / rangeX; const normalizedY = (mouseY - minMouseY) / rangeY; playerGridX = Math.floor(normalizedX * GRID_SIZE); playerGridY = Math.floor(normalizedY * GRID_SIZE); // 限制在网格内 playerGridX = Math.max(0, Math.min(GRID_SIZE - 1, playerGridX)); playerGridY = Math.max(0, Math.min(GRID_SIZE - 1, playerGridY)); updatePlayerPosition(); } });
设计亮点:
1. 过滤无效坐标:(0,0) 坐标会破坏范围计算,必须过滤
2. 动态范围适配:不依赖固定坐标范围,自动适应不同设备
3. 最小阈值检测:rangeX/Y > 20 确保有足够的移动数据才开始映射
采用网格坐标精确匹配的方式检测碰撞:
if (playerGridX === targetGridX && playerGridY === targetGridY) { score++; console.log('[SUCCESS] 得分成功!'); console.log('当前得分: ' + score); // 创建新目标 createTarget(); // 玩家闪烁反馈 const originalColor = playerMat.diffuseColor.clone(); playerMat.diffuseColor = new BABYLON.Color3(0, 1, 0); setTimeout(() => { playerMat.diffuseColor = originalColor; }, 200); }
俯视角度展示了完整的网格地面,黄色玩家球位于左上角,红色目标球位于中右区域,网格线清晰可见,提供了良好的空间定位参考。
透视角度展示了 3D 效果,可以看到地面网格的透视变化和球体的立体感,底部的 AR 控制图标清晰可见。
控制台输出显示了玩家从 [3,17] 移动到 [9,9] 的过程,最终成功与目标重合,得分为 1。
得分后,目标切换,黄色球和红色球的位置发生了变化,得分后目标球自动移动到了新的随机位置 [0,15]。
控制台日志详细记录了目标创建过程,包括网格坐标、世界坐标、Mesh 名称和场景中的对象总数,便于调试。
问题:初期使用 Babylon.js 的 ActionManager,点击事件完全无响应。
解决:
● 发现 JSAR 需要显式调用 watchInputEvent()
● 改用 spatialDocument.addEventListener('mouse', ...) 模式
问题:
● 玩家位置始终为 [0,0]
● 坐标范围在不同设备上不一致
● 出现 (0,0) 无效坐标污染数据
解决方案:
// 1. 过滤无效坐标 if (mouseX === 0 || mouseY === 0) return; // 2. 动态范围追踪 minMouseX = Math.min(minMouseX, mouseX); maxMouseX = Math.max(maxMouseX, mouseX); // 3. 归一化映射 const normalizedX = (mouseX - minMouseX) / rangeX; playerGridX = Math.floor(normalizedX * GRID_SIZE);
jsar-rokid/ ├── main.xsml # XSML 场景入口 ├── lib/ │ └── main.ts # 游戏主逻辑(255 行) ├── package.json # 项目配置 ├── model/ │ └── welcome.glb # 3D 模型资源 └── icon.png # 应用图标
本项目成功实现了 JSAR 框架下的鼠标交互机制,通过动态坐标映射算法解决了跨设备输入适配问题。简洁的游戏设计验证了 JSAR 在 AR 应用开发中的可行性,为后续更复杂的空间交互应用奠定了基础。
关键技术突破在于理解 JSAR 的输入事件模型,以及实现自适应的坐标归一化算法。这些经验对于开发 Rokid AR 应用具有重要的参考价值。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。