帝游网提供最新手游APP下载和游戏攻略!

用Three.js构建你的赛车梦想

发布时间:2024-08-28浏览:14

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

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

以下是操作方法: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);}}

用户评论

将妓就计

看起来很有趣!我想知道游戏里有哪些赛道选择呢?

    有11位网友表示赞同!

玻璃渣子

我一直对 使用 Three.js 制作游戏的想法很着迷,这绝对是个好主意!

    有11位网友表示赞同!

陌颜幽梦

这个游戏能支持多人同屏竞技吗?我很想和朋友们展开一场刺激的比赛。

    有13位网友表示赞同!

未来未必来

简单赛车游戏玩起来应该不会太费脑子,适合休闲娱乐!

    有5位网友表示赞同!

全网暗恋者

我很好奇游戏的操控方式会是怎样的?键位还是鼠标操作?

    有8位网友表示赞同!

见朕骑妓的时刻

希望游戏能加入一些炫酷特效和音效,这样会更加吸引人!

    有11位网友表示赞同!

厌归人

制作赛车游戏的难度应该不会很高吧?我也有兴趣试试自己动手开发一个。

    有15位网友表示赞同!

花开丶若相惜

这个项目看起来很有学习价值,可以让我更好地了解 Three.js 的应用。

    有14位网友表示赞同!

情字何解ヘ

真希望能看到游戏里各种各样的赛车模型,比如经典跑车、肌肉车等等。

    有20位网友表示赞同!

红尘烟雨

如果能添加一些障碍物和道具会更有趣吧!

    有13位网友表示赞同!

我的黑色迷你裙

我觉得这个游戏的目标人群应该就是想学习 Three.js 编程的玩家。

    有14位网友表示赞同!

命硬

简单难度游戏玩起来也挺好的,可以放松一下心情。

    有20位网友表示赞同!

孤独症

期待看到成品的游戏演示视频!

    有13位网友表示赞同!

闷骚闷出味道了

希望这个游戏能运行流畅,没有卡顿的问题。

    有10位网友表示赞同!

从此我爱的人都像你

一个好玩的赛车游戏中不能少场景的变化吧?不同的赛道风格会更丰富。

    有8位网友表示赞同!

有些人,只适合好奇~

这类的简单游戏适合碎片时间玩,不用投入太多时间精力。

    有10位网友表示赞同!

满心狼藉

如果支持自定义车辆的话那就更好了!可以发挥自己的创意。

    有20位网友表示赞同!

青衫故人

看起来这个项目非常适合初学者尝试 Three.js 的学习过程。

    有7位网友表示赞同!

安好如初

希望能看到更多类似的游戏作品,开发者的创作力令人佩服!

    有10位网友表示赞同!

热点资讯