找回密码
 立即注册
首页 业界区 业界 语音控制的太空射击游戏开发笔记

语音控制的太空射击游戏开发笔记

师悠逸 3 天前
语音控制的太空射击游戏开发笔记

项目背景

最近在研究 Rokid AR 眼镜的开发,想做点有意思的东西。看了一圈案例,发现大家都在做一些比较"正经"的应用——导航、信息展示之类的。我就想,能不能整点不一样的?
游戏!而且是用语音控制的游戏
这篇文章记录了我从零开始,用 JSAR 框架开发一款太空射击小游戏的全过程。代码不复杂,效果还挺酷的。
1.png

技术选型


  • 框架: JSAR (Rokid 官方的空间应用运行时)
  • 3D 引擎: Babylon.js
  • 目标设备: Rokid AR 智能眼镜
  • 开发环境: VSCode + JSAR 扩展
游戏设计

在写代码之前,先把游戏玩法想清楚:
元素
设计
玩家角色
三角形飞船,可左右移动
敌人
随机生成的陨石,从上往下掉
战斗方式
发射能量弹击毁陨石
得分规则
每击毁一个陨石+10分
失败条件
被陨石撞击3次游戏结束
难度曲线
分数越高,陨石速度越快
控制方式设计成双模式

  • 键盘模式:电脑上调试用(方向键移动,空格射击)
  • 语音模式:Rokid 眼镜实际使用(说"左"、"右"、"发射")
核心技术实现

1. 3D 场景搭建

1.1 相机视角

采用 45 度俯视角,让玩家能同时看到自己的飞船和前方飞来的陨石:
  1. const camera = new BABYLON.ArcRotateCamera(
  2.   'camera',
  3.   0,
  4.   Math.PI / 4,  // 45度俯视角
  5.   80,           // 距离场景中心80单位
  6.   new BABYLON.Vector3(0, 0, 0),
  7.   scene
  8. );
复制代码
1.2 网格地板

为了增强 3D 空间感,添加了蓝色网格地板:
  1. // 用线条绘制网格
  2. const gridSize = 150;
  3. const gridDivisions = 15;
  4. const lineColor = new BABYLON.Color3(0, 0.5, 1);
  5. // 横向线
  6. for (let i = 0; i <= gridDivisions; i++) {
  7.   const z = (i / gridDivisions) * 200;
  8.   const points = [
  9.     new BABYLON.Vector3(-gridSize / 2, -20, z - 50),
  10.     new BABYLON.Vector3(gridSize / 2, -20, z - 50)
  11.   ];
  12.   const line = BABYLON.MeshBuilder.CreateLines('gridLineH' + i, { points }, scene);
  13.   line.color = lineColor;
  14.   line.alpha = 0.5;
  15. }
  16. // 纵向线(类似代码)
复制代码
2.png

2. 飞船系统

2.1 飞船模型

飞船由三角形机身 + 机翼 + 引擎光效组成。为了在 AR 眼镜中看得清楚,所有尺寸都放大了 5 倍:
  1. const starCount = 300;
  2. const stars = new BABYLON.PointsCloudSystem('stars', 3, scene);
  3. stars.addPoints(starCount, (particle, i) => {
  4.   particle.position = new BABYLON.Vector3(
  5.     Math.random() * 200 - 100,
  6.     Math.random() * 200 - 100,
  7.     Math.random() * 100 + 30  // Z轴分布增强深度
  8.   );
  9.   particle.color = new BABYLON.Color4(1, 1, 1, Math.random() * 0.8 + 0.2);
  10. });
  11. stars.buildMeshAsync();
复制代码
2.2 流畅移动控制

使用按键状态追踪实现流畅的连续移动:
  1. // 三角形机身
  2. const ship = BABYLON.MeshBuilder.CreateCylinder('player', {
  3.   height: 10,        // 放大5倍后的尺寸
  4.   diameterTop: 0,
  5.   diameterBottom: 7.5,
  6.   tessellation: 3    // 三条边形成三角形
  7. }, scene);
  8. ship.rotation.x = Math.PI / 2;  // 旋转90度,让尖端朝上
  9. ship.position.y = -15;
  10. // 添加机翼增强3D效果
  11. const wingLeft = BABYLON.MeshBuilder.CreateBox('wingLeft', {
  12.   width: 3,
  13.   height: 0.5,
  14.   depth: 4
  15. }, scene);
  16. wingLeft.position = new BABYLON.Vector3(-4, -2, 0);
  17. wingLeft.parent = ship;
  18. // 引擎光效
  19. const engineGlow = BABYLON.MeshBuilder.CreateSphere('engineGlow', {
  20.   diameter: 2.5
  21. }, scene);
  22. engineGlow.position = new BABYLON.Vector3(0, -6, 0);
  23. engineGlow.parent = ship;
  24. const glowMaterial = new BABYLON.StandardMaterial('glowMat', scene);
  25. glowMaterial.emissiveColor = new BABYLON.Color3(1, 0.5, 0);  // 橙色发光
  26. engineGlow.material = glowMaterial;
