JSAR 鼠标交互接球游戏开发实践

举报
Damon小智 发表于 2025/10/16 10:20:43 2025/10/16
【摘要】 本项目是一个基于 JSAR(JavaScript Spatial Augmented Reality)框架和 Babylon.js 引擎开发的 3D 交互游戏,专为 Rokid AR 眼镜设计。游戏采用简洁的玩法:玩家通过手指移动(本地使用鼠标模拟)控制黄色球体,移动去接触随机生成的红色目标球,每次成功接触即可得分。

项目概述

本项目是一个基于 JSAR(JavaScript Spatial Augmented Reality)框架和 Babylon.js 引擎开发的 3D 交互游戏,专为 Rokid AR 眼镜设计。游戏采用简洁的玩法:玩家通过手指移动(本地使用鼠标模拟)控制黄色球体,移动去接触随机生成的红色目标球,每次成功接触即可得分。


技术栈

- JSAR: Rokid AR 空间计算框架

- Babylon.js: 3D 渲染引擎

- TypeScript: 开发语言

- XSML: 空间场景标记语言


场景结构与核心设计

XSML 场景定义

项目使用 XSML 作为入口文件,定义了空间场景的基本结构:

<xsml version="1.0">
  <head>
    <title>接球游戏</title>
    <script src="./lib/main.ts"></script>
  </head>
  <space>
  </space>
</xsml>

XSML 提供了轻量级的场景声明方式,所有游戏逻辑都在 TypeScript 模块中实现。


核心代码深度解析

1. 场景初始化

游戏采用俯视视角,创建了一个 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);
  // ... 垂直网格线类似
}


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);
}


3. JSAR 鼠标交互系统

这是项目的核心技术突破。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 确保有足够的移动数据才开始映射

4. 碰撞检测与得分系统

采用网格坐标精确匹配的方式检测碰撞:

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 名称和场景中的对象总数,便于调试。



卡点回顾

卡点1:JSAR 输入事件不触发

问题:初期使用 Babylon.js 的 ActionManager,点击事件完全无响应。

解决

  • 发现 JSAR 需要显式调用 watchInputEvent()
  • 改用 spatialDocument.addEventListener('mouse', ...) 模式

卡点2:鼠标坐标映射不准确

问题

  • 玩家位置始终为 [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 应用具有重要的参考价值。


【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。