背景
由于笔者最近在研究图形学Three.js相关的知识,也是想自己实现一个有趣且有一丢丢难度的demo,所以就决定做一个地铁跑酷的一个游戏,之前也搜过一些资料也没人做过类似地铁跑酷的游戏,所以可以简单的算是全网第一个地铁跑酷网页版游戏了
仓库地址:https://github.com/DanielLin0516/SUBWAY-SURFERS
游戏体验地址:https://subway-surfers-threejs.vercel.app/
玩法介绍
目前只有一些基本玩法,见下图:
代码结构介绍
主要核心代码还是在controlPlayer
模块,大量的碰撞检测、游戏判定等都在这个里面,如果要看实现逻辑就看这个就好啦
核心技术实现
无限地图
首先我们需要把我们的环境分组,好让我们能够一组一组的进行添加场景,我是这样进行划分的,下面的蓝框则为一整组场景:
我们可以发现在地铁跑酷过程当中,角色不倒下那么地图也是永无止境的,那么这个“永无止境”的地图是怎么实现的呢?
方案其实有两个:
- 让角色动,去动态添加地图
- 让角色原地跑动,动态添加障碍物
而在本项目实现方案是方案1,当我们人物在跑动的时候我们可以算出他跑动的距离
在角色没死的情况下,将每秒跑动的距离相加,就能获取到总跑动路程:
const moveZ = this.runVelocity * delta;
if (!this.frontCollide) {
if (this.status !== playerStatus.DIE) {
this.playerRunDistance += moveZ;
this.model.position.z -= moveZ;
}
}
在我们获取到角色跑动的路程和每个场景的地板长度,那么就可以很轻松获取到角色占当前板块的百分比 0-100%,在我们人物跑到当前板块的45%距离时候添加下一组板块,这样就形成了无线地图:
注意:添加完新的场景,记得把跑过的板块销毁,不然会随着人物跑动越远板块也无法销毁,内存增加造成掉帧卡死的情况,在本项目还没做这部分,后面会进行完善
// 检查玩家距离
checkPlayerDistance() {
const ds = this.playerRunDistance;
// 当前所在的地板块
const nowPlane = Math.floor(ds / roadLength) + 1;
// 当前走的路程站总长度的百分比
// 当到达45%的时候动态添加场景 无限地图
const runToLength = (ds - roadLength * (nowPlane - 1)) / roadLength;
if (runToLength > 0.45 && this.currentPlane !== nowPlane) {
console.log('添加下一个地板');
this.currentPlane = nowPlane;
this.environement.z -= roadLength;
const newZ = this.environement.z;
// 放置在z轴方向上
this.environement.setGroupScene(newZ, -5 - nowPlane * roadLength, false);
}
}
碰撞检测
在本项目里其实最复杂的就是碰撞检测,可能有很多种case,例如:左右碰撞火车回弹、正面大面积碰撞火车、小部分碰撞障碍物(不会终止游戏)等,也尝试过以下碰撞检测方案,最终还是选择了射线碰撞检测,以下说明原因以及实现细节
- 八叉树碰撞 ❌
- 射线碰撞 ✅
- 包围盒碰撞 ❌
在方案三包围盒
由于人物是骨骼动画会导致包围盒大小获取错误,只能自己给固定大小,也没太深入钻研包围盒碰撞检测的一些方法
项目刚开始选择的是八叉树碰撞,具体八叉树原理这里就不仔细讲解了,在使用的过程中发现Threejs提供给我们暴露出的八叉树碰撞api有点鸡肋,他只能将胶囊体赋值给人物角色,并将胶囊体碰撞检测结果给角色去做对应的操作,并且缺少我们需要的一些功能,当然我们也可以进行魔改八叉树的逻辑但这样往往时间成本以及上手成本会大大提升,例如:
- 碰到障碍物,获取障碍物信息
- 若八叉树