复制代码
这样按住方向键就能持续移动,没有系统按键延迟的顿挫感。
3.png

3. 武器系统

3.1 子弹设计

子弹采用圆柱体造型,带有尾迹效果:
  1. this.keys = {};
  2. window.addEventListener('keydown', (event) => {
  3.   this.keys[event.key] = true;
  4.   if (event.key === ' ' || event.key === 'Enter') {
  5.     this.shoot();
  6.   }
  7. });
  8. window.addEventListener('keyup', (event) => {
  9.   this.keys[event.key] = false;
  10. });
  11. // 在渲染循环中处理移动
  12. scene.registerBeforeRender(() => {
  13.   if (this.keys['ArrowLeft'] || this.keys['a'] || this.keys['A']) {
  14.     this.movePlayer(-1);
  15.   }
  16.   if (this.keys['ArrowRight'] || this.keys['d'] || this.keys['D']) {
  17.     this.movePlayer(1);
  18.   }
  19. });
复制代码
4.png

3.2 子弹更新逻辑
  1. shoot() {
  2.   const bullet = BABYLON.MeshBuilder.CreateCylinder('bullet', {
  3.     height: 3,
  4.     diameter: 1
  5.   }, this.scene);
  6.   bullet.position = this.player.position.clone();
  7.   bullet.position.y += 5;
  8.   bullet.rotation.x = Math.PI / 2;
  9.   // 黄色发光材质
  10.   const material = new BABYLON.StandardMaterial('bulletMat', this.scene);
  11.   material.emissiveColor = new BABYLON.Color3(1, 1, 0);
  12.   bullet.material = material;
  13.   // 添加尾迹
  14.   const trail = BABYLON.MeshBuilder.CreateCylinder('trail', {
  15.     height: 2,
  16.     diameter: 0.5
  17.   }, this.scene);
  18.   trail.position = new BABYLON.Vector3(0, -2.5, 0);
  19.   trail.parent = bullet;
  20.   const trailMat = new BABYLON.StandardMaterial('trailMat', this.scene);
  21.   trailMat.emissiveColor = new BABYLON.Color3(1, 0.8, 0);
  22.   trailMat.alpha = 0.6;
  23.   trail.material = trailMat;
  24.   this.bullets.push(bullet);
  25. }
复制代码
4. 敌人系统

4.1 陨石生成

陨石使用 Babylon.js 的多面体,有 3 种不同形状:
  1. scene.registerBeforeRender(() => {
  2.   for (let i = this.bullets.length - 1; i >= 0; i--) {
  3.     this.bullets[i].position.y += 8;  // 速度也放大了
  4.     // 飞出屏幕后销毁
  5.     if (this.bullets[i].position.y > 50) {
  6.       this.bullets[i].dispose();
  7.       this.bullets.splice(i, 1);
  8.     }
  9.   }
  10. });
复制代码
5.png

4.2 定时生成
  1. spawnAsteroid() {
  2.   const asteroid = BABYLON.MeshBuilder.CreatePolyhedron('asteroid', {
  3.     type: Math.floor(Math.random() * 3),  // 0-2随机形状
  4.     size: Math.random() * 4 + 3           // 放大5倍
  5.   }, this.scene);
  6.   asteroid.position = new BABYLON.Vector3(
  7.     Math.random() * 80 - 40,  // X: -40到40随机位置
  8.     50,                       // Y: 从顶部出现
  9.     Math.random() * 10 - 5    // Z: -5到5增加深度变化
  10.   );
  11.   // 岩石材质
  12.   const material = new BABYLON.StandardMaterial('asteroidMat', this.scene);
  13.   material.diffuseColor = new BABYLON.Color3(0.6, 0.4, 0.3);
  14.   material.emissiveColor = new BABYLON.Color3(0.2, 0.1, 0.05);
  15.   asteroid.material = material;
  16.   // 随机旋转速度
  17.   asteroid.rotationSpeed = new BABYLON.Vector3(
  18.     Math.random() * 0.05,
  19.     Math.random() * 0.05,
  20.     Math.random() * 0.05
  21.   );
  22.   this.asteroids.push(asteroid);
  23. }
复制代码
5. 碰撞检测与爆炸效果

5.1 碰撞检测

使用 Babylon.js 内置的 intersectsMesh 方法:
  1. startAsteroidSpawner() {
  2.   this.asteroidSpawnTimer = setInterval(() => {
  3.     this.spawnAsteroid();
  4.   }, 2000);  // 每2秒生成一个
  5. }
