在WebGL场景中使用2DA*寻路

    这篇文章将讨论如何在一个自定义的地面网格上进行简单的2D寻路,以及确定路径后如何使用基于物理引擎的运动方式使物体沿路径到达目标地点。读者需要预先对WebGL和Babylonjs知识有一些了解,可以参考我录制的WebGL入门视频教程和翻译的官方入门文档,当然也可以用自己喜欢的其他方式来学习。

  文章主要分成如下几部分:

  1、自定义地面网格与寻路矩阵

  2、生成Babylon格式3D模型

  3、使用pathfinding库进行2D寻路

  4、基于cannon.js物理引擎使物体沿路径移动

  场景可以通过http://ljzc002.github.io/FPS3/index.html访问,完整代码可以在https://github.com/ljzc002/ljzc002.github.io查看。

  场景如下图:

  使用WASD控制自由相机位置,移动鼠标控制视角,右键点击地面会在地面上放置一个“目标方块”,然后标有“农民”标志的小球会向目标方块移动。

  场景中使用了2DA*寻路算法,如下图所示:

  当目标方块位于障碍的另一边时,农民会尽量寻找最短的路径绕开障碍物前往目标方块。

1、自定义地面网格与寻路矩阵:

a、在Babylon.js渲染引擎中自定义地面网格

 1     var vdata_ground=new BABYLON.VertexData.CreateGround({width:198,height:198,subdivisionsX:99,subdivisionsY:99});//分成了99段,一条边上100个顶点
 2     var arr_vposition=vdata_ground.positions;
 3     var map=[];
 4     var len=arr_vposition.length/3;//和挨个遍历比起来,似乎有目的的去找更好
 5     for(var i=20;i<81;i++)//对于这个范围内的行
 6     {
 7         for(var j=20;j<23;j++)//对于这个范围内的列
 8         {
 9             arr_vposition[(j+100*i)*3+1]=1;
10         }
11     }
12     for(var i=20;i<81;i++)//对于这个范围内的行
13     {
14         for(var j=40;j<43;j++)//对于这个范围内的列
15         {
16             arr_vposition[(j+100*i)*3+1]=2;
17         }
18     }
19     for(var i=20;i<81;i++)//对于这个范围内的行
20     {
21         for(var j=60;j<63;j++)//对于这个范围内的列
22         {
23             arr_vposition[(j+100*i)*3+1]=4;
24         }
25     }
26     BABYLON.VertexData._ComputeSides(0, arr_vposition, vdata_ground.indices, vdata_ground.normals
27         , vdata_ground.uvs);
28     var mesh_ground=new BABYLON.Mesh("mesh_ground",scene);
29     mesh_ground.renderingGroupId=2;
30     vdata_ground.applyToMesh(mesh_ground, true);

  第一行建立了一个Ground类型的Babylonjs“顶点数据”对象,这个对象包含了建立地面网格所需的顶点位置、法线、纹理坐标、顶点索引数据(建议读者亲自用调试模式看一下这个对象的结构),构造函数中的两个198表示地面的长宽是198,两个99表示每一条边被分为99段(由100个顶点组成,每两个顶点之间的距离为2),至于为什么设置为99段后文会有说明,这时的地面网格如果渲染出来将是一个平面。

  要让地面变得凹凸不平有两种思路:在高度变化的点的比例较大时,可以尝试对每个顶点进行遍历,然后按照某种规则改变顶点的高度;在比例较小时建议直接在缓存数组中找到这些顶点进行改变,显然后者速度更快。

  第26行根据顶点数据对每个面的正反进行计算,在对网格的顶点信息进行修改后一般都要执行这一条语句,而另一条经常在它之前执行的语句是:“

BABYLON.VertexData.ComputeNormals(positions, indices, normals);

”,它的作用是在顶点数据变化后重新计算法线方向。这里不执行这条语句的原因是Babylonjs中的地面网格是一种“简化”的网格

  如图所示:

  同样表示两个方块,简化的方式使用六个顶点,顶点之间的片元数据由顶点数据插值而成,因为左边的方块和右边的方块共用了两个顶点,所以这两个方块的法线方向和纹理坐标必定是连续的。而用非简化的方式表示这两个方块,则需要使用八个顶点,缺点是增加了对性能的消耗,优点是法线方向和纹理坐标不必连续,可以进行截然不同的变化。

  因为采用了简化的方式,地面网格的同一个顶点处于多个不同的平面中,使用ComputeNormals计算地面网格的顶点的法线方向也就失去了意义,事实上Babylonjs把地面网格顶点的法线方向都默认为竖直向上。

  后面的代码建立了一个空的网格,将网格的渲染组设为2,将顶点数据交给这个空网格对象。

  生成的网格如下图所示:

b、建立2D寻路矩阵

  在寻路场景中使用的pathfinding库需要用一个矩阵(二维数组)来定义障碍物的位置,其中零元素表示这个地块可以通行,不为零的元素表示无法通行,下面是建立这个数组的方法:

 1     mesh_ground.mydata={};
 2     mesh_ground.mydata.walkabilityMatrix=MakewalkabilityMatrix(arr_vposition,99,99,2);
 3     mesh_ground.mydata.len_x=99;
 4     mesh_ground.mydata.len_y=99;
 5     mesh_ground.mydata.len_s=2;
 6
 7     。
 8     。
 9     。
