网站首页 > 编程文章 正文
有一天,我看到汽车在路上超速驶过,我想制作一个赛车游戏。
我没有使用原生,而是使用threejs。毕竟,对于较大的3D项目,如果我继续使用原生,我会给自己带来麻烦......
本文解释了这个游戏从0到1的开发过程。没有关于webgl和threejs的特别介绍。没有基础的学生可以和threejs文档一起阅读,或者先学习webgl的基本知识~
以下是操作方法:
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:// w
car.run = true;
休息;
案例65:// a
car.rSpeed = 0.02;
休息;
案例68:// d
car.rSpeed = -0.02;
休息;
案例32://空间
car.brake();
休息;
}
});
document.body.addEventListener('keyup', function(e) {
switch(e.keyCode){
案例87:// w
car.run = false;
休息;
案例65:// a
car.rSpeed = 0;
休息;
案例68:// d
car.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);
}
}
猜你喜欢
- 2024-10-24 啥叫完美?基于three.js的数字孪生设计
- 2024-10-24 threejs非同凡响|建模和大数据的完美融合
- 2024-10-24 Three.JS编程中如何切换gltf模型动画?
- 2024-10-24 Three.JS教程4 threejs中的辅助类
- 2024-10-24 THREEJS学习之路-镜头切换动画(threejs camera)
- 2024-10-24 Threejs技术助力IoT可视化大屏的创新展示方式
- 2024-10-24 threejs3d学习笔记3(threejs教程)
- 2024-10-24 three.js还是cesium.js ? 究其根本只是实现工具,思路才关键!
- 2024-10-24 threejs中,如何检测一个模型附近一定范围内的其它模型
- 2024-10-24 Threejs 与blender贴图 结合用法(threejs导入blender模型)
你 发表评论:
欢迎- 06-24一个老爸画了超级有爱的365幅画 | 父亲节献礼
- 06-24产品小白看魏则西事件——用产品思维审视百度推广
- 06-24某教程学习笔记(一):13、脚本木马原理
- 06-24十大常见web漏洞——命令执行漏洞
- 06-24初涉内网,提权那些事(内网渗透提权)
- 06-24黑客命令第16集:47种最常见的**网站方法2/2
- 06-24铭说 | 一句话木马的多种变形方式
- 06-24Java隐藏的10倍效率技巧!90%程序员不知道的魔法方法(附代码)
- 最近发表
- 标签列表
-
- spire.doc (70)
- instanceclient (62)
- solidworks (78)
- system.data.oracleclient (61)
- 按键小精灵源码提取 (66)
- pyqt5designer教程 (65)
- 联想刷bios工具 (66)
- c#源码 (64)
- graphics.h头文件 (62)
- mysqldump下载 (66)
- libmp3lame (60)
- maven3.3.9 (63)
- 二调符号库 (57)
- git.exe下载 (68)
- diskgenius_winpe (72)
- pythoncrc16 (57)
- solidworks宏文件下载 (59)
- qt帮助文档中文版 (73)
- satacontroller (66)
- hgcad (64)
- bootimg.exe (69)
- android-gif-drawable (62)
- axure9元件库免费下载 (57)
- libmysqlclient.so.18 (58)
- springbootdemo (64)
本文暂时没有评论,来添加一个吧(●'◡'●)