语音控制的太空射击游戏开发笔记
项目背景
最近在研究 Rokid AR 眼镜的开发,想做点有意思的东西。看了一圈案例,发现大家都在做一些比较"正经"的应用——导航、信息展示之类的。我就想,能不能整点不一样的?
游戏!而且是用语音控制的游戏。
这篇文章记录了我从零开始,用 JSAR 框架开发一款太空射击小游戏的全过程。代码不复杂,效果还挺酷的。
技术选型
- 框架: JSAR (Rokid 官方的空间应用运行时)
- 3D 引擎: Babylon.js
- 目标设备: Rokid AR 智能眼镜
- 开发环境: VSCode + JSAR 扩展
游戏设计
在写代码之前,先把游戏玩法想清楚:
元素
| 设计
| 玩家角色
| 三角形飞船,可左右移动
| 敌人
| 随机生成的陨石,从上往下掉
| 战斗方式
| 发射能量弹击毁陨石
| 得分规则
| 每击毁一个陨石+10分
| 失败条件
| 被陨石撞击3次游戏结束
| 难度曲线
| 分数越高,陨石速度越快
| 控制方式设计成双模式:
- 键盘模式:电脑上调试用(方向键移动,空格射击)
- 语音模式:Rokid 眼镜实际使用(说"左"、"右"、"发射")
核心技术实现
1. 3D 场景搭建
1.1 相机视角
采用 45 度俯视角,让玩家能同时看到自己的飞船和前方飞来的陨石:- const camera = new BABYLON.ArcRotateCamera(
- 'camera',
- 0,
- Math.PI / 4, // 45度俯视角
- 80, // 距离场景中心80单位
- new BABYLON.Vector3(0, 0, 0),
- scene
- );
复制代码 1.2 网格地板
为了增强 3D 空间感,添加了蓝色网格地板:- // 用线条绘制网格
- const gridSize = 150;
- const gridDivisions = 15;
- const lineColor = new BABYLON.Color3(0, 0.5, 1);
- // 横向线
- for (let i = 0; i <= gridDivisions; i++) {
- const z = (i / gridDivisions) * 200;
- const points = [
- new BABYLON.Vector3(-gridSize / 2, -20, z - 50),
- new BABYLON.Vector3(gridSize / 2, -20, z - 50)
- ];
- const line = BABYLON.MeshBuilder.CreateLines('gridLineH' + i, { points }, scene);
- line.color = lineColor;
- line.alpha = 0.5;
- }
- // 纵向线(类似代码)
复制代码
2. 飞船系统
2.1 飞船模型
飞船由三角形机身 + 机翼 + 引擎光效组成。为了在 AR 眼镜中看得清楚,所有尺寸都放大了 5 倍:- const starCount = 300;
- const stars = new BABYLON.PointsCloudSystem('stars', 3, scene);
- stars.addPoints(starCount, (particle, i) => {
- particle.position = new BABYLON.Vector3(
- Math.random() * 200 - 100,
- Math.random() * 200 - 100,
- Math.random() * 100 + 30 // Z轴分布增强深度
- );
- particle.color = new BABYLON.Color4(1, 1, 1, Math.random() * 0.8 + 0.2);
- });
- stars.buildMeshAsync();
复制代码 2.2 流畅移动控制
使用按键状态追踪实现流畅的连续移动:- // 三角形机身
- const ship = BABYLON.MeshBuilder.CreateCylinder('player', {
- height: 10, // 放大5倍后的尺寸
- diameterTop: 0,
- diameterBottom: 7.5,
- tessellation: 3 // 三条边形成三角形
- }, scene);
- ship.rotation.x = Math.PI / 2; // 旋转90度,让尖端朝上
- ship.position.y = -15;
- // 添加机翼增强3D效果
- const wingLeft = BABYLON.MeshBuilder.CreateBox('wingLeft', {
- width: 3,
- height: 0.5,
- depth: 4
- }, scene);
- wingLeft.position = new BABYLON.Vector3(-4, -2, 0);
- wingLeft.parent = ship;
- // 引擎光效
- const engineGlow = BABYLON.MeshBuilder.CreateSphere('engineGlow', {
- diameter: 2.5
- }, scene);
- engineGlow.position = new BABYLON.Vector3(0, -6, 0);
- engineGlow.parent = ship;
- const glowMaterial = new BABYLON.StandardMaterial('glowMat', scene);
- glowMaterial.emissiveColor = new BABYLON.Color3(1, 0.5, 0); // 橙色发光
- engineGlow.material = glowMaterial;
复制代码 这样按住方向键就能持续移动,没有系统按键延迟的顿挫感。
3. 武器系统
3.1 子弹设计
子弹采用圆柱体造型,带有尾迹效果:- this.keys = {};
- window.addEventListener('keydown', (event) => {
- this.keys[event.key] = true;
- if (event.key === ' ' || event.key === 'Enter') {
- this.shoot();
- }
- });
- window.addEventListener('keyup', (event) => {
- this.keys[event.key] = false;
- });
- // 在渲染循环中处理移动
- scene.registerBeforeRender(() => {
- if (this.keys['ArrowLeft'] || this.keys['a'] || this.keys['A']) {
- this.movePlayer(-1);
- }
- if (this.keys['ArrowRight'] || this.keys['d'] || this.keys['D']) {
- this.movePlayer(1);
- }
- });
复制代码
3.2 子弹更新逻辑
- shoot() {
- const bullet = BABYLON.MeshBuilder.CreateCylinder('bullet', {
- height: 3,
- diameter: 1
- }, this.scene);
- bullet.position = this.player.position.clone();
- bullet.position.y += 5;
- bullet.rotation.x = Math.PI / 2;
- // 黄色发光材质
- const material = new BABYLON.StandardMaterial('bulletMat', this.scene);
- material.emissiveColor = new BABYLON.Color3(1, 1, 0);
- bullet.material = material;
- // 添加尾迹
- const trail = BABYLON.MeshBuilder.CreateCylinder('trail', {
- height: 2,
- diameter: 0.5
- }, this.scene);
- trail.position = new BABYLON.Vector3(0, -2.5, 0);
- trail.parent = bullet;
- const trailMat = new BABYLON.StandardMaterial('trailMat', this.scene);
- trailMat.emissiveColor = new BABYLON.Color3(1, 0.8, 0);
- trailMat.alpha = 0.6;
- trail.material = trailMat;
- this.bullets.push(bullet);
- }
复制代码 4. 敌人系统
4.1 陨石生成
陨石使用 Babylon.js 的多面体,有 3 种不同形状:- scene.registerBeforeRender(() => {
- for (let i = this.bullets.length - 1; i >= 0; i--) {
- this.bullets[i].position.y += 8; // 速度也放大了
- // 飞出屏幕后销毁
- if (this.bullets[i].position.y > 50) {
- this.bullets[i].dispose();
- this.bullets.splice(i, 1);
- }
- }
- });
复制代码
4.2 定时生成
- spawnAsteroid() {
- const asteroid = BABYLON.MeshBuilder.CreatePolyhedron('asteroid', {
- type: Math.floor(Math.random() * 3), // 0-2随机形状
- size: Math.random() * 4 + 3 // 放大5倍
- }, this.scene);
- asteroid.position = new BABYLON.Vector3(
- Math.random() * 80 - 40, // X: -40到40随机位置
- 50, // Y: 从顶部出现
- Math.random() * 10 - 5 // Z: -5到5增加深度变化
- );
- // 岩石材质
- const material = new BABYLON.StandardMaterial('asteroidMat', this.scene);
- material.diffuseColor = new BABYLON.Color3(0.6, 0.4, 0.3);
- material.emissiveColor = new BABYLON.Color3(0.2, 0.1, 0.05);
- asteroid.material = material;
- // 随机旋转速度
- asteroid.rotationSpeed = new BABYLON.Vector3(
- Math.random() * 0.05,
- Math.random() * 0.05,
- Math.random() * 0.05
- );
- this.asteroids.push(asteroid);
- }
复制代码 5. 碰撞检测与爆炸效果
5.1 碰撞检测
使用 Babylon.js 内置的 intersectsMesh 方法:- startAsteroidSpawner() {
- this.asteroidSpawnTimer = setInterval(() => {
- this.spawnAsteroid();
- }, 2000); // 每2秒生成一个
- }
复制代码 6. UI 系统
6.1 得分和生命值显示
UI 使用 DynamicTexture 在 3D 平面上绘制文字:- for (let i = this.asteroids.length - 1; i >= 0; i--) {
- const asteroid = this.asteroids[i];
- // 检测子弹碰撞
- let hit = false;
- for (let j = this.bullets.length - 1; j >= 0; j--) {
- const bullet = this.bullets[j];
- if (asteroid.intersectsMesh(bullet, false)) {
- this.createExplosion(asteroid.position);
- asteroid.dispose();
- bullet.dispose();
- this.asteroids.splice(i, 1);
- this.bullets.splice(j, 1);
- this.score += 10;
- this.updateUI();
- hit = true;
- break;
- }
- }
- // 检测飞船碰撞
- if (!hit && asteroid.intersectsMesh(this.player, false)) {
- this.createExplosion(asteroid.position);
- asteroid.dispose();
- this.asteroids.splice(i, 1);
- this.lives--;
- this.updateUI();
- if (this.lives <= 0) {
- this.endGame();
- }
- }
- }
复制代码 canvas 尺寸是 1024x256,而 plane 是 60x12,这样宽高比一致,文字不会变形。
6.2 游戏结束界面
- createExplosion(position) {
- const particleSystem = new BABYLON.ParticleSystem('explosion', 100, this.scene);
- particleSystem.particleTexture = new BABYLON.Texture('', this.scene);
- particleSystem.emitter = position;
- particleSystem.minSize = 0.5;
- particleSystem.maxSize = 1.5;
- particleSystem.minLifeTime = 0.3;
- particleSystem.maxLifeTime = 0.6;
- particleSystem.emitRate = 200;
- particleSystem.createSphereEmitter(1);
- particleSystem.color1 = new BABYLON.Color4(1, 0.5, 0, 1);
- particleSystem.color2 = new BABYLON.Color4(1, 0.8, 0, 1);
- particleSystem.colorDead = new BABYLON.Color4(0.5, 0.5, 0.5, 0);
- particleSystem.minEmitPower = 1;
- particleSystem.maxEmitPower = 3;
- particleSystem.start();
- setTimeout(() => {
- particleSystem.stop();
- setTimeout(() => particleSystem.dispose(), 1000);
- }, 200);
- }
复制代码
7. 语音控制
7.1 Rokid 语音 API
JSAR 提供了 rokid.voice API 用于语音识别:- createUI() {
- // 得分显示
- this.scoreText = BABYLON.MeshBuilder.CreatePlane('scoreText', {
- width: 60,
- height: 12
- }, this.scene);
- this.scoreText.position = new BABYLON.Vector3(-50, 40, 0);
- const scoreTexture = new BABYLON.DynamicTexture('scoreTexture',
- { width: 1024, height: 256 }, this.scene, true);
- const scoreMaterial = new BABYLON.StandardMaterial('scoreMat', this.scene);
- scoreMaterial.diffuseTexture = scoreTexture;
- scoreMaterial.emissiveColor = new BABYLON.Color3(1, 1, 1);
- this.scoreText.material = scoreMaterial;
- this.scoreText.renderingGroupId = 1; // 确保UI在最上层
- this.scoreTexture = scoreTexture;
- // 生命值显示(类似代码)
- }
- updateUI() {
- // 更新得分
- const ctx = this.scoreTexture.getContext();
- ctx.clearRect(0, 0, 1024, 256);
- ctx.fillStyle = '#FFD700';
- ctx.font = 'bold 120px Arial';
- ctx.textAlign = 'left';
- ctx.fillText('得分: ' + this.score, 50, 160);
- this.scoreTexture.update();
- // 更新生命值(类似代码)
- }
复制代码 7.2 键盘模拟语音(调试用)
在电脑上调试时,用 J/K/L 键模拟语音命令:
[code]window.addEventListener('keydown', (event) => { // 语音模拟 if (event.key === 'j' || event.key === 'J') { console.log('
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |