当前位置: 首页 > Web前端 > HTML

Three.js系列:写一个第一-第三人称视角游戏

时间:2023-03-28 13:50:50 HTML

大家好,我是秋风。在上一篇文章中,我提到了Three.js系列和神奇宝贝游戏的目标,所以今天我将通过Three.js。js说说游戏中的透视跟随问题。相信各位读者都或多或少地玩过一些游戏,比如王者荣耀、绝地求生、宝可梦、塞尔达、原神等游戏。那么你知道他们在游戏中是什么样的视角吗?你知道第一人称视角和第三人称视角的区别吗?我们如何通过代码实现这样的效果呢?如果您对以上问题感到好奇而无法完全回答。所以请跟我往下看。视角讲解首先,我们来了解一下第一人称视角和第三人称视角的概念。其实第一人称和第三人称我们都很熟悉。第一个人用我们自己的声音讲故事。比如自传就是这样写的,第三人称是旁观者。比如在很多小说中,都是和他(xxx)一起展开的,观众是站在上帝的角度来看整个故事的。对应的第一人称视角和第三人称视角也是同一个概念,只不过是视觉上的而已。那么它们之间有什么区别呢?第一人称视角的优势在于能够带给玩家最大的沉浸感。以“我”的第一人称视角观察场景和画面,让玩家更细心地感受细节。最常见的类似绝地求生,极品飞车什么的。而且第一人称视角也有其局限性。玩家的视野是有限的,看不到更广阔的视野。还有一个就是第一人称视角会给玩家带来“3D眩晕感”。当反应速度不如相机速度快时,就会引起头晕。第三人称视角呢?他的优点是自由,视野开阔,角色移动和视角是分开的,一个用来控制角色的方向,一个用来控制视线的方向。它的缺点是不能很好地聚焦局部,容易遗漏细节。但总的来说,目前大多数游戏都提供了两种视角的切换,以满足不同的情况。比如在《绝地求生》中,你通常在行走时使用第三人称视角跟随移动,而在射击时一般使用第一人称视角。好了,至此我们已经知道了第一人称视角和第三人称视角的概念和区别。那我们就以第三人称视角为例,分析一下如何才能达到这样的效果呢?(第三人称代词写完后稍加修改就可以改成第一人称代词,所以以比较复杂的第三人称代词为例)把一只大象放进去需要几步冰箱?三步!打开冰箱,把大象放进冰箱,关上冰箱。显然,把一头大象放进冰箱是非常困难的,但从宏观上看,就是三步走。因此,我们也将实现第三人称视角的功能分为三步:stepsplit下面的stepsplit不会包含任何代码,请放心使用:1、人物移动我们都知道。在物理现实世界中,我们走动靠的是双腿,迈出一步就是走动。那么从更宏观的角度来看,这个过程是什么样的呢?其实,如果我们站在地球之外,从更远的角度来看,我们的运动更像是平移变化。同样,我们使用平移变化来表示计算机中的运动。之前翻译改动的细节大家都很熟悉了。如果你现在不熟悉,也没关系。我们先看看下面的坐标轴。(小方块的边长为1)小方块从位置A1到位置A2的移动是平移变化。如果用数学表达式来表达,上面的是什么意思呢?也就是说,我们将小方块中所有小点的x值加2,而y值不变。我们随便取一些值来验证一下。比如A1处的小方块,左下角是(0,0),通过上面的变化变成了(2,0),我们看A2处小方块的新位置是(2,0);然后用右上角的(1,1)代入,结果是(3,1),和我们实际移动到的位置是一样的。所以上面的公式没有错。但是后来大家觉得像上面这样的公式有点不太通用。至于这里为什么不够通用,我会在后面的系列文章中详细说明,因为涉及到其他的变化,比如旋转,缩放,都可以用一个矩阵来描述,所以如果平移也可以用矩阵的形式表示,那么整个问题就变得简单了,也就是说:运动变化=矩阵变化我们来看看把初始公式变成矩阵是什么样子的:你可以简单解释一下矩阵是如何变化的右边来自左上角的这部分称为单位矩阵,后面的2个0就是我们需要的平移变化。至于为什么从2维变成3维,是因为引入了齐次矩阵的概念。同样的原理,类比3维,我们需要用到4维矩阵。所以,通过一系列的例子,我们最终想得出一个结论,所有的运动都是矩阵变化。2、镜头正对着人物我们都知道,在现实世界中,我们眼睛的视野是有限的,在电脑中也是一样。假设我们在电脑中的视野是一个3*3的正方形,我们还是以之前的坐标轴为例,黄色区域是我们视野的可见区域:现在我们让小方块向右移动3个单位,然后在直线单位上移动1。这时,我们会发现这个小块已经不在我们的视野中了。试想一下,我们正在玩一款射击游戏,敌人就在我们眼前移动,我们要怎么做才能找到它呢?是的,我们转动头部,使敌人暴露在我们的视野中。就像这样:这会锁定敌人,使角色保持在视线范围内并保持相对静止。3.镜头与人物的距离相同。镜头对着人物是不够的。我们还必须让我们的镜头和角色保持相同的距离。为什么这么说呢,首先我们还是以我们的坐标轴为例,只不过这次我们要展开一个z轴:然后我们看法平面截图screenshot:现在我们将我们的小块移动到——Z增加1个单位:screenshot:这个时候我们发现小方块变小了,随着小方块往-z方向移动的越多,我们看到的小方块就会越来越小。这时候我们显然没有改变视角,但是还是不能很好的跟踪小块。因此,我们需要移动我们的视角位置。当我们看不清远处的路标时,我们该怎么办?是的,靠近点!截图:完美!下面通过三个方向的讲解,让我们从理论上实现实现第三人称视角的功能!做好代码之后,我们只需要按照我们上面的理论来实现代码即可。代码无法用另一种语言实现。知道原理就很简单了。1.初始化画布场景...场景、相机、渲染器是相对固定的东西。本节不主要对它们进行解释。可以理解为我们项目初始化的时候一些必要的语句。这时候我们打开页面,是一片漆黑一片。为了美观,我给整个场景加了一层地板。//设置地板constgeometry=newTHREE.PlaneGeometry(1000,1000,1,1);//地板纹理constfloorTexture=newTHREE.ImageUtils.loadTexture('12.jpeg');floorTexture.wrapS=floorTexture.wrapT=THREE.RepeatWrapping;floorTexture.repeat.set(10,10);//地板材质constfloorMaterial=newTHREE.MeshBasicMaterial({map:floorTexture,side:THREE.DoubleSide});constfloor=newTHREE.Mesh(geometryfloorMaterial);//设置楼层位置floor.position.y=-1.5;floor.rotation.x=-Math.PI/2;scene.add(楼层);`![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/aa8cea3705594d10a7e01e7225283a78~tplv-k3u1fbpfcp-zoom-1.image)此时的画面还不错\~2.角色移动按照理论,我们需要添加一个角色,这里为了方便,还是添加一个小方块为主://smallsliderconstboxgeometry=newTHREE.BoxGeometry(1,1,1);constboxMaterials=[];for(leti=0;i<6;i++){constboxMaterial=newTHREE.MeshBasicMaterial({color:Math.random()*0xffffff,});}boxMaterials.push(boxMaterial);}//小块constbox=newTHREE.Mesh(boxgeometry,boxMaterials);box.position.y=1;box.position.z=8;场景。添加(框);为了好看,我在小块里加了六个虽然不同颜色的面条看起来有点简陋,但俗话说,高级的食材往往只需要最简单的烹饪方法。小块虽小,却五脏六腑一应俱全。现在我们已经渲染了小块,接下来要做的就是绑定快捷键。对应的代码://控制代码constkeyboard=newTHREEx.KeyboardState();constclock=newTHREE.Clock();consttick=()=>{constdelta=clock.getDelta();constmoveDistance=5*delta;constrotateAngle=Math.PI/2*delta;if(keyboard.pressed("down"))box.translateZ(moveDistance);if(keyboard.pressed("up"))box.translateZ(-moveDistance);如果(keyboard.pressed("left"))box.translateX(-moveDistance);if(keyboard.pressed("right"))box.translateX(moveDistance);if(keyboard.pressed("w"))box.rotateOnAxis(newTHREE.Vector3(1,0,0),rotateAngle);if(keyboard.pressed("s"))box.rotateOnAxis(newTHREE.Vector3(1,0,0),-rotateAngle);if(keyboard.pressed("a"))box.rotateOnAxis(newTHREE.Vector3(0,1,0),rotateAngle);如果(键盘。按下(“d”))box.rotateOnAxis(newTHREE.Vector3(0,1,0),-rotateAngle);renderer.render(scene,camera)window.requestAnimationFrame(tick)}tick();这里解释一下translateZ,translateX,这两个函数就是字面意思,移动到z轴和x轴。如果要向前移动,请移动到-z轴。如果要向左移动,请移动到-x轴。clock.getDelta()是什么意思?简单的说,.getDelta()方法的作用就是获取方法两次执行的时间间隔。比如我们想在1秒内向前移动5个单位,但是直接移动肯定比较生硬,所以我们要加动画。我们知道,为了实现流畅的动画效果,一般都是通过浏览器的APIrequestAnimationFrame来实现的。浏览器将控制渲染频率。一般理想性能下,每秒渲染60次左右。在实际项目中,如果需要渲染的场景比较复杂,一般低于60,即两个渲染帧之间的时间间隔大于16.67ms。所以为了移动这5个单位,我们把每一帧的移动距离拆分成这60张效果图。最后说说rotateOnAxios,主要用来控制小盒子的旋转。.rotateOnWorldAxis(axis:Vector3,angle:Float):这个轴——世界空间中的归一化向量。angle--角度,以弧度表示。3.相机与人的同步回顾理论部分,我们最后一步是让相机(人眼)和物体保持相对静止,即距离不变。consttick=()=>{...constrelativeCameraOffset=newTHREE.Vector3(0,5,10);constcameraOffset=relativeCameraOffset.applyMatrix4(box.matrixWorld);amcamera.position.x=x.cameraOffset。position.y=cameraOffset.y;camera.position.z=cameraOffset.z;//始终让相机注视对象controls.target=box.position;...}这里还有一个比较核心的点就是relativeCameraOffset.applyMatrix4(box.matrixWorld);其实我们在理论部分已经说过了,因为我们物体运动的底层原理是做矩阵变化,所以如果我们想保持相机(人眼)和物体的距离不变,我们只需要让相机(人眼)和物体做同样的变化。在Three.js中,对象的所有变化都记录在.matrix中。只要外部场景不变,.matrixWorld就等于.matrix。applyMatrix4表示相乘。效果演示让我终于实现了整个功能!下次见!源码地址:https://github.com/hua1995116...结语??关注+点赞+收藏+评论+转发??,原创不易,鼓励作者创作更好的文章关注公众号秋风的笔记,一个专注于前端面试,工程,开源前端公众号