复制代码
6. UI 系统

6.1 得分和生命值显示

UI 使用 DynamicTexture 在 3D 平面上绘制文字:
  1. for (let i = this.asteroids.length - 1; i >= 0; i--) {
  2.   const asteroid = this.asteroids[i];
  3.   // 检测子弹碰撞
  4.   let hit = false;
  5.   for (let j = this.bullets.length - 1; j >= 0; j--) {
  6.     const bullet = this.bullets[j];
  7.     if (asteroid.intersectsMesh(bullet, false)) {
  8.       this.createExplosion(asteroid.position);
  9.       asteroid.dispose();
  10.       bullet.dispose();
  11.       this.asteroids.splice(i, 1);
  12.       this.bullets.splice(j, 1);
  13.       this.score += 10;
  14.       this.updateUI();
  15.       hit = true;
  16.       break;
  17.     }
  18.   }
  19.   // 检测飞船碰撞
  20.   if (!hit && asteroid.intersectsMesh(this.player, false)) {
  21.     this.createExplosion(asteroid.position);
  22.     asteroid.dispose();
  23.     this.asteroids.splice(i, 1);
  24.     this.lives--;
  25.     this.updateUI();
  26.     if (this.lives <= 0) {
  27.       this.endGame();
  28.     }
  29.   }
  30. }
复制代码
canvas 尺寸是 1024x256,而 plane 是 60x12,这样宽高比一致,文字不会变形。
6.png

6.2 游戏结束界面
  1. createExplosion(position) {
  2.   const particleSystem = new BABYLON.ParticleSystem('explosion', 100, this.scene);
  3.   particleSystem.particleTexture = new BABYLON.Texture('', this.scene);
  4.   particleSystem.emitter = position;
  5.   particleSystem.minSize = 0.5;
  6.   particleSystem.maxSize = 1.5;
  7.   particleSystem.minLifeTime = 0.3;
  8.   particleSystem.maxLifeTime = 0.6;
  9.   particleSystem.emitRate = 200;
  10.   particleSystem.createSphereEmitter(1);
  11.   particleSystem.color1 = new BABYLON.Color4(1, 0.5, 0, 1);
  12.   particleSystem.color2 = new BABYLON.Color4(1, 0.8, 0, 1);
  13.   particleSystem.colorDead = new BABYLON.Color4(0.5, 0.5, 0.5, 0);
  14.   particleSystem.minEmitPower = 1;
  15.   particleSystem.maxEmitPower = 3;
  16.   particleSystem.start();
  17.   setTimeout(() => {
  18.     particleSystem.stop();
  19.     setTimeout(() => particleSystem.dispose(), 1000);
  20.   }, 200);
  21. }
复制代码

7. 语音控制

7.1 Rokid 语音 API

JSAR 提供了 rokid.voice API 用于语音识别:
  1. createUI() {
  2.   // 得分显示
  3.   this.scoreText = BABYLON.MeshBuilder.CreatePlane('scoreText', {
  4.     width: 60,
  5.     height: 12
  6.   }, this.scene);
  7.   this.scoreText.position = new BABYLON.Vector3(-50, 40, 0);
  8.   const scoreTexture = new BABYLON.DynamicTexture('scoreTexture',
  9.     { width: 1024, height: 256 }, this.scene, true);
  10.   const scoreMaterial = new BABYLON.StandardMaterial('scoreMat', this.scene);
  11.   scoreMaterial.diffuseTexture = scoreTexture;
  12.   scoreMaterial.emissiveColor = new BABYLON.Color3(1, 1, 1);
  13.   this.scoreText.material = scoreMaterial;
  14.   this.scoreText.renderingGroupId = 1;  // 确保UI在最上层
  15.   this.scoreTexture = scoreTexture;
  16.   // 生命值显示(类似代码)
  17. }
  18. updateUI() {
  19.   // 更新得分
  20.   const ctx = this.scoreTexture.getContext();
  21.   ctx.clearRect(0, 0, 1024, 256);
  22.   ctx.fillStyle = '#FFD700';
  23.   ctx.font = 'bold 120px Arial';
  24.   ctx.textAlign = 'left';
  25.   ctx.fillText('得分: ' + this.score, 50, 160);
  26.   this.scoreTexture.update();
  27.   // 更新生命值(类似代码)
  28. }
复制代码
7.2 键盘模拟语音(调试用)

在电脑上调试时,用 J/K/L 键模拟语音命令:
[code]window.addEventListener('keydown', (event) => {  // 语音模拟  if (event.key === 'j' || event.key === 'J') {    console.log('
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册