使用Tabs选项卡组件快速搭建鸿蒙APP框架

砂中蛔融项目背景

最近在研究 Rokid AR 眼镜的开发,想做点有意思的东西。看了一圈案例,发现大家都在做一些比较"正经"的应用——导航、信息展示之类的。我就想,能不能整点不一样的?

游戏!而且是用语音控制的游戏。

这篇文章记录了我从零开始,用 JSAR 框架开发一款太空射击小游戏的全过程。代码不复杂,效果还挺酷的。

image

技术选型

框架: 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;

}

// 纵向线(类似代码)

网格的透视效果让玩家能清晰感知深度和速度。

image

1.3 星空背景

用点云系统创建 300 颗随机分布的星星:

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

image

2. 飞船系统

2.1 飞船模型

飞船由三角形机身 + 机翼 + 引擎光效组成。为了在 AR 眼镜中看得清楚,所有尺寸都放大了 5 倍:

// 三角形机身

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;

2.2 流畅移动控制

使用按键状态追踪实现流畅的连续移动:

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

}

});

这样按住方向键就能持续移动,没有系统按键延迟的顿挫感。

image

3. 武器系统

3.1 子弹设计

子弹采用圆柱体造型,带有尾迹效果:

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

}

image

3.2 子弹更新逻辑

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. 敌人系统

4.1 陨石生成

陨石使用 Babylon.js 的多面体,有 3 种不同形状:

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

}

image

4.2 定时生成

startAsteroidSpawner() {

this.asteroidSpawnTimer = setInterval(() => {

this.spawnAsteroid();

}, 2000); // 每2秒生成一个

}

5. 碰撞检测与爆炸效果

5.1 碰撞检测

使用 Babylon.js 内置的 intersectsMesh 方法:

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

}

}

}

5.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);

}

6. UI 系统

6.1 得分和生命值显示

UI 使用 DynamicTexture 在 3D 平面上绘制文字:

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

// 更新生命值(类似代码)

}

canvas 尺寸是 1024x256,而 plane 是 60x12,这样宽高比一致,文字不会变形。

image

6.2 游戏结束界面

endGame() {

this.gameOver = true;

clearInterval(this.asteroidSpawnTimer);

const gameOverText = BABYLON.MeshBuilder.CreatePlane('gameOverText', {

width: 120,

height: 40

}, this.scene);

gameOverText.position = new BABYLON.Vector3(0, 0, 0);

const texture = new BABYLON.DynamicTexture('gameOverTexture', {

width: 1024,

height: 512

}, this.scene, true);

const ctx = texture.getContext();

ctx.fillStyle = 'rgba(0, 0, 0, 0.9)';

ctx.fillRect(0, 0, 1024, 512);

ctx.fillStyle = '#FF3333';

ctx.font = 'bold 100px Arial';

ctx.textAlign = 'center';

ctx.fillText('游戏结束', 512, 150);

ctx.fillStyle = '#FFD700';

ctx.font = 'bold 80px Arial';

ctx.fillText('最终得分: ' + this.score, 512, 280);

ctx.fillStyle = '#FFFFFF';

ctx.font = '50px Arial';

ctx.fillText('按 R 重新开始', 512, 400);

texture.update();

const material = new BABYLON.StandardMaterial('gameOverMat', this.scene);

material.diffuseTexture = texture;

material.emissiveColor = new BABYLON.Color3(0.8, 0.8, 0.8);

gameOverText.material = material;

gameOverText.renderingGroupId = 2; // 最上层

}

7. 语音控制

7.1 Rokid 语音 API

JSAR 提供了 rokid.voice API 用于语音识别:

setupVoiceControl() {

if (typeof rokid !== 'undefined' && rokid.voice) {

rokid.voice.on('command', (command) => {

switch(command) {

case '左':

this.movePlayer(-1);

break;

case '右':

this.movePlayer(1);

break;

case '发射':

case '开火':

this.shoot();

break;

}

});

}

}

7.2 键盘模拟语音(调试用)

在电脑上调试时,用 J/K/L 键模拟语音命令:

window.addEventListener('keydown', (event) => {

// 语音模拟

if (event.key === 'j' || event.key === 'J') {

console.log('?? 语音命令: 左');

this.movePlayer(-1);

} else if (event.key === 'l' || event.key === 'L') {

console.log('?? 语音命令: 右');

this.movePlayer(1);

} else if (event.key === 'k' || event.key === 'K') {

console.log('?? 语音命令: 发射');

this.shoot();

}

});

image

8. 游戏难度递增

根据得分提升游戏难度:

// 在碰撞检测中,击中陨石后

this.score += 10;

this.updateUI();

// 难度递增

if (this.score % 50 === 0 && this.difficulty < 2) {

this.difficulty += 0.1;

console.log('难度提升至: ' + this.difficulty.toFixed(1));

}

// 在陨石更新时应用难度

asteroid.position.y -= GAME_CONFIG.asteroidSpeed * this.difficulty;