10
11 //对每个正方形区块的倾斜程度进行计算,得出是否可以通行
12 //顶点数据,寻路空间宽度,寻路空间高度,每个方格区域的边长
13 function MakewalkabilityMatrix(arr,len_x,len_y,len_s)
14 {
15     var arr_Matrix=numeric.rep([len_y,len_x],0);
16     var len_s2=len_s*0.707;//求得平均点到其中一个边线点的水平距离0.7071067811865476
17     //var len_s2a=len_s2;
18     //var len_s2b=len_s2;
19     //var len_s2c=len_s2;
20     for(var i=0;i<len_y;i++)//对于每一行寻路单元格
21     {
22         for(var j=0;j<len_x;j++)//对于这一行里的每一个单元格
23         {
24             var int1=j+i*(len_x+1);
25             var int2=j+i*(len_x+1)+1;
26             var int3=j+(i+1)*(len_x+1);
27             var int4=j+(i+1)*(len_x+1)+1;
28             var y1=arr[int1*3+1];
29             var y2=arr[int2*3+1];
30             var y3=arr[int3*3+1];
31             var y4=arr[int4*3+1];
32             var ya=(y1+y2+y3+y4)/4;
33             var yb=Math.max(Math.abs(y1-ya),Math.abs(y2-ya),Math.abs(y3-ya),Math.abs(y4-ya));
34             arr_Matrix[i][j]=parseInt((yb)/0.707);//高度超过了水平距离几倍就认为是几倍的障碍物
35         }
36     }
37     return arr_Matrix;
38 }

  这一段代码的思路是:将地面网格垂直方向的正投影作为寻路单元格,取每一个寻路单元格的四个顶点,算出这四个顶点的平均高度与每个顶点高度的差的最大值与寻路单元格中心点到顶点的水平距离的比,将这个比值作为“障碍程度”(简单来说就是顶点高度变化的越剧烈,障碍就越难跨越)。然后为网格添加一个mydata属性(JavaScript语言的优势),把和寻路矩阵有关的信息放到这个属性里。

  这里用到了numeric数学库,可以在http://www.numericjs.com/查看文档。

  2、生成babylon格式的3D模型:

  网格生成完毕后需要拿到其他程序中使用,这里我选择把它保存为babylon格式的3D模型,babylon是一种json字符串模型文件,其优点是结构简单功能全面。

  Babylon.js的官方网站上有完整的格式说明和例子:http://doc.babylonjs.com/generals/file_format_map_(.babylon),遗憾的是例子里的一行少了一个逗号所以会导致导入出错,不知道现在改正了没有。

  以下是生成对应json的代码:

  1 /**
  2  * Created by Administrator on 2017/7/14.
  3  */
  4 function Export_mesh(arr_mesh,PngName)//用Babylon格式导出模型
  5 {
  6     //场景对象
  7     var obj_scene=
  8     {
  9         ‘autoClear‘: true,
 10         ‘clearColor‘: [0,0,0],
 11         ‘ambientColor‘: [0,0,0],
 12         ‘gravity‘: [0,-9.81,0],
 13         ‘cameras‘: [{
 14             ‘name‘: ‘Camera‘,
 15             ‘id‘: ‘Camera‘,
 16             ‘position‘: [7.4811,5.3437,-6.5076],
 17             ‘target‘: [-0.3174,0.8953,0.3125],
 18             ‘fov‘: 0.8576,
 19             ‘minZ‘: 0.1,
 20             ‘maxZ‘: 100,
 21             ‘speed‘: 1,
 22             ‘inertia‘: 0.9,
 23             ‘checkCollisions‘: false,
 24             ‘applyGravity‘: false,
 25             ‘ellipsoid‘: [0.2,0.9,0.2]
 26         }],
 27         ‘activeCamera‘: ‘Camera‘,
 28         ‘lights‘: [{
 29             ‘name‘: ‘Sun‘,
 30             ‘id‘: ‘Sun‘,
 31             ‘type‘: 1,
 32             ‘position‘: [0.926,7.3608,14.1829],
 33             ‘direction‘: [-0.347,-0.4916,-0.7987],
 34             ‘intensity‘: 1,
 35             ‘diffuse‘: [1,1,1],
 36             ‘specular‘: [1,1,1]
 37         }],
 38         ‘materials‘:[{
 39             ‘name‘: ‘mball‘,
 40             ‘id‘: ‘mball‘,
 41             ‘ambient‘: [1,1,1],
 42             ‘diffuse‘: [1,1,1],
 43             ‘specular‘: [1,1,1],
 44             ‘specularPower‘: 50,
 45             ‘emissive‘: [0,0,0],
 46             ‘alpha‘: 1,
 47             ‘backFaceCulling‘: true,
 48             ‘diffuseTexture‘: {
 49                 ‘name‘: PngName?PngName:‘snow2.jpg‘,
 50                 ‘level‘: 1,
 51                 ‘hasAlpha‘: 1,
 52                 ‘coordinatesMode‘: 0,
 53                 ‘uOffset‘: 0,
 54                 ‘vOffset‘: 0,
 55                 ‘uScale‘: 1,
 56                 ‘vScale‘: 1,
 57                 ‘uAng‘: 0,
 58                 ‘vAng‘: 0,
 59                 ‘wAng‘: 0,
 60                 ‘wrapU‘: true,
 61                 ‘wrapV‘: true,
 62                 ‘coordinatesIndex‘: 0
 63             }
 64         }],
 65         ‘geometries‘: {},
 66         ‘meshes‘: [],
 67         ‘multiMaterials‘: [],
 68         ‘shadowGenerators‘: [],
 69         ‘skeletons‘: [],
 70         ‘sounds‘: [],
 71         ‘mydata‘:{‘walkabilityMatrix‘:[]}
 72     };
 73     //所有模型组件的父物体
 74     var obj_allbase=
 75     {
 76         ‘name‘: ‘allbase‘,
 77         ‘id‘: ‘allbase‘,
 78         ‘materialId‘: ‘mball‘,
 79         ‘position‘: [0,0,0],
 80         ‘rotation‘: [0,0,0],
 81         ‘scaling‘: [1,1,1],
 82         ‘isVisible‘: true,
 83         ‘isEnabled‘: true,
 84         ‘checkCollisions‘: false,
 85         ‘billboardMode‘: 0,
 86         ‘receiveShadows‘: true,
 87         ‘positions‘: [],
 88         ‘normals‘: [],
 89         ‘uvs‘: [],
 90         ‘indices‘: [],
 91         ‘subMeshes‘: [{
 92             ‘materialIndex‘: 0,
 93             ‘verticesStart‘: 0,
 94             ‘verticesCount‘: 0,
 95             ‘indexStart‘: 0,
 96             ‘indexCount‘: 0
 97         }]
 98     };
 99     obj_scene.meshes.push(obj_allbase);
100     var len=arr_mesh.length;
101     var all_x=0;
102     var all_y=0;
103     var all_z=0;
104     for(var i=0;i<len;i++)
105     {
106         var obj_child={};
107         if(arr_mesh[i].geometry._vertexBuffers!=null)
108         {
109             var child=arr_mesh[i];
110             if(!child.mydata)
111             {
112                 child.mydata={}
113             }
114             var vb=child.geometry._vertexBuffers;
115             all_x+=child.position.x;
116             all_y+=child.position.y;
117             all_z+=child.position.z;
118             obj_child=
119             {
120                 ‘name‘: child.name,
121                 ‘id‘: child.id,
122                 ‘parentID‘: ‘allbase‘,
123                 ‘materialId‘: ‘mball‘,
124                 ‘position‘: [child.position.x,child.position.y,child.position.z],
125                 ‘rotation‘: [child.rotation.x,child.rotation.y,child.rotation.z],
126                 ‘scaling‘: [child.scaling.x,child.scaling.y,child.scaling.z],
127                 ‘isVisible‘: true,
128                 ‘isEnabled‘: true,
129                 ‘checkCollisions‘: false,
130                 ‘billboardMode‘: 0,
131                 ‘receiveShadows‘: true,
132                 ‘positions‘: vb.position._buffer._data,
133                 ‘normals‘: vb.normal._buffer._data,
134                 ‘uvs‘: vb.uv._buffer._data,
135                 ‘indices‘: child.geometry._indices,
136                 ‘subMeshes‘: [{
137                     ‘materialIndex‘: 0,
138                     ‘verticesStart‘: 0,
139                     ‘verticesCount‘: vb.position._buffer._data.length,
140                     ‘indexStart‘: 0,
141                     ‘indexCount‘: child.geometry._indices.length
142                 }],
143                 ‘mydata‘:child.mydata
144             };
145             obj_scene.meshes.push(obj_child);
146         }
147     }
148     //不能让模型的主体过于偏离模型的中心
149     all_x=all_x/len;
150     all_y=all_y/len;
151     all_z=all_z/len;
152     for(var i=1;i<len+1;i++)
153     {
154         obj_scene.meshes[i].position[0]-=all_x;
155         obj_scene.meshes[i].position[1]-=all_y;
156         obj_scene.meshes[i].position[2]-=all_z;
157     }
158     var str_data=JSON.stringify(obj_scene);
159     DownloadText(MakeDateStr()+"testscene",str_data,".babylon");
160 }

  可以看出,一个babylon文件可以包含多个网格对象,除了网格对象之外这个模型文件还可以存储场景、光照、相机、动画、骨骼等信息,这些功能可以选择性使用。方法的最后使用DownloadText方法将json文本导出,DownloadText是我参考网络资料编写的字符下载方法,如果不使用DownloadText,直接在Chrome浏览器的调试模式下的命令行里输入“console.log(str_data)”也能得到json字符串。

  DownloadText内容如下:

  1 /**
  2  * Created by Administrator on 2015/3/2.
  3  */
  4 /**
  5  * 将指定字符写入指定名称的文本文件中,并可以选择本地保存目录,兼容IE11和谷歌浏览器
  6  */
  7 function DownloadText(filename,content,filetype)
  8 {
  9     if(filetype==null)
 10     {
 11         filetype=".txt";
 12     }
 13     if(document.createElement("a").download!=null)//谷歌和火狐
 14     {
 15         var aLink = document.createElement(‘a‘);
 16         var datatype="data:text/plain;charset=UTF-8,";
 17         if(filetype==".xml")
 18         {
 19             datatype="data:text/xml;charset=UTF-8,";
 20         }
 21         if(filetype==".babylon")
 22         {//浏览器还没有支持babylon的mime类型!!
 23             datatype="data:text/plain;charset=UTF-8,";
 24         }
 25         if(filetype==".png"||filetype==".jpeg")
 26         {
 27             datatype="";
 28         }
 29         if(content.length<1000000)
 30         {
 31             aLink.href = datatype+content;//dataurl格式的字符串"
 32         }
 33         else
 34         {//对于过大的文件普通dataURL不支持,所以使用“二进制流大对象”
 35             aLink.href=URL.createObjectURL(new Blob([content],{type:"text/plain"}));
 36         }
 37         aLink.download = filename;
 38         aLink.innerHTML=filename;
 39         //aLink.setAttribute("onclick","");
 40         aLink.onclick=function()
 41         {
 42             document.getElementById("div_choose").style.display="none";
 43             //delete_div(‘div_choose‘);
 44             delete_div(‘div_mask‘);
 45         }
 46         //aLink.style.display="none";
 47         //document.body.appendChild(aLink);
 48         /*var evt = document.createEvent("HTMLEvents");//建立一个事件
 49         evt.initEvent("click", false, false);//这是一个单击事件
 50         evt.eventType = ‘message‘;
 51         aLink.dispatchEvent(evt);//触发事件*/
 52         //chrome认为点击超链接下载文件是超链接标签的“默认属性”,谷歌认为默认属性不可以用脚本来触发,所以从M53版本开始dispatchEvent无法触发超链接下载
 53         //window.open(datatype+content, "_blank");
 54         //document.write(datatype+content);
 55         delete_div(‘div_choose‘);
 56         delete_div(‘div_mask‘);
 57         var evt=evt||window.event;
 58         cancelPropagation(evt);
 59         var obj=evt.currentTarget?evt.currentTarget:evt.srcElement;
 60
 61         Open_div("", "div_choose", 240, 180, 400, 80, "", "",1,401);//打开一个带遮罩的弹出框
 62         var div_choose=$("#div_choose")[0];
 63         div_choose.style.border="1px solid";
 64         div_choose.innerHTML="<span>谷歌浏览器专用文件生成完毕,请点击下面的文件名下载文件。</span><br>"
 65         div_choose.appendChild(aLink);
 66         drag(div_choose);//让弹出框可以被拖拽
 67         aLink.onmousedown=function()
 68         {
 69             var evt=evt||window.event;
 70             cancelPropagation(evt);
 71         }
 72     }
 73     else//IE
 74     {
 75         var Folder=BrowseFolder();
 76         if(Folder=="false")
 77         {
 78             alert("保存失败!");
 79         }
 80         else
 81         {
 82             var fso, tf;
 83             fso = new ActiveXObject("Scripting.FileSystemObject");//创建文件系统对象
 84             tf = fso.CreateTextFile(Folder + filename+filetype, true,true);//创建一个文件
 85             tf.write(content);
 86             tf.Close();
 87             alert("保存完毕!");
 88         }
 89     }
 90 }
 91 function BrowseFolder()
 92 {//使用ActiveX控件
 93     try
 94     {
 95         var Message = "请选择保存文件夹";  //选择框提示信息
 96         var Shell = new ActiveXObject( "Shell.Application" );
 97         var Folder = Shell.BrowseForFolder(0,Message,0x0040,0x11);//起始目录为:我的电脑
 98         //var Folder = Shell.BrowseForFolder(0,Message,0); //起始目录为:桌面//选择桌面会报错!!
 99
100         if(Folder != null)
101         {
102             Folder = Folder.items();  // 返回 FolderItems 对象
103             Folder = Folder.item();  // 返回 Folderitem 对象
104             Folder = Folder.Path;   // 返回路径
105             if(Folder.charAt(Folder.length-1) != "\\")
106             {
107                  Folder = Folder + "\\";
108             }
109             //document.all.savePath.value=Folder;
110             return Folder;
111         }
112     }
113     catch(e)
114     {
115         return "false";
116         alert(e.message);
117     }
118 }

  接下来,我们要在另一个程序中使用上面生成的模型文件,使用Babylonjs的资源管理器加载网格:

 1     this.loader =  new BABYLON.AssetsManager(this.scene);//资源管理器
 2
 3     // 资源数组
 4     this.assets = {};
 5     //为资源管理器分配一个任务
 6     var meshTask = this.loader.addMeshTask("gun", "", "./assets/", "gun.babylon");
 7     meshTask.onSuccess = function(task) {//这个任务完成
 8         _this._initMesh(task);
 9     };    //第一个参数表示task的name,第二个参数表示加载模型文件中的哪个网格,为空则用数组形式加载全部,第三个参数表示路径,第四个参数是文件名
