小游戏灵感来自于抖音的奶龙飞车 4 小游戏,感兴趣的可以搜索查看下 ~
这里尝试基于 threejs 实现一个空中飞车小游戏,可以通过键盘方向键控制小车的行驶和转向,经过前方多段不规则的路后到达终点。先看下需要实现哪些效果:
- 首先整体场景是在半空中悬浮的,需要做个天空背景
- 然后要实现几段悬浮的倾斜度不一样的道路
- 第二段道路中间设定一些道路是左右移动的,稍微增加点游戏难度
- 玩家可以根据方向键控制小车方向
- 小车和道路的碰撞处理。这部分逻辑沿用前文的小车碰撞处理逻辑,继续用 cannon-es 这个库,以下代码也是基于前文的代码,所以物理库的相关逻辑请参照前文或代码注释,这篇文章不会赘述
还是拿之前自己实现的小车来当主角吧 ~
天空背景
其实做个天空盒子就可以了,盒子就是一个比较大的正方体 BoxGeometry
,给六面材质都加上贴图来模拟天空,然后将场景包裹在里面。这里简单点就用一张浅蓝色背景的图,如果还想做的更好点,那就得准备 6 张不同的图,图里加一些云彩和渐变色
// 创建天空盒子
const skyGeometry = new THREE.BoxGeometry(1000, 1000, 1000);
const materialArray = [];
for (let i = 0; i < 6; i++)
materialArray.push(
new THREE.MeshBasicMaterial({
// 加载贴图
map: textureLoader.load("./gta/sky.jpg"),
// 只渲染背面就行
side: THREE.BackSide,
})
);
const skyBox = new THREE.Mesh(skyGeometry, materialArray);
scene.add(skyBox);
制作道路
一段道路其实就是一个平面多边形,可以用 PlaneGeometry
实现,实现代码参考:
// 加载道路的纹理贴图
function texturePromise() {
return new Promise((resolve, reject) => {
textureLoader.load("/gta/floor.jpg", (texture) => {
resolve(texture);
});
});
}
// 第一段道路
const planeGeometry = new THREE.PlaneGeometry(10, 50);
// 设置道路材质
const texture: any = await texturePromise();
const planeMaterial = new THREE.MeshLambertMaterial({
map: texture,
// 渲染两面,因为道路是悬浮的,位置有高低,相机可能会观察到俩面
side: THREE.DoubleSide,
});
const plane = new THREE.Mesh(planeGeometry, planeMaterial);
// 接收阴影
plane.receiveShadow = true;
// 设置倾斜度
plane.rotation.x = Math.PI / 2;
// 增加到3d场景里
scene.add(plane);
然后咱们参考上述的代码多实现几段道路,分别赋予不同的位置(position)、长宽和倾斜度(rotation)
接下来就是创建每段道路的刚体,下面以一段道路为例:
import * as CANNON from "cannon-es";
// ...
// 第一段刚体
const q = plane.quaternion;
// 类比3d场景的Mesh
const roadShape = new CANNON.Box(new CANNON.Vec3(5, 25, 0.01));
// 类比3d场景的Geometry
const roadBody = new CANNON.Body({ mass: 0 });
roadBody.addShape(roadShape);
// 刚体位置和3d对象对齐
roadBody.position.set(0, 0, 0);
// 倾斜度和3d对象对齐
roadBody.quaternion = new CANNON.Quaternion(q._x, q._y, q._z, q._w);
// 添加到物理世界里
this.world.addBody(roadBody);
其他道路刚体的实现类似。ok,接下来调整下相机参数,使其在运动的时候跟随自车:
const cameraOffsetY = 4;
const cameraOffsetZ = 16;
// ...
/***** 创建一个具有透视效果的摄像机 *****/
const camera = new THREE.PerspectiveCamera(45, width / height, 0.1, 800);
const animate = () => {
// ...
// 相机跟随自车
camera.position.y = egoCar.position.y + cameraOffsetY;
camera.position.z = egoCar.position.z + cameraOffsetZ;
camera.lookAt(egoCar.position);
renderer.render(scene, camera);
// ...
};
然后第二段道路再加点倾斜度,这样两段路的效果如下图:
然后第二段道路设计成有几段小道路组成,穿插有左右移动的动画,怎么做这个动画呢?可以借助 tween.js 做个平滑动画。tween.js 其实是一个补间动画库,提供了丰富的缓动函数,可以确保在两个值之间平滑地过渡,经常配合 threejs 来实现一些动画效果,比如物体平移和旋转、镜头推进等效果
npm install @tweenjs/tween.js
第二段道路再分出几段小路做左右平移的动画,需要设置对应的动画参数,其中一段道路的代码如下:
import * as TWEEN from "@tweenjs/tween.js";
// ...
const tweenRoad1Start = new TWEEN.Tween(plane21.position)
// 在1s内向x为-2的位置移动
.to({ x: -2 }, 1000)
// 延迟动画
.delay(500)
// 重复动画的次数
.repeat(Infinity)
// 也就是在初始位置和目标位置来回运动
.yoyo(true)
// 更新回调
.onUpdate((data) => {
// 更新刚体数据
roadBody21.position.x = data.x;
})
// 启动动画
.start();
// ...循环里更新动画
const animate = () => {
// ...
updatePhysics();
// 更新tween动画
TWEEN.update();
// ...
};
其他几段道路做动画的逻辑类似,最终效果如图:
ok,到这里就可以自己开一段路试试了。掉下去或者想重新开始,自己刷新下界面,懒得做边界逻辑…forgive me。具体的自车控制逻辑,可以参考前文或者源码 ~
- 体验地址(仅支持 pc 端,通过方向键控制行驶和转向)
最后
当然,这一版实现还是很简单的,完全可以加些更复杂的场景元素或者障碍物,实现飞车 2.0?