每得 50 分,陨石速度提升 10%,最高提升到 2 倍速。

性能优化

对象管理

游戏中会频繁创建和销毁子弹、陨石,必须及时清理:

// 子弹飞出屏幕

if (bullet.position.y > 50) {

bullet.dispose(); // 销毁3D对象

this.bullets.splice(i, 1); // 从数组删除

}

// 陨石飞出屏幕

if (asteroid.position.y < -40) {

asteroid.dispose();

this.asteroids.splice(i, 1);

}

渲染分组

使用 renderingGroupId 控制渲染顺序,确保 UI 始终在最上层:

this.scoreText.renderingGroupId = 1;

this.livesText.renderingGroupId = 1;

gameOverText.renderingGroupId = 2;

控制说明

键盘模式(电脑调试)

按键

功能

← / A

向左移动

→ / D

向右移动

空格 / 回车

发射

R

重新开始

语音模拟(键盘)

按键

模拟语音

J

"左"

L

"右"

K

"发射"

语音控制(Rokid 眼镜)

直接说:

"左" - 飞船左移

"右" - 飞船右移

"发射" / "开火" - 射击

开发心得

踩过的坑

忘记 dispose 对象 → 内存泄漏,游戏越来越卡

碰撞检测死循环 → 删除元素时索引出错,要倒序遍历

粒子系统没清理 → 爆炸效果叠加导致卡顿

相机角度调试 → 最初是正面视角太平面,改成 45 度俯视后立体感大增

物体尺寸太小 → 第一版看不清细节,放大 5-10 倍后体验好很多

经验总结

3D 游戏要注重空间感:相机角度、参考物(网格)、Z 轴变化

尺寸和速度要成比例调整:放大物体后速度也要相应提升

按键状态追踪:比 keydown 事件更流畅,没有系统延迟

UI 宽高比:canvas 和 plane 的宽高比要一致,避免文字变形

及时清理对象:dispose + 数组 splice 两步都要做

多用 console.log 调试:特别是碰撞检测部分

基于数据驱动的 Koopman 算子的递归神经网络模型线性化,用于纳米定位系统的预测控制研究(Matlab代码实现)内容概要:本文围绕“基于数据驱动的 Koopman 算子的递归神经网络模型线性化,用于纳米定位系统的预测控制研究”展开,提出了一种结合数据驱动方法与Koopman算子理论的递归神经网络(RNN)模型线性化方法,旨在提升纳米定位系统的预测控制精度与动态响应能力。研究通过构建数据驱动的线性化模型,克服了传统非线性系统建模复杂、计算开销大的问题,并在Matlab平台上实现了完整的算法仿真与验证,展示了该方法在高精度定位控制中的有效性与实用性。; 适合人群:具备一定自动化、控制理论或机器学习背景的科研人员与工程技术人员,尤其是从事精密定位、智能控制、非线性系统建模与预测控制相关领域的研究生与研究人员。; 使用场景及目标:①应用于纳米级精密定位系统(如原子力显微镜、半导体制造设备)中的高性能预测控制;②为复杂非线性系统的数据驱动建模与线性化提供新思路;③结合深度学习与经典控制理论,推动智能控制算法的实际落地。; 阅读建议:建议读者结合Matlab代码实现部分,深入理解Koopman算子与RNN结合的建模范式,重点关注数据预处理、模型训练与控制系统集成等关键环节,并可通过替换实际系统数据进行迁移验证,以掌握该方法的核心思想与工程应用技巧。
基于粒子群算法优化Kmeans聚类的居民用电行为分析研究(Matlb代码实现)内容概要:本文围绕基于粒子群算法(PSO)优化Kmeans聚类的居民用电行为分析展开研究,提出了一种结合智能优化算法与传统聚类方法的技术路径。通过使用粒子群算法优化Kmeans聚类的初始聚类中心,有效克服了传统Kmeans算法易陷入局部最优、对初始值敏感的问题,提升了聚类的稳定性和准确性。研究利用Matlab实现了该算法,并应用于居民用电数据的行为模式识别与分类,有助于精细化电力需求管理、用户画像构建及个性化用电服务设计。文档还提及相关应用场景如负荷预测、电力系统优化等,并提供了配套代码资源。; 适合人群:具备一定Matlab编程基础,从事电力系统、智能优化算法、数据分析等相关领域的研究人员或工程技术人员,尤其适合研究生及科研人员。; 使用场景及目标:①用于居民用电行为的高效聚类分析,挖掘典型用电模式;②提升Kmeans聚类算法的性能,避免局部最优问题;③为电力公司开展需求响应、负荷预测和用户分群管理提供技术支持;④作为智能优化算法与机器学习结合应用的教学与科研案例。; 阅读建议:建议读者结合提供的Matlab代码进行实践操作,深入理解PSO优化Kmeans的核心机制,关注参数设置对聚类效果的影响,并尝试将其应用于其他相似的数据聚类问题中,以加深理解和拓展应用能力。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值