10     var meshTask2 = this.loader.addMeshTask("mesh_ground", "", "./assets/arena/", "2017810_14_12_59testscene.babylon");
11     meshTask2.onSuccess = function(task) {
12         _this._initMesh(task);
13     };
14
15     this.loader.onFinish = function (tasks)//所有任务完成
16     {
17     。。。
18     }
19
20     。
21     。
22     。
23
24     _initMesh : function(task)
25     {
26         this.assets[task.name] = task.loadedMeshes;
27         for (var i=0; i<task.loadedMeshes.length; i++ ){
28             var mesh = task.loadedMeshes[i];
29             mesh.isVisible = false;
30             //预先把所有资源加载下来,但不显示,当需要时再把它显示在需要的位置,或者在需要的位置,建立一个资源的实例(克隆)
31         }
32     }
33     

  这时,会发生一个小问题:Babylonjs并不支持我们夹带在mesh中的mydata属性。解决方法是在babylon.30.all.max.js的21272行附近修改:

1             if (parsedMesh.metadata !== undefined) {
2                 mesh.metadata = parsedMesh.metadata;
3             }
4             if (parsedMesh.mydata !== undefined) {
5                 mesh.mydata = parsedMesh.mydata;
6             }    

  仿照metadata的写法加上对mydata的支持,当然,也可以考虑把mydata夹带到其他被mesh所支持的属性里。

  3、使用pathfinding库进行2D寻路

  pathfindingjs是一个开源2D寻路库,可以在https://github.com/qiao/PathFinding.js下载完整代码和文档,可以在http://qiao.github.io/PathFinding.js/visual/在线试验各种寻路方式

  pathfinding的基本用法如下:

 1 var finder = new PF.AStarFinder({//“寻路器”
 2     diagonalMovement: 3
 3 });
 4
 5 。
 6 this.grid=new PF.Grid(this.len_x,this.len_y,this.walkabilityMatrix);//生成寻路网格 7 。

