首页 » 排名链接 » 用Threejs创建一个简单的赛车游戏(汽车赛车游戏旋转创建一个漂移)

用Threejs创建一个简单的赛车游戏(汽车赛车游戏旋转创建一个漂移)

萌界大人物 2024-07-24 17:16:02 0

扫一扫用手机浏览

文章目录 [+]

有一天,我看到汽车在路上超速驶过,我想制作一个赛车游戏。

我没有使用原生,而是使用threejs。
毕竟,对于较大的3D项目,如果我继续使用原生,我会给自己带来麻烦......

本文解释了这个游戏从0到1的开发过程。
没有关于webgl和threejs的特别介绍。
没有基础的学生可以和threejs文档一起阅读,或者先学习webgl的基本知识~

用Threejs创建一个简单的赛车游戏(汽车赛车游戏旋转创建一个漂移) 排名链接
(图片来自网络侵删)

以下是操作方法:w,前进a,d左转和右转空间,减速,可以漂移

目前,游戏的碰撞检测尚未完成(未来将进行更新和改进),只有汽车左侧和赛道两侧将进行碰撞测试。
细节将在下面提及~你也可以通过自己尝试来找出哪两面。

接下来,我们将从0到1实施这个赛车游戏~

1.游戏准备

首先,我们必须选择制作什么游戏。
如果这是一个公司级的游戏项目,那么在开发方面基本上别无选择。
如果你自己练习,你可以根据自己的喜好来练习。
我选择赛车的原因就是一个例子:

首先,这是因为赛车游戏相对简单,不需要太多材料。
毕竟,这是一个个人发展,没有专门的设计来提供模型。
你必须自己找到模型。

其次,赛车游戏的成本是简单而闭环的。
有了汽车和赛道,这实际上是最简单的游戏。

所以我们最终决定制作一个简单的赛车游戏。
接下来,我们必须寻找材料。

2.材料准备

我在网上搜索了很长时间,发现了一个好的汽车obj文件。
它有纹理等,但有些颜色还没有添加。
我用搅拌机来完成它。

现在我们有了汽车材料,下一步就是赛道。
轨道的最早想法是动态生成它,类似于之前的迷宫游戏。

正式的赛车游戏绝对不能动态生成,因为赛道需要定制,并有许多细节,如纹理风景等。

我们的实践项目不可能这么酷,所以我们可以考虑动态生成。

动态生成的优点是,每次刷新时,您都会播放一张新地图,这可能会更新鲜。

还有两种动态生成方法。
一种是使用一块板连续地将其平铺,而一块板的顶点信息是[-1,0,1, 1,0,1, 1,0,-1, -1,0,-1]。

从顶部视图来看,它看起来像这样:

但这个有一个非常糟糕的事情,那就是曲线太粗糙,每条曲线都是直角的,这不是很好看。
只是改变一个计划Obj构建了两个模型,即直路和转弯,如图所示。

然后,这两个模型不断被平铺。
在2D中,它看起来像这样。

看起来这是可能的,但是!
在实际实施后,我发现它仍然不好!

首先,赛道上没有回头路,因为我们的y轴是固定的,没有上坡或下坡的概念。
一旦轨道转弯,新道路与现有道路相遇,它就会变得混乱,成为道路的分叉。

其次,必须对随机性进行大量控制,否则可能会有太频繁的角落,如图所示。

在兼容一段时间后,我发现它真的搞砸了,所以我决定自己建立一个赛道模型,自己有足够的食物和衣服,如图所示。

再说一遍,搅拌机非常有用~

在设计这里的赛道时,有一个角落太难设计了。
不减速就不可能在拐角处谈判......我相信你肯定可以通过尝试一圈就能知道它是哪个角落~

3.Threejs

准备工作已经完成,下一步是编写代码

我不知道你是否还记得之前的原生webgl开发。
这很麻烦,对吧?这次我们使用了threejs,这要方便得多。
然而,我仍然不得不说,在联系threejs之前,建议你熟悉原生webgl,否则可能会有很多依赖性,并且图形的一些基础将不扎实。

我们的第一步是创造整个场景世界

