视频1 视频21 视频41 视频61 视频文章1 视频文章21 视频文章41 视频文章61 推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37 推荐39 推荐41 推荐43 推荐45 推荐47 推荐49 关键词1 关键词101 关键词201 关键词301 关键词401 关键词501 关键词601 关键词701 关键词801 关键词901 关键词1001 关键词1101 关键词1201 关键词1301 关键词1401 关键词1501 关键词1601 关键词1701 关键词1801 关键词1901 视频扩展1 视频扩展6 视频扩展11 视频扩展16 文章1 文章201 文章401 文章601 文章801 文章1001 资讯1 资讯501 资讯1001 资讯1501 标签1 标签501 标签1001 关键词1 关键词501 关键词1001 关键词1501 专题2001
详细介绍HTML5实现3D迷宫的代码案例
2020-11-27 15:10:35 责编:小采
文档
功能描述:

  左右方向键控制玩家的方向,上下方向键控制玩家的前进和后退。

效果预览:

  

实现原理:

  在上面的效果预览中,可以看到右边是2D的平面地图,而左边的则是第一人称的3D视图,这两幅图的关系是非常密切的,实质上,实现3D视觉的过程,就是依据2D地图把地图转换成第一人称视觉的过程。

3D效果的实现只局限于平面(意思是从侧面看没有立体效果),在这种有局限性的3D效果中,我们以一个个物体为单位,通过不同物体平面之间的视觉差实现3D。而在这次的效果中,为了使物体从不同角度看都能具有立体效果,我们把单位从平面改成线。

  首先,我们创建一个叫视觉平面的东西,它像一面镜子,把实物投影到一个平面上,首先初始化该平面的尺寸:

screenSize:[320,240],//视觉屏幕尺寸

  之后,我们可以以1像素为单位,只要知道物体每个像素在该视觉平面上显示出来的高度,就可以绘制出物体在第一人称视觉上的效果。

  以视觉平面上的第一个像素线段为例,根据比例可得知:玩家到视觉平面的距离/玩家到物体的实际距离=物体在视觉平面上的高度/物体的实际高度。由于玩家到视觉平面的距离和物体的实际高度我们可以自己定义,因此我们只要知道玩家到物体的距离,就可以知道物体该像素在视觉平面的高度。

  怎样知道玩家到物体的实际距离呢?这时我们就需要借助和3D视觉图密切相关的2D地图了。首先,我们定义玩家的最大视觉角度为60度(意思是玩家的视觉范围角度),由于我们现在处理的是平面的第一条像素线,因此该像素线相对于玩家的角度就为-30度。在地图上,我们可以知道玩家的X,Y位置和玩家的方向,因此我们就可以知道第一条像素线在地图上的方向。

  在2D地图上怎么表示3D视觉平面上的一条像素线呢?其实仔细想想就可以发现,3D视觉平面中的一条像素线,相当于2D地图上特定方向上发出的一条射线,射线与物体的交点就是3D视觉平面中的那条像素线的内容。因此,只要我们计算出该射线的长度(起点:玩家位置 终点:射线和物体相交的位置),就可以知道玩家与物体的距离,从而求得改像素的物体在视觉平面上的高度。

  最后只要循环遍历视觉平面上的每一条1px宽的像素线,并根据2D地图上对应射线的长度,就可以求得视觉平面上所有视觉范围内的物体每一像素的高度,形成3D视觉效果。

代码分析:

  主要看实现的核心代码,循环遍历视觉平面上每条像素线,绘制出该像素线上的物体内容:

var context=this.screenContext;

 context.clearRect(0,0,this.screenSize[0],this.screenSize[1]);
 context.fillStyle="rgb(203,242,238)";
 context.fillRect(0,0,this.screenSize[0],this.screenSize[1]/2);
 context.fillStyle="rgb(77,88,87)";
 context.fillRect(0,this.screenSize[1]/2,this.screenSize[0],this.screenSize[1]/2);

由于该效果需要两个canvas(一个显示地图,一个显示3D视觉图),因此我们首先获取3D视觉图上的canvas,并绘制地面和天空。

//cnGame.context.beginPath();
 for(var index=0,colCount=this.screenSize[0]/this.viewColWidth;index<colCount;index++){
 screenX=-this.screenSize[0]/2+index*this.viewColWidth;//该竖线在屏幕的x坐标 
 colAngle=Math.atan(screenX/this.screenDistant);//玩家的视线到屏幕上的竖线所成的角度
 colAngle%=2*Math.PI;
 var angle=this.player.angle/180*(Math.PI)-colAngle;//射线在地图内所成的角度
 angle%=2*Math.PI;
 if(angle<0){
 angle+=2*Math.PI;
 }
 distant=0;
 x=0;
 y=0;
 centerX=this.player.x+(this.player.width)/2;//玩家中点X坐标
 centerY=this.player.y+(this.player.height)/2;//玩家中Y坐标
 while(this.map.getPosValue(centerX+x,centerY-y)==0){
 distant+=1;
 x=distant*Math.cos(angle);
 y=distant*Math.sin(angle);
 }
 //如果射线在地图遇到墙壁,则画线
 /*cnGame.context.strokeStyle="#000"; 
 cnGame.context.moveTo(centerX,centerY);
 cnGame.context.lineTo(centerX+x,Math.floor(centerY-y));
 cnGame.context.closePath();
 */
 
 distant*=Math.cos(colAngle);//防止鱼眼效果
 
 heightInScreen=this.screenDistant/(distant)*this.wallSize[2];//根据玩家到墙壁的距离计算墙壁在视觉平面的高度
 var img=cnGame.loader.loadedImgs[srcObj.stone2];
 context.drawImage(img,0,0,2,240,this.viewColWidth*index,(this.screenSize[1]-heightInScreen)/2, this.viewColWidth,heightInScreen) 
 }

  之后,就可以开始循环遍历每条像素线,绘制出物体内容。在处理每条像素线的过程中,我们让射线每次增加1像素的长度,当射线遇到非空地的时候(getPosValue(x,y)>0),就停止增长,并记录下此时射线的长度,该长度就是玩家到该像素线内容的实际距离。

  上面代码中,注释掉的部分其实就是用于绘制出发射的射线,如果大家有需要,可以恢复该部分的代码,就可以看到玩家的视觉范围。

  需要注意的是,由于视觉平面有区别人的眼球(是一个平面而非球体),因此我们还需要使距离乘上玩家视觉角度的余弦值,从而避免了鱼眼效果。

  最后,我们还可以在3D视觉图上绘制出玩家(拿的手),达到更好的效果。

下载本文
显示全文
专题