8
 9 function FindWaytogo(pickResult)//pickResult是Babylonjs中定义的“鼠标选取结果”对象
10 {
11     var faceId=pickResult.faceId;//点击了网格中的第几个面
12     var pickedMesh=pickResult.pickedMesh;//被点击的网格
13     var px=MyGame.player.mesh.position.x;//被控对象在场景中的水平位置
14     var py=MyGame.player.mesh.position.z;
15
16     var len_x=MyGame.arena.len_x;//寻路网格的格数和每格的长度
17     var len_y=MyGame.arena.len_y;
18     var len_s=MyGame.arena.len_s;
19     if(px>-len_x*len_s/2&&px<len_x*len_s/2&&py>-len_y*len_s/2&&py<len_y*len_s/2&&MyGame.arena.grid)//如果使用了pathfinder的障碍矩阵
20     {
21         var arr_matrix=MyGame.arena.walkabilityMatrix;//寻路矩阵
22         var count=parseInt(faceId/2);//第几个方格
23         //接下来要把网格的面转换为寻路方格的坐标,后面还要把寻路方格的坐标转换为scene中的位置
24         //面数转换为方格坐标
25         var count_y=parseInt(count/len_x);
26         var count_x=count%len_x;
27         //场景坐标转化为方格坐标
28         var count_x0=parseInt(px/len_s+len_x/2);
29         var count_y0=parseInt(-py/len_s+len_y/2);
30
31         //寻路,返回一个由方格坐标组成的数组
32         var path = finder.findPath(count_x0,count_y0 , count_x,count_y, MyGame.arena.grid.clone());//这些是寻路网格坐标
33         var len=path.length;
34         for(var i=0;i<len;i++)
35         {//把方格坐标转化为场景坐标
36             var obj=path[i];
37             obj[0]=(obj[0]-len_x/2)*len_s;
38             obj[1]=(-obj[1]+len_y/2)*len_s;
39         }
40
41         path.push([pickResult.pickedPoint.x,pickResult.pickedPoint.z]);
42         MyGame.player.path_goto=path;//在使用时在生成高度
43         MyGame.player.positiontogo=[pickResult.pickedPoint.x,pickResult.pickedPoint.z];
44         path.shift();//把第一个出发节点去掉
45         console.log("生成路径,起点:["+px+","+py+"],终点:["+pickResult.pickedPoint.x+","+pickResult.pickedPoint.z+"]");
46     }
47 }

  这样,我们就把场景中的位置对应成了寻路网格中的位置,然后使用pathfinding生成了2D路径。需要注意的是pathfinding中的grid对象只能使用一次,再次寻路时需要重新生成grid或者使用grid的克隆对象。

  4、基于cannon.js物理引擎沿路径移动 

  接下来需要让被控物体沿着指定的路径运动,为了能让物体在凹凸不平的地形中运动时保持紧贴地面,我在这里使用了cannonjs物理引擎(关于物理引擎的用法可以参考上一篇文章)。经过试验,这个版本的cannonjs的单个物理仿真器最多支持对10000个顶点的物理仿真,所以前文没法把地面网格分成更多段。

  我们在这个场景中监听“右键点击地面”的事件,代码如下:

 1 canvas.addEventListener("click", function(evt) {
 2             var width = engine.getRenderWidth();
 3             var height = engine.getRenderHeight();
 4             var pickInfo = scene.pick(width/2, height/2, null, false, _this.camera);//点击信息
 5             if(evt.button==2)//右键单击
 6             {
 7                 cancelEvent(evt);//阻止默认响应
 8                 if(pickInfo.hit&&pickInfo.pickedMesh.name=="mesh_ground")//点击到了地面上
 9                 {
10                     MyGame.player.mesh.physicsImpostor.setMass(70);//给被控物体赋予质量,这样它才可以下落
11                     FindWaytogo(pickInfo);//在玩家到点击目的地之间找到一条路径
12                     var mesh_togo=BABYLON.Mesh.CreateBox("box", 1, scene);//目标方块
13                     mesh_togo.position = pickInfo.pickedPoint.clone();//pickResult.pickedPoint
14                     mesh_togo.renderingGroupId=2;
15                     MyGame.player.mesh_togo=mesh_togo;
16                 }
17             }
18
19
20
21         }, false);

  然后在每次渲染之前执行以下运动方法:

 1         scene.registerBeforeRender(function() {
 2             if(MyGame.flag_startr==1)//如果开始渲染了
 3             {
 4                 if(MyGame.flag_view=="first"||MyGame.flag_view=="third")
 5                 {
 6                     physics20170725(MyGame.player);
 7                 }
 8                 if(MyGame.flag_view=="free")
 9                 {
10                     pathgoto20170808(MyGame.player);
11                 }
12             }
13         });
 1 function pathgoto20170808(obj)//obj是player
 2 {
 3     if(true)
 4     //if(obj.standonTheGround==1)//站在地面上时考虑将质量设为0?
 5     {
 6         if(obj.path_goto!="sleep"&&obj.path_goto!="lose")
 7         {
 8             var len_x=MyGame.arena.len_x;
 9             var len_y=MyGame.arena.len_y;
10             var len_s=MyGame.arena.len_s;
11             var vl_now=obj.mesh.physicsImpostor.getLinearVelocity();
12
13             if(obj.path_goto.length>0)
14             {
15                 var px=obj.mesh.position.x;//全是场景坐标!!
16                 var py=obj.mesh.position.z;
17                 var count_x0=px;
18                 var count_y0=py;
19                 var count_x=obj.path_goto[0][0];
20                 var count_y=obj.path_goto[0][1];
21                 var len=obj.path_goto.length;
22                 var count_x2=obj.path_goto[len-1][0];
23                 var count_y2=obj.path_goto[len-1][1];
24                 var y_obj=obj.mesh.position.y;
25                 if((Math.pow(count_x0-count_x2,2)+Math.pow(count_y0-count_y2,2))<0.25*len_s*len_s)
26                 {//在移动过程中因未知原因跳到距终点0.5以内距离的地方,直接寻找最终点
27                     console.log("在最终格内");
28                     if((Math.pow(count_x0-count_x2,2)+Math.pow(count_y0-count_y2,2))<0.01*len_s*len_s)//到达0.1距离以内的地方,认为到达最终目标,直接定位
29                     {
30
31                         obj.mesh.position.x=count_x;
32                         obj.mesh.position.z=count_y;
33                         obj.path_goto="sleep";
34                         console.log("到达最终目标:["+obj.mesh.position.x+","+obj.mesh.position.y+","+obj.mesh.position.z+"]");
35                         obj.mesh.physicsImpostor.setMass(0);//质量设为零将不会下落
36                         obj.mesh_togo.dispose();
37                         obj.mesh.physicsImpostor.setAngularVelocity(new BABYLON.Vector3(0,0,0));//停下
38                         obj.mesh.physicsImpostor.setLinearVelocity(new BABYLON.Vector3(0,0,0));
39
40                     }
41                     else{
42                         var v_temp=new BABYLON.Vector3(count_x2,0,count_y2).subtract(new BABYLON.Vector3(obj.mesh.position.x,0,obj.mesh.position.z)).normalize().scaleInPlace(obj.vm.forward);
43                         v_temp.y=vl_now.y<=0?vl_now.y:0;//这个单位应该脚踏实地的平稳运动
44                         obj.mesh.physicsImpostor.setLinearVelocity(v_temp);
45                         //obj.mesh.physicsImpostor.setAngularVelocity(new BABYLON.Vector3(0,0,0));//停下
46                         if(obj.path_goto.length>1)
47                         {
48                             obj.path_goto=[obj.path_goto[len-1]];//只剩一个最终目标
49                         }
50                     }
51                 }
52                 else if((Math.pow(count_x0-count_x,2)+Math.pow(count_y0-count_y,2))>4*len_s*len_s)
53                 {//在移动过程中因未知原因跳到距下个目标格2以外距离的地方,需要重新寻路,这种计算可能耗时较大,不能每帧执行!!
54                     obj.path_goto="lose";
55                     //obj.mesh.physicsImpostor.setMass(0);//不掉落
56                     obj.mesh.physicsImpostor.setLinearVelocity(new BABYLON.Vector3(0,0,0));
57                     obj.mesh.physicsImpostor.setAngularVelocity(new BABYLON.Vector3(0,0,0));//停下
58                     return false;
59                 }
60                 else if(obj.path_goto.length>1&&(Math.pow(count_x0-count_x,2)+Math.pow(count_y0-count_y,2))<0.25*len_s*len_s)
61                 {//距离下一寻路格足够近,切换下一寻路格
62
63                     obj.path_goto.shift();
64                     count_x=obj.path_goto[0][0];
65                     count_y=obj.path_goto[0][1];
66                     console.log("切换下一个寻路单元格:["+count_x+","+count_y+"]");
67                     var v_temp=new BABYLON.Vector3(count_x,0,count_y).subtract(new BABYLON.Vector3(obj.mesh.position.x,0,obj.mesh.position.z)).normalize().scaleInPlace(obj.vm.forward);
68                     v_temp.y=vl_now.y<=0?vl_now.y:0;
69                     obj.mesh.physicsImpostor.setLinearVelocity(v_temp);
70                     //obj.mesh.physicsImpostor.setAngularVelocity(new BABYLON.Vector3(0,0,0));//停下
71                 }
72                 else//正常向目标寻路格移动
73                 {
74                     console.log("普通寻路");
75                     var v_temp=new BABYLON.Vector3(count_x,0,count_y).subtract(new BABYLON.Vector3(obj.mesh.position.x,0,obj.mesh.position.z)).normalize().scaleInPlace(obj.vm.forward);
76                     v_temp.y=vl_now.y<=0?vl_now.y:0;
77                     obj.mesh.physicsImpostor.setLinearVelocity(v_temp);
78                     //obj.mesh.physicsImpostor.setAngularVelocity(new BABYLON.Vector3(0,0,0));//停下
79                 }
80
81             }
82         }
83         else
84         {
85             //obj.mesh.physicsImpostor.setMass(0);//不掉落
86         }
87     }
88
89
90
91 }

  这里分几种可能发生的运动情况(最常见的几种)分别设置被控物体的线速度,使得物体平稳的沿着路径运动,当物体到达目标时将进入sleep状态,当物体偏离路径时将进入lose状态,程序每秒钟检查一下物体是否lose,如果lose则重新寻路(没有测试过):

 1 _this.currentframet=new Date().getTime();
 2                 _this.DeltaTime=_this.currentframet-_this.lastframet;//取得两帧之间的时间
 3                 _this.lastframet=_this.currentframet;
 4                 _this.nohurry+=_this.DeltaTime;
 5                 if(MyGame&&_this.nohurry>1000)//每一秒进行一次导航修正
 6                 {
 7                     _this.nohurry=0;
 8                     if(_this.player.path_goto=="lose")//发现迷失了路途
 9                     {
10                         console.log("发现迷路,重新规划路径");
11                         var len_x=MyGame.arena.len_x;
12                         var len_y=MyGame.arena.len_y;
13                         var len_s=MyGame.arena.len_s;
14                         //场景坐标转化为方格坐标
15                         var count_x0=parseInt(_this.player.mesh.position.x/len_s+len_x/2);
16                         var count_y0=parseInt(-_this.player.mesh.position.z/len_s+len_y/2);
17                         var count_x=parseInt(_this.player.positiontogo[0]/len_s+len_x/2);
18                         var count_y=parseInt(-_this.player.positiontogo[1]/len_s+len_y/2);
19                         var path = finder.findPath(count_x0,count_y0 , count_x,count_y, MyGame.arena.grid.clone());//这些是寻路网格坐标
20                         var len=path.length;
21                         for(var i=0;i<len;i++)
22                         {//把方格坐标转化为场景坐标
23                             var obj=path[i];
24                             obj[0]=(obj[0]-len_x/2)*len_s;
25                             obj[1]=(-obj[1]+len_y/2)*len_s;
26                         }
27                         path.push(MyGame.player.positiontogo);
28                         path.shift();//把第一个出发节点去掉
29                         MyGame.player.path_goto=path;//在使用时在生成高度
30                         console.log("生成路径,起点:["+_this.player.mesh.position.x+","+_this.player.mesh.position.z+"]" +
31                             ",终点:["+_this.player.positiontogo[0]+","+_this.player.positiontogo[1]+"]");
32                     }
33                 }

  这样,我们就成功的完成了在WebGL场景中寻路的目标,接下来可以尝试修改pathfinding使之能根据不同地形进行加权寻路,以及控制多个单位进行寻路行为。

  

  