var scene = new THREE.Scene();var camera = new THREE.PerspectiveCamera(90,window.innerWidth/window.innerHeight,0.1,1000);camera.position.z = 0;camera.position.x = 0;var webGLRenderer = new THREE.WebGLRenderer();webGLRenderer.setPixelRatio(window.devicePixelRatio);webGLRenderer.setSize(window.innerWidth,window.innerHeight);webGLRenderer.setClearColor(0x0077ec,1);

这些是使用threejs所必需的。
这比自己创建程序、着色器以及各种编译和绑定要方便得多。

接下来,我们需要导入模型。
上次我写了一个简单的objLoader,这次我们使用threejs附带的那个。

var mtlLoader = new THREE.MTLLoader();mtlLoader.setPath('./assets/');mtlLoader.load('car4.mtl', function(materials) {materials.preload(); var objLoader = new THREE.OBJLoader();objLoader.set材料(材料);objLoader.setPath('./assets/');objLoader.load('car4.obj', function(object) {汽车=物体;car.children.forEach(function(item) {item.castShadow = true;});car.position.z = -20;car.position.y = -5;params.scene.add(汽车);self.car = 汽车;params.cb();},函数(){ console.log('progress');},函数(){ console.log('错误');});});

首先加载mtl文件,生成材料,然后加载obj文件,这非常方便。
请注意,在将汽车添加到场景中后,我们需要调整位置。
我们世界地面的y轴坐标是-5。

从之前的代码中可以看出,相机的起始z坐标为0,我们最初将汽车的z坐标设置为-20。

以同样的方式,再次导入轨道文件。
如果我们此时访问它,我们会发现它完全黑暗,如图所示。

为什么会这样?

上帝说,让光明!

轨道和汽车本身没有颜色,因此需要材料和灯光来创造颜色。
在原生webgl中创建灯光也很麻烦,需要编写着色器。
Threejs非常方便。
我们只需要以下代码:

var dirLight = new THREE.DirectionalLight(0xccbbaa,0.5,100);dirLight.position.set(-120,500,-0);dirLight.castShadow = true;dirLight.shadow.mapSize.width = 1000; // 默认dirLight.shadow.mapSize.height = 1000; // 默认dirLight.shadow.camera.near = 2;dirLight.shadow.camera.far = 1000;dirLight.shadow.camera.left = -50;dirLight.shadow.camera.right = 50;dirLight.shadow.camera.top = 50;dirLight.shadow.camera.bottom = -50;scene.add(dirLight);var light = new THREE.AmbientLight( 0xccbbaa, 0.1 );scene.add(光);

刷新它,我们的整个世界将变得更光明!
(请注意,在这里我们使用环境光+平行光。
我们稍后会把它改成其他灯,原因也会给出),但缺少什么吗?对!
仍然缺少阴影。

但让我们在下一节中谈谈阴影,因为这里阴影的处理并不像光那么简单。

抛开阴影,我们可以理解,一个静态的世界已经完成,有汽车和轨道。

