当前位置: 首页 > 科技观察

265行代码实现第一人称游戏引擎

时间:2023-03-13 02:22:08 科技观察

今天,让我们进入一个触手可及、触手可及的世界。在本文中,我们将从头开始进行快速的第一人称探索。本文不涉及复杂的数学计算,仅使用射线投射技术。您可能以前见过这种技术,例如《上古卷轴2 : 匕首雨》、《毁灭公爵3D》,以及NotchPersson最近在ludumdare上的条目。Notch觉得够好了,我觉得够好了![Demo(arrowkeys/touch)][Source]使用光线投射就像打开一个钩子。作为一个懒惰的程序员,我非常喜欢它。您可以舒适地沉浸在3D环境中,而不会被“真正的3D”的复杂性所束缚。例如,光线投射算法消耗线性时间,因此可以在不优化的情况下加载一个巨大的世界,并且它的执行速度与一个小世界一样快。关卡被定义为简单的网格而不是多边形网格树,因此即使没有3D建模背景或没有数学博士学位的人也可以直接学习。使用这些技巧很容易做一些令人惊奇的事情。15分钟后,您开始为办公室的墙壁拍照,并检查您的HR文档中是否存在反对“工作场所枪战建模”的规则。Player我们从哪里投射光线?这是玩家对象(Player)的角色,只需要三个属性x,y,方向。functionPlayer(x,y,direction){this.x=x;这个.y=y;this.direction=方向;}地图我们将地图存储为一个简单的二维数组。数组中,0表示没有墙,1表示有墙。您还可以做更复杂的事情,比如给墙一个任意高度,或者将墙数据的多个“故事”打包到一个数组中。但对于我们的第一次尝试,0-1就足够了。functionMap(size){this.size=size;this.wallGrid=newUint8Array(大小*大小);投射光线诀窍在于:光线投射引擎不会一次绘制整个场景。相反,它将场景分成单独的列并一一渲染。每列代表玩家以特定角度投射的光线。如果射线击中墙壁,引擎会计算玩家到墙壁的距离并在该列中绘制一个矩形。矩形的高度取决于光线的长度——距离越远,光线越短。您绘制的光线越多,它就会显得越平滑。1.找到每条光线的角度我们首先找到每条光线投射的角度。角度取决于三件事:玩家面对的方向、相机的视野和正在绘制的柱子。varangle=this.fov*(column/this.resolution-0.5);varray=map.cast(player,player.direction+angle,this.range);2.追踪每条光线穿过网格接下来,我们检查每条光线穿过的墙。这里的目标是最终得到一个数组,列出光线离开玩家后穿过的每一堵墙。从玩家开始,我们找到最近的水平(stepX)和垂直(stepY)网格坐标线。移动到最近的地方并检查墙壁(检查)。不断重复检查,直到您追踪完每条线的所有长度。functionray(origin){v??arstepX=step(sin,cos,origin.x,origin.y);varstepY=step(cos,sin,origin.y,origin.x,true);varnextStep=stepX.length2范围)返回[原点];返回[原点].concat(射线(下一步));找到网格交点很简单:只需向下舍入x(1,2,3...),然后乘以射线的斜率(rise/run)得到y。vardx=run>0?Math.floor(x+1)-x:Math.ceil(x-1)-x;vardy=dx*(上升/运行);你现在看到这个算法的亮点了吗?我们不在乎地图有多大!只关注网格上的特定点——每帧的点数大致相同。例子中的地图是32×32,32000×32000的地图跑起来真快!3.绘制柱子追踪一条射线后,我们需要绘制它在其路径上经过的所有墙壁。varz=距离*Math.cos(角度);varwallHeight=this.height*height/z;我们将墙的最大高度除以z得到它的高度。墙越远,平局越短。嗯,这里的cos是怎么回事?如果直接使用原始距离,会产生超广角效果(鱼眼镜头)。为什么?想象一下,你面对一堵墙,墙的左右边缘比墙的中心离你更远。所以原来直墙的中心会膨胀!为了渲染出我们实际看到的墙壁,我们用每条投射光线构造一个三角形,并通过cos计算垂直距离。如图:我向你保证,这已经是本文中最难的数学题了。渲染出来我们使用相机对象Camera从玩家的角度绘制地图的每一帧。当我们从左向右滑动屏幕时,它负责渲染每一列。在我们绘制墙壁之前,我们渲染一个天空盒——一个带有星星和地平线的大背景图像,在我们绘制墙壁之后,我们将武器放在前景中。Camera.prototype.render=function(player,map){this.drawSky(player.direction,map.skybox,map.light);this.drawColumns(播放器,地图);this.drawWeapon(player.weapon,player.paces);};相机最重要的属性是分辨率、视野(fov)和范围(range)。分辨率决定每帧绘制多少列,即投射多少条光线。视野决定了我们能看到的宽度,也就是光线的角度。范围决定了我们能看到多远,这是使用控制对象Controls监听方向键(和触摸事件)的光线长度组合的最大值。使用游戏循环对象GameLoop调用requestAnimationFrame来请求渲染一帧。这里的游戏循环只有三行oop.start(functionframe(seconds){map.update(seconds);player.update(controls.states,map,seconds);camera.render(player,map);});Detailsrainddrops雨滴是用大量随机放置的短墙模拟出来的。varrainDrops=Math.pow(Math.random(),3)*s;varrain=(rainDrops>0)&&this.project(0.1,angle,step.distance);ctx.fillStyle='#ffffff';ctx.globalAlpha=0.15;while(--rainDrops>0)ctx.fillRect(left,Math.random()*rain.top,1,rain.height);在这里,没有画出墙的整个宽度,而是画出了一个像素的宽度。照明和闪电照明实际上只是阴影。所有墙壁都以全亮度绘制,然后覆盖有一些不透明度的黑色矩形。不透明度取决于墙的距离和方向(N/S/E/W)。ctx.fillStyle='#000000';ctx.globalAlpha=Math.max((step.distance+step.shading)/this.lightRange-map.light,0);ctx.fillRect(左,wall.top,width,wall.height);为了模拟闪电,map.light随机达到2并快速淡出。碰撞检测为了防止玩家穿墙,我们只需要将他想要到达的位置与地图进行比较即可。分别检查x和y允许玩家靠墙滑动。Player.prototype.walk=function(distance,map){vardx=Math.cos(this.direction)*distance;vardy=Math.sin(this.direction)*distance;if(map.get(this.x+dx,this.y)<=0)this.x+=dx;if(map.get(this.x,this.y+dy)<=0)this.y+=dy;};墙壁纹理没有纹理(texture)墙壁看起来不那么有趣。但是我们如何将地图的某个部分映射到特定的列呢?其实很简单:取交点坐标的小数部分。step.offset=offset-Math.floor(offset);vartextureX=Math.floor(texture.width*step.offset);例如,一面墙的交点为(10,8.2),则小数部分为0.2。这意味着交点距墙(8)的左边缘20%,距墙(9)的右边缘80%。所以我们使用0.2*texture.width来获取纹理的x坐标。尝试在幽灵般的废墟中漫步。其他人扩展了社区版。ctolsen添加了WASD箭头键。FredrikWallgren实现了Java端口。下一步是什么?由于光线投射器非常快速和简单,您可以快速实现许多想法。您可以成为地下城探索者、第一人称射击游戏或侠盗猎车手风格的沙盒游戏。取决于!不断耗费的时间真的让我想做一个具有大量程序生成世界的老式MMORPG。以下是一些可以帮助您入门的谜题:沉浸。该示例恳求您同时添加全屏、鼠标悬停、雨背景和闪电。室内水平。用对称渐变替换天空盒。或者,如果您觉得疯狂,可以尝试用瓷砖渲染地板和天花板。(可以这样想:画完所有的墙之后,画面中剩下的空间就是地板和天花板)照明对象。我们已经有了一个相当强大的光照模型。为什么不在地图上放置灯光并根据它们计算墙壁照明?光源占大气的80%。良好的触摸事件。我做了一些基本的触摸操作,手机和平板的朋友可以试试同样的demo。但是这里有很大的改进空间。相机效果。比如放大缩小、模糊、醉酒模式等等。使用光线投射器,这一切都非常容易。首先在控制台修改camera.fov。与往常一样,如果您构建了一些很棒的东西或有任何相关研究要分享,请给我发电子邮件或发推文,我会分享。讨论HackerNews上的讨论。科曼奇光线投射-使用高度图进行光线投射的绝佳示例。感谢这篇原本打算写两个小时的文章,却用了三个星期。如果没有以下人员的帮助,我不可能写出这篇文章:JimSnodgrass:编辑和反馈JeremyMorrell:编辑和反馈JeffPeterson:编辑和反馈ChrisGomez:武器和反馈AmandaLenz:笔记本电脑包和支架NicholasS:墙面纹理DanDuriscoe:DeathValleyskybox原文链接:Afirst-personenginein265lines翻译:伯乐在线-JawardHuaTsai翻译链接:http://blog.jobbole.com/70956/