时间: 2024-10-03 23:00:43

在WebGL场景中使用2DA*寻路的相关文章

MMORPG大型游戏设计与开发(服务器 游戏场景 聊天管道和寻路器)

又快到双十一,又是不少同仁们出血的日子,首先希望大家玩的开心.我曾经想要仔细的剖析场景的的每个组件,就像这里的聊天管道与寻路器,但是仔细阅读别人代码的时候才发现元件虽小但是实现并不简单,因为有些东西还没有完全想明白我就暂时不说其具体的实现过程,但是我会保证这些文章在后面会不断更新,同时也希望对这方面有兴趣和经验的朋友们能够指正.聊天这个我们都知道,因为它几乎成了游戏或是生活中不可或缺的一部分,那么什么是寻路器?我想未必大家都知道什么是寻路器,不过我可以告诉大家的是你在游戏中自动寻路这个功能就是由

UnityEditor扩展编辑器实现从场景中渲染得到Cubemap

(学习笔记,希望能帮助到有需要的人.) 在自定义的EditorWindow中定义2个变量,分别代表需要渲染的Cubemap 和 视点对象(通常是Camera对象) private Cubemap cubemap; private GameObject obj; 在OnGUI 函数中 <span style="white-space:pre"> </span>this.cubemap = (Cubemap) EditorGUILayout.ObjectField