document.body.addEventListener('keydown', function(e) { switch(e.keyCode){ 案例87:// wcar.run = true; 休息; 案例65:// acar.rSpeed = 0.02; 休息; 案例68:// dcar.rSpeed = -0.02; 休息; 案例32://空间car.brake(); 休息;}});document.body.addEventListener('keyup', function(e) { switch(e.keyCode){ 案例87:// wcar.run = false; 休息; 案例65:// acar.rSpeed = 0; 休息; 案例68:// dcar.rSpeed = 0; 休息; 案例32://空间car.cancelBrake(); 休息;}});

我们不使用任何与键盘事件相关的库,我们只是自己写几个键。
代码应该仍然易于理解。

按w意味着踩油门,汽车的运行属性设置为true,加速将发生在刻度中;同样,按a会修改rSpeed,汽车的旋转将在刻度中发生变化。

如果(this.run){ this.speed += this.acceleration; if(this.speed > this.maxSpeed) { this.speed = this.maxSpeed;}} 否则 { this.speed -= this.deceleration; if(this.speed < 0) { this.speed = 0;}}var speed = -this.speed;如果(速度 === 0){ 返回;}var rotation = this.dirRotation += this.rSpeed;var speedX = Math.sin(旋转)速度;var speedZ = Math.cos(旋转)速度;this.car.rotation.y = 旋转;this.car.position.z += speedZ;this.car.position.x += speedX;

这非常方便。
用一些数学计算来修改汽车的旋转和位置是可以的。
这比在原生webgl本身中实现各种转换矩阵要方便得多。
然而,您必须知道,threejs的底层仍然通过矩阵改变。

为了简要总结本节,我们使用threejs来完成整个世界的布局,然后使用键盘事件来使汽车移动,但我们仍然缺少很多东西。

4.特点和功能

本节主要讨论threejs无法实现或不能由threejs轻松实现的函数。
让我们先总结一下第三季度后我们仍然缺乏的能力。
a.相机关注b.轮胎详情c.影子d.碰撞检测e.漂移我们一个接一个地去吧。

相机关注

刚才我们成功地让汽车移动了,但我们的视角没有移动,汽车似乎正在逐渐远离我们。
透视由相机控制。
之前我们创建了一个相机,现在我们希望它跟随汽车的运动。
相机和汽车之间的关系如下图所示。

相机的旋转对应于汽车的旋转,但无论汽车是转动(旋转)还是移动(位置),它也必须改变相机的位置!
这份信件需要澄清。

camera.rotation.y = 旋转;camera.position.x = this.car.position.x + Math.sin(旋转) 20;camera.position.z = this.car.position.z + Math.cos(旋转) 20;

在汽车的刻度法中,相机的位置是根据汽车本身的位置和旋转计算的。
20是汽车不旋转时相机与汽车之间的距离(如第3节开头所述)。
最好将代码与上图一起理解。
这使得相机能够跟随。

轮胎详情

轮胎细节需要体验偏航角度的真实性。
如果你不知道偏航角度也没关系,只需将其理解为漂移的真实性,如下所示。

事实上,当正常转弯时,轮胎应该先走,车身应该走第二走,但由于透视问题,我们在这里省略了它。

这里的核心是车身方向和轮胎方向之间的不一致。
但诀窍来了。
threejs的旋转相对僵化。
它不能指定任何旋转轴。
要么使用 rotation.xyz 旋转坐标轴,要么使用 rotateOnAxis 选择穿过原点旋转的轴。
因此,我们只能与车辆一起旋转轮胎,但不能自行旋转。
如图所示。

然后,如果我们想旋转,我们首先需要单独提取轮胎模型,它看起来像这样,如图所示。

然后我们发现轮换没问题,但汽车轮换消失了......然后我们必须建立父母关系。
汽车的旋转由父母完成,旋转由轮胎自己完成。

mtlLoader.setPath('./assets/');mtlLoader.load(params.mtl, function(materials) {materials.preload(); var objLoader = new THREE.OBJLoader();objLoader.set材料(材料);objLoader.setPath('./assets/');objLoader.load(params.obj, function(object) {object.children.forEach(function(item) {item.castShadow = true;}); var wrapper = new THREE.Object3D();wrapper.position.set(0,-5,-20);wrapper.add(对象);object.position.set(params.offsetX,0,params.offsetZ);scene.add(包装);self.wheel = 对象;self.wrapper = wrapper;},函数(){ console.log('progress');},函数(){ console.log('错误');});});......this.frontLeftWheel.wrapper.rotation.y = this.realRotation;this.frontRightWheel.wrapper.rotation.y = this.realRotation;this.frontLeftWheel.wheel.rotation.y = (this.dirRotation - this.realRotation) / 2;this.frontRightWheel.wheel.rotation.y = (this.dirRotation - this.realRotation) / 2;

图片的演示是这样的:

影子

我们之前跳过阴影,说它们不像光那么简单。
事实上,shadow在threejs中实现,这比webgl的原生实现更简单几个级别。
让我们看看threejs中阴影的实现。
这需要三个步骤。
1.光源计算阴影2.对象计算阴影3.物体带有阴影这三个步骤可以使阴影出现在你的场景中。

dirLight.castShadow = true;dirLight.shadow.mapSize.width = 1000;dirLight.shadow.mapSize.height = 1000;dirLight.shadow.camera.near = 2;dirLight.shadow.camera.far = 1000;dirLight.shadow.camera.left = -50;dirLight.shadow.camera.right = 50;dirLight.shadow.camera.top = 50;dirLight.shadow.camera.bottom = -50;......objLoader.load('car4.obj', function(object) {汽车=物体;car.children.forEach(function(item) {item.castShadow = true;});......objLoader.load('ground.obj', function(object) {object.children.forEach(function(item) {item.receiveShadow = true;});

但是!
我们这里有的是动态阴影,可以理解为整个场景在不断变化。
这样,threejs中的阴影就更麻烦了,需要一些额外的处理。

首先,我们知道我们的光是平行光。
平行光可以被视为阳光,覆盖了整个场景。
但阴影不起作用。
阴影需要通过正交矩阵来计算!
然后问题来了。
我们的整个场景都非常大。
如果您想用正交矩阵覆盖整个场景,您的帧缓冲图像也会非常大,否则阴影将非常不现实。
事实上,没有必要考虑这一步,因为帧缓冲图像根本不能那么大,而且肯定会卡住。
该怎么办?我们必须动态地改变正交矩阵!

var tempX = this.car.position.x + speedX;var tempZ = this.car.position.z + speedZ;this.light.shadow.camera.left = (tempZ-50+20) >> 0;this.light.shadow.camera.right = (tempZ+50+20) >> 0;this.light.shadow.camera.top = (tempX+50) >> 0;this.light.shadow.camera.bottom = (tempX-50) >> 0;this.light.position.set(-120+tempX,500,tempZ);this.light.shadow.camera.updateProjectionMatrix();

我们只考虑了汽车在地面上的阴影,所以正交矩阵只能确保它能完全容纳汽车。
墙壁没有被考虑。
事实上,根据完美,墙壁也应该有阴影。
正交矩阵需要放大。

threejs中没有平行光的镜面反射效果,整辆车也不够生动,所以我试着把平行光换成点光源(感觉像路灯?),然后让点光源一直跟着汽车。

var pointLight = new THREE.PointLight(0xccbbaa, 1, 0, 0);pointLight.position.set(-10,20,-20);pointLight.castShadow = true;scene.add(点光);......this.light.position.set(-10+tempX,20,tempZ);this.light.shadow.camera.updateProjectionMatrix();

总的来说,这看起来好多了。
这就是改变之前提到的灯光类型的原因~

影响检查

我不知道你是否找到了哪些边缘有碰撞检测,但实际上是这些边缘~

红色边缘和汽车右侧之间有碰撞检测,但碰撞检测非常随机。
一旦它击中,它就被视为崩溃......速度直接设置为0,然后重新出现。

它确实很

为了检测汽车和轨道之间的碰撞,我们首先必须将3D转换为2D才能看到它,因为我们这里没有任何上坡或下坡的障碍,这很简单。

二维碰撞,我们可以检测汽车的左侧和右侧以及障碍物的侧面。

首先,我们有轨道的二维数据,然后动态获取汽车的左侧和右侧进行检查。

var tempA = -(this.car.rotation.y + 0.523);this.leftFront.x = Math.sin(tempA) 8 + tempX;this.leftFront.y = Math.cos(tempA) 8 + tempZ;tempA = -(this.car.rotation.y + 2.616);this.leftBack.x = Math.sin(tempA) 8 + tempX;this.leftBack.y = Math.cos(tempA) 8 + tempZ;......Car.prototype.physical = function() { var i = 0; for(; i < outside.length; i += 4) { if(isLineSegmentIntr(this.leftFront, this.leftBack, { x:外面[i], y:外面[i+1]}, { x:外面[i+2], y:外面[i+3]})) { 返回i;}} 返回-1;};

这有点类似于相机的概念,但数学更麻烦一些。

对于线对线碰撞检测,我们使用三角形面积法,这是最快的线对线碰撞检测。

函数是LineSegmentIntr(a, b, c, d) { // console.log(a, b); var area_abc = (a.x - c.x) (b.y - c.y) - (a.y - c.y) (b.x - c.x); var area_abd = (a.x - d.x) (b.y - d.y) - (a.y - d.y) (b.x - d.x); if(area_abc area_abd > 0) { 返回false;} var area_cda = (c.x - a.x) (d.y - a.y) - (c.y - a.y) (d.x - a.x); var area_cdb = area_cda + area_abc - area_abd ; if(area_cda area_cdb > 0) { 返回false;} 返回真实;}}

他们见面后会发生什么?虽然我们没有完美的反馈,但我们应该有基本的反馈。
当我们将速度设置为0并重新出现时,我们必须正确重置汽车的方向,对吗?否则,玩家将不断碰撞......要重置方向,请使用汽车的原始方向矢量并将其投射到碰撞边缘。
生成的向量是重置方向。

函数getBounceVector(obj,w){ var len = Math.sqrt(w.vx w.vx + w.vy w.vy);w.dx = w.vx / len;w.dy = w.vy / len;w.rx = -w.dy;w.ry = w.dx;w.lx = w.dy;w.ly = -w.dx; var projw = getProjectVector(obj,w.dx,w.dy); var projn; var left = isLeft(w.p0, w.p1, obj.p0); 如果(左){projn = getProjectVector(obj,w.rx,w.ry);} 否则 {projn = getProjectVector(obj,w.lx,w.ly);}projn.vx = -0.5;projn.vy = -0.5; 返回{ vx:projw.vx + projn.vx, vy:projw.vy + projn.vy,};}函数getProjectVector(u,dx,dy){ var dp = u.vx dx + u.vy dy; 返回{ vx:(dp dx), vy:(dp dy)};}漂移

汽车不会漂移,就像打开一个在线游戏,发现网络电缆坏了。

我们不考虑哪一个更快,漂移还是正常的转弯。
有兴趣的学生可以去看看。
这很有趣。
让我先解释三个结论:1.漂移赛车游戏的核心部分之一(英俊),不可能不做。

2.漂移的核心是,它有更好的出口方向,而不会扭曲汽车前部(省略了其他优点和缺点,因为这在视觉上是最直观的)。

3.互联网上没有现成的漂移算法(无论统一性如何),因此我们需要模拟漂移。

对于模拟,我们首先需要了解漂移原理。
你还记得我们之前谈论的偏航角度吗?偏航角度是漂移的视觉体验。

更具体地说,偏航角度意味着当汽车的运动方向与汽车前部的方向不一致时,这种差异被称为偏航角度。
因此,我们的模拟漂移需要分两步完成:1.生成一个偏航角度,让玩家在视觉上感受到漂移。

2.离开角落的方向是正确的,让玩家感受到真实性。
玩家在漂移后不会觉得转弯更不舒服......

下面我们将模拟这两点。
事实上,一旦你知道目的,它仍然很容易模拟。

当产生偏航角度时,我们需要保持两个方向,一个是车身的真实旋转方向,真实旋转,另一个是汽车的真实运动方向,旋转(这就是相机遵循的!
)。

这两个值通常是相同的,但一旦用户按下空格键,它们就会开始改变。

var time = Date.now();this.dirRotation += this.rSpeed;this.realRotation += this.rSpeed;var rotation = this.dirRotation;如果(this.isBrake){ this.realRotation += this.rSpeed (this.speed / 2);}this.car.rotation.y = this.realRotation;this.frontLeftWheel.wrapper.rotation.y = this.realRotation;this.frontRightWheel.wrapper.rotation.y = this.realRotation;this.frontLeftWheel.wheel.rotation.y = (this.dirRotation - this.realRotation) / 2;this.frontRightWheel.wheel.rotation.y = (this.dirRotation - this.realRotation) / 2;camera.rotation.y = this.dirRotation;

此时,已经产生了偏航角度。

当用户释放空间时,两个方向必须开始统一。
此时,请记住dirRotation必须统一到realRotation,否则漂流出角落的意义将消失。

var time = Date.now();如果(this.isBrake){ this.realRotation += this.rSpeed (this.speed / 2);} 否则 { 如果(这个。
真正的旋转!
== this.dirRotation) { this.dirRotation += (this.realRotation - this.dirRotation) / 20000 (this.speed) (time - this.cancelBrakeTime);}}

标签:

相关文章