ActionScript3.0教你在影片剪辑中访问主场景中的变量

在ActionScript2.0中,影片剪辑访问主场景中的变量非常的简单,仅仅需要用一个带有_root的绝对路径即可. 然而在ActionScript3.0影片剪辑中访问主场景中的变量却没有那么容易,使用root将会报错.下面为大家介绍一种非常简单的方法来访问主场景中的变量. 方法如下: 1.在MC(影片剪辑实例名称)里定义一个变量father:Object; 2.在主场景代码中进行赋值 MC.father = this; 3.在影片剪辑里访问的时候就直接 father.变量名 就行.

Linux系统对分区的基本要求及企业生产场景中的分区方案

Linux系统对分区的基本要求 1.   最少要有一个根(/)分区,用来存放系统文件及程序,其大小至少在5GB\以上. 2.   要有一个swap(交换)分区,它的作用相当于Windows系统里的虚拟内存,swap分区的大小一般为物理内存容量的1.5倍(内存<8GB).但当系统物理内存大于8GB时,swap分区配置(8-16GB)即可,太大无用,浪费磁盘空间.swap分区不是必须的,但是大多数据情况还是设置比较好,个别企业的数据库应用场景不分swap分区. 3.   /boot分区,这是Linu

cocos2dx2.2.2登录场景中Checkbox选择框的实现

在前两篇文章中,我们介绍了在注册场景中需要用到的输入框及弹出框的实现方式,这两篇文章中介绍的内容在登录场景同样会用到.而我们经常会在登录场景中见到的另一种元素就是自动登录或者记住密码的Checkbox选择框.那么,接下来就让我们看看这个选择框如何实现. 首先,我们先看一下效果 我们需要的就是一个Checkbox选择框,后面加上“自动登录”或者其他的文字.效果就是点击选择框或文字时,Checkbox的状态会进行切换:同时在程序中还要知道当前选择框的状态,只要我们能够实现这几点,这个功能就完成了.

LoadRunner中Action的迭代次数的设置和运行场景中设置

LoadRunner中Action的迭代次数的设置和运行场景中设置 LoadRunner是怎么重复迭代和怎么增加并发运行的呢? 另外,在参数化时,对于一次压力测试中均只能用一次的资源应该怎么参数化呢?就是说这些资源用了一次就不能在用了的. --参数化时,在select  next row选择unique,update value on选择 each occurence, 1. 迭代跟虚拟用户数没什么必然联系 迭代是这样的: 迭代1次   迭代2次  迭代3次 用户1     X1        

向场景中加入光照

向场景中加入光照的4个步骤: 1)为每一个物体的每一个顶点计算法向量,法线确定了物体相对于光源的指向 法线的计算:设向量a(x1,y1,z1).向量b(x2,y2,z2) 则a×b=(x2·y3-x3·y2,x3·y1-x1·y3,x1·y2-x2·y1) 2)创建.选择并定位全部的光源 光源的创建: glLight*()函数能够定义光的一些属性--颜色.位置.方向 定义光时.此族函数最长用的一种形式是: void glLightfv(Glenum light,Glenum pname,TYPE

游戏开发之UE4添加角色到场景中

接着上次继续学习,现在我们已经有了一个场景并且运行了,我们需要添加一个角色到场景中.要这样做,我们必须从UE4的GameFramework类继承它. 一. 创建一个从Character类继承的类 从基本框架类继承是很简单的: 1) 在项目中打开你的UE4编辑器. 2) 在文件,选择"新建C++类". 3) 这里你可以选择从Pawn类(Pawn类从控制器接收输入)继承或从Actor类继承.不过我们可以这里选择从Character类(角色)继承. 4) 点击继续,然后你可以命名这个类.这里

查看系统版本、32位与64位生产场景中的使用及对比

一.查看系统版本 [[email protected] ~]# uname -a #系统版本详细信息 Linux ysolin 2.6.32-431.el6.x86_64 #1 SMP Fri Nov 22 03:15:09 UTC 2013 x86_64 x86_64 x86_64 GNU/Linux [[email protected] ~]# uname -r   #内核版本 2.6.32-431.el6.x86_64 [[email protected] ~]# uname -m   #