在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渲染引擎中自定义地面网格
var vdata_ground=new BABYLON.VertexData.CreateGround({width:198,height:198,subdivisionsX:99,subdivisionsY:99});//分成了99段,一条边上100个顶点
var arr_vposition=vdata_ground.positions;
var map=[];
var len=arr_vposition.length/3;//和挨个遍历比起来,似乎有目的的去找更好
for(var i=20;i<81;i++)//对于这个范围内的行
{
for(var j=20;j<23;j++)//对于这个范围内的列
{
arr_vposition[(j+100*i)*3+1]=1;
}
}
for(var i=20;i<81;i++)//对于这个范围内的行
{
for(var j=40;j<43;j++)//对于这个范围内的列
{
arr_vposition[(j+100*i)*3+1]=2;
}
}
for(var i=20;i<81;i++)//对于这个范围内的行
{
for(var j=60;j<63;j++)//对于这个范围内的列
{
arr_vposition[(j+100*i)*3+1]=4;
}
}
BABYLON.VertexData._ComputeSides(0, arr_vposition, vdata_ground.indices, vdata_ground.normals
, vdata_ground.uvs);
var mesh_ground=new BABYLON.Mesh("mesh_ground",scene);
mesh_ground.renderingGroupId=2;
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库需要用一个矩阵(二维数组)来定义障碍物的位置,其中零元素表示这个地块可以通行,不为零的元素表示无法通行,下面是建立这个数组的方法:
mesh_ground.mydata={};
mesh_ground.mydata.walkabilityMatrix=MakewalkabilityMatrix(arr_vposition,99,99,2);
mesh_ground.mydata.len_x=99;
mesh_ground.mydata.len_y=99;
mesh_ground.mydata.len_s=2; 。
。
。 //对每个正方形区块的倾斜程度进行计算,得出是否可以通行
//顶点数据,寻路空间宽度,寻路空间高度,每个方格区域的边长
function MakewalkabilityMatrix(arr,len_x,len_y,len_s)
{
var arr_Matrix=numeric.rep([len_y,len_x],0);
var len_s2=len_s*0.707;//求得平均点到其中一个边线点的水平距离0.7071067811865476
//var len_s2a=len_s2;
//var len_s2b=len_s2;
//var len_s2c=len_s2;
for(var i=0;i<len_y;i++)//对于每一行寻路单元格
{
for(var j=0;j<len_x;j++)//对于这一行里的每一个单元格
{
var int1=j+i*(len_x+1);
var int2=j+i*(len_x+1)+1;
var int3=j+(i+1)*(len_x+1);
var int4=j+(i+1)*(len_x+1)+1;
var y1=arr[int1*3+1];
var y2=arr[int2*3+1];
var y3=arr[int3*3+1];
var y4=arr[int4*3+1];
var ya=(y1+y2+y3+y4)/4;
var yb=Math.max(Math.abs(y1-ya),Math.abs(y2-ya),Math.abs(y3-ya),Math.abs(y4-ya));
arr_Matrix[i][j]=parseInt((yb)/0.707);//高度超过了水平距离几倍就认为是几倍的障碍物
}
}
return arr_Matrix;
}
这一段代码的思路是:将地面网格垂直方向的正投影作为寻路单元格,取每一个寻路单元格的四个顶点,算出这四个顶点的平均高度与每个顶点高度的差的最大值与寻路单元格中心点到顶点的水平距离的比,将这个比值作为“障碍程度”(简单来说就是顶点高度变化的越剧烈,障碍就越难跨越)。然后为网格添加一个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的代码:
/**
* Created by Administrator on 2017/7/14.
*/
function Export_mesh(arr_mesh,PngName)//用Babylon格式导出模型
{
//场景对象
var obj_scene=
{
'autoClear': true,
'clearColor': [0,0,0],
'ambientColor': [0,0,0],
'gravity': [0,-9.81,0],
'cameras': [{
'name': 'Camera',
'id': 'Camera',
'position': [7.4811,5.3437,-6.5076],
'target': [-0.3174,0.8953,0.3125],
'fov': 0.8576,
'minZ': 0.1,
'maxZ': 100,
'speed': 1,
'inertia': 0.9,
'checkCollisions': false,
'applyGravity': false,
'ellipsoid': [0.2,0.9,0.2]
}],
'activeCamera': 'Camera',
'lights': [{
'name': 'Sun',
'id': 'Sun',
'type': 1,
'position': [0.926,7.3608,14.1829],
'direction': [-0.347,-0.4916,-0.7987],
'intensity': 1,
'diffuse': [1,1,1],
'specular': [1,1,1]
}],
'materials':[{
'name': 'mball',
'id': 'mball',
'ambient': [1,1,1],
'diffuse': [1,1,1],
'specular': [1,1,1],
'specularPower': 50,
'emissive': [0,0,0],
'alpha': 1,
'backFaceCulling': true,
'diffuseTexture': {
'name': PngName?PngName:'snow2.jpg',
'level': 1,
'hasAlpha': 1,
'coordinatesMode': 0,
'uOffset': 0,
'vOffset': 0,
'uScale': 1,
'vScale': 1,
'uAng': 0,
'vAng': 0,
'wAng': 0,
'wrapU': true,
'wrapV': true,
'coordinatesIndex': 0
}
}],
'geometries': {},
'meshes': [],
'multiMaterials': [],
'shadowGenerators': [],
'skeletons': [],
'sounds': [],
'mydata':{'walkabilityMatrix':[]}
};
//所有模型组件的父物体
var obj_allbase=
{
'name': 'allbase',
'id': 'allbase',
'materialId': 'mball',
'position': [0,0,0],
'rotation': [0,0,0],
'scaling': [1,1,1],
'isVisible': true,
'isEnabled': true,
'checkCollisions': false,
'billboardMode': 0,
'receiveShadows': true,
'positions': [],
'normals': [],
'uvs': [],
'indices': [],
'subMeshes': [{
'materialIndex': 0,
'verticesStart': 0,
'verticesCount': 0,
'indexStart': 0,
'indexCount': 0
}]
};
obj_scene.meshes.push(obj_allbase);
var len=arr_mesh.length;
var all_x=0;
var all_y=0;
var all_z=0;
for(var i=0;i<len;i++)
{
var obj_child={};
if(arr_mesh[i].geometry._vertexBuffers!=null)
{
var child=arr_mesh[i];
if(!child.mydata)
{
child.mydata={}
}
var vb=child.geometry._vertexBuffers;
all_x+=child.position.x;
all_y+=child.position.y;
all_z+=child.position.z;
obj_child=
{
'name': child.name,
'id': child.id,
'parentID': 'allbase',
'materialId': 'mball',
'position': [child.position.x,child.position.y,child.position.z],
'rotation': [child.rotation.x,child.rotation.y,child.rotation.z],
'scaling': [child.scaling.x,child.scaling.y,child.scaling.z],
'isVisible': true,
'isEnabled': true,
'checkCollisions': false,
'billboardMode': 0,
'receiveShadows': true,
'positions': vb.position._buffer._data,
'normals': vb.normal._buffer._data,
'uvs': vb.uv._buffer._data,
'indices': child.geometry._indices,
'subMeshes': [{
'materialIndex': 0,
'verticesStart': 0,
'verticesCount': vb.position._buffer._data.length,
'indexStart': 0,
'indexCount': child.geometry._indices.length
}],
'mydata':child.mydata
};
obj_scene.meshes.push(obj_child);
}
}
//不能让模型的主体过于偏离模型的中心
all_x=all_x/len;
all_y=all_y/len;
all_z=all_z/len;
for(var i=1;i<len+1;i++)
{
obj_scene.meshes[i].position[0]-=all_x;
obj_scene.meshes[i].position[1]-=all_y;
obj_scene.meshes[i].position[2]-=all_z;
}
var str_data=JSON.stringify(obj_scene);
DownloadText(MakeDateStr()+"testscene",str_data,".babylon");
}
可以看出,一个babylon文件可以包含多个网格对象,除了网格对象之外这个模型文件还可以存储场景、光照、相机、动画、骨骼等信息,这些功能可以选择性使用。方法的最后使用DownloadText方法将json文本导出,DownloadText是我参考网络资料编写的字符下载方法,如果不使用DownloadText,直接在Chrome浏览器的调试模式下的命令行里输入“console.log(str_data)”也能得到json字符串。
DownloadText内容如下:
/**
* Created by Administrator on 2015/3/2.
*/
/**
* 将指定字符写入指定名称的文本文件中,并可以选择本地保存目录,兼容IE11和谷歌浏览器
*/
function DownloadText(filename,content,filetype)
{
if(filetype==null)
{
filetype=".txt";
}
if(document.createElement("a").download!=null)//谷歌和火狐
{
var aLink = document.createElement('a');
var datatype="data:text/plain;charset=UTF-8,";
if(filetype==".xml")
{
datatype="data:text/xml;charset=UTF-8,";
}
if(filetype==".babylon")
{//浏览器还没有支持babylon的mime类型!!
datatype="data:text/plain;charset=UTF-8,";
}
if(filetype==".png"||filetype==".jpeg")
{
datatype="";
}
if(content.length<1000000)
{
aLink.href = datatype+content;//dataurl格式的字符串"
}
else
{//对于过大的文件普通dataURL不支持,所以使用“二进制流大对象”
aLink.href=URL.createObjectURL(new Blob([content],{type:"text/plain"}));
}
aLink.download = filename;
aLink.innerHTML=filename;
//aLink.setAttribute("onclick","");
aLink.onclick=function()
{
document.getElementById("div_choose").style.display="none";
//delete_div('div_choose');
delete_div('div_mask');
}
//aLink.style.display="none";
//document.body.appendChild(aLink);
/*var evt = document.createEvent("HTMLEvents");//建立一个事件
evt.initEvent("click", false, false);//这是一个单击事件
evt.eventType = 'message';
aLink.dispatchEvent(evt);//触发事件*/
//chrome认为点击超链接下载文件是超链接标签的“默认属性”,谷歌认为默认属性不可以用脚本来触发,所以从M53版本开始dispatchEvent无法触发超链接下载
//window.open(datatype+content, "_blank");
//document.write(datatype+content);
delete_div('div_choose');
delete_div('div_mask');
var evt=evt||window.event;
cancelPropagation(evt);
var obj=evt.currentTarget?evt.currentTarget:evt.srcElement; Open_div("", "div_choose", 240, 180, 400, 80, "", "",1,401);//打开一个带遮罩的弹出框
var div_choose=$("#div_choose")[0];
div_choose.style.border="1px solid";
div_choose.innerHTML="<span>谷歌浏览器专用文件生成完毕,请点击下面的文件名下载文件。</span><br>"
div_choose.appendChild(aLink);
drag(div_choose);//让弹出框可以被拖拽
aLink.onmousedown=function()
{
var evt=evt||window.event;
cancelPropagation(evt);
}
}
else//IE
{
var Folder=BrowseFolder();
if(Folder=="false")
{
alert("保存失败!");
}
else
{
var fso, tf;
fso = new ActiveXObject("Scripting.FileSystemObject");//创建文件系统对象
tf = fso.CreateTextFile(Folder + filename+filetype, true,true);//创建一个文件
tf.write(content);
tf.Close();
alert("保存完毕!");
}
}
}
function BrowseFolder()
{//使用ActiveX控件
try
{
var Message = "请选择保存文件夹"; //选择框提示信息
var Shell = new ActiveXObject( "Shell.Application" );
var Folder = Shell.BrowseForFolder(0,Message,0x0040,0x11);//起始目录为:我的电脑
//var Folder = Shell.BrowseForFolder(0,Message,0); //起始目录为:桌面//选择桌面会报错!! if(Folder != null)
{
Folder = Folder.items(); // 返回 FolderItems 对象
Folder = Folder.item(); // 返回 Folderitem 对象
Folder = Folder.Path; // 返回路径
if(Folder.charAt(Folder.length-1) != "\\")
{
Folder = Folder + "\\";
}
//document.all.savePath.value=Folder;
return Folder;
}
}
catch(e)
{
return "false";
alert(e.message);
}
}
接下来,我们要在另一个程序中使用上面生成的模型文件,使用Babylonjs的资源管理器加载网格:
this.loader = new BABYLON.AssetsManager(this.scene);//资源管理器 // 资源数组
this.assets = {};
//为资源管理器分配一个任务
var meshTask = this.loader.addMeshTask("gun", "", "./assets/", "gun.babylon");
meshTask.onSuccess = function(task) {//这个任务完成
_this._initMesh(task);
};
//第一个参数表示task的name,第二个参数表示加载模型文件中的哪个网格,为空则用数组形式加载全部,第三个参数表示路径,第四个参数是文件名
var meshTask2 = this.loader.addMeshTask("mesh_ground", "", "./assets/arena/", "2017810_14_12_59testscene.babylon");
meshTask2.onSuccess = function(task) {
_this._initMesh(task);
}; this.loader.onFinish = function (tasks)//所有任务完成
{
。。。
} 。
。
。 _initMesh : function(task)
{
this.assets[task.name] = task.loadedMeshes;
for (var i=0; i<task.loadedMeshes.length; i++ ){
var mesh = task.loadedMeshes[i];
mesh.isVisible = false;
//预先把所有资源加载下来,但不显示,当需要时再把它显示在需要的位置,或者在需要的位置,建立一个资源的实例(克隆)
}
}
这时,会发生一个小问题:Babylonjs并不支持我们夹带在mesh中的mydata属性。解决方法是在babylon.30.all.max.js的21272行附近修改:
if (parsedMesh.metadata !== undefined) {
mesh.metadata = parsedMesh.metadata;
}
if (parsedMesh.mydata !== undefined) {
mesh.mydata = parsedMesh.mydata;
}
仿照metadata的写法加上对mydata的支持,当然,也可以考虑把mydata夹带到其他被mesh所支持的属性里。
3、使用pathfinding库进行2D寻路
pathfindingjs是一个开源2D寻路库,可以在https://github.com/qiao/PathFinding.js下载完整代码和文档,可以在http://qiao.github.io/PathFinding.js/visual/在线试验各种寻路方式
pathfinding的基本用法如下:
var finder = new PF.AStarFinder({//“寻路器”
diagonalMovement: 3
}); 。
this.grid=new PF.Grid(this.len_x,this.len_y,this.walkabilityMatrix);//生成寻路网格 。 function FindWaytogo(pickResult)//pickResult是Babylonjs中定义的“鼠标选取结果”对象
{
var faceId=pickResult.faceId;//点击了网格中的第几个面
var pickedMesh=pickResult.pickedMesh;//被点击的网格
var px=MyGame.player.mesh.position.x;//被控对象在场景中的水平位置
var py=MyGame.player.mesh.position.z; var len_x=MyGame.arena.len_x;//寻路网格的格数和每格的长度
var len_y=MyGame.arena.len_y;
var len_s=MyGame.arena.len_s;
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的障碍矩阵
{
var arr_matrix=MyGame.arena.walkabilityMatrix;//寻路矩阵
var count=parseInt(faceId/2);//第几个方格
//接下来要把网格的面转换为寻路方格的坐标,后面还要把寻路方格的坐标转换为scene中的位置
//面数转换为方格坐标
var count_y=parseInt(count/len_x);
var count_x=count%len_x;
//场景坐标转化为方格坐标
var count_x0=parseInt(px/len_s+len_x/2);
var count_y0=parseInt(-py/len_s+len_y/2); //寻路,返回一个由方格坐标组成的数组
var path = finder.findPath(count_x0,count_y0 , count_x,count_y, MyGame.arena.grid.clone());//这些是寻路网格坐标
var len=path.length;
for(var i=0;i<len;i++)
{//把方格坐标转化为场景坐标
var obj=path[i];
obj[0]=(obj[0]-len_x/2)*len_s;
obj[1]=(-obj[1]+len_y/2)*len_s;
} path.push([pickResult.pickedPoint.x,pickResult.pickedPoint.z]);
MyGame.player.path_goto=path;//在使用时在生成高度
MyGame.player.positiontogo=[pickResult.pickedPoint.x,pickResult.pickedPoint.z];
path.shift();//把第一个出发节点去掉
console.log("生成路径,起点:["+px+","+py+"],终点:["+pickResult.pickedPoint.x+","+pickResult.pickedPoint.z+"]");
}
}
这样,我们就把场景中的位置对应成了寻路网格中的位置,然后使用pathfinding生成了2D路径。需要注意的是pathfinding中的grid对象只能使用一次,再次寻路时需要重新生成grid或者使用grid的克隆对象。
4、基于cannon.js物理引擎沿路径移动
接下来需要让被控物体沿着指定的路径运动,为了能让物体在凹凸不平的地形中运动时保持紧贴地面,我在这里使用了cannonjs物理引擎(关于物理引擎的用法可以参考上一篇文章)。经过试验,这个版本的cannonjs的单个物理仿真器最多支持对10000个顶点的物理仿真,所以前文没法把地面网格分成更多段。
我们在这个场景中监听“右键点击地面”的事件,代码如下:
canvas.addEventListener("click", function(evt) {
var width = engine.getRenderWidth();
var height = engine.getRenderHeight();
var pickInfo = scene.pick(width/2, height/2, null, false, _this.camera);//点击信息
if(evt.button==2)//右键单击
{
cancelEvent(evt);//阻止默认响应
if(pickInfo.hit&&pickInfo.pickedMesh.name=="mesh_ground")//点击到了地面上
{
MyGame.player.mesh.physicsImpostor.setMass(70);//给被控物体赋予质量,这样它才可以下落
FindWaytogo(pickInfo);//在玩家到点击目的地之间找到一条路径
var mesh_togo=BABYLON.Mesh.CreateBox("box", 1, scene);//目标方块
mesh_togo.position = pickInfo.pickedPoint.clone();//pickResult.pickedPoint
mesh_togo.renderingGroupId=2;
MyGame.player.mesh_togo=mesh_togo;
}
} }, false);
然后在每次渲染之前执行以下运动方法:
scene.registerBeforeRender(function() {
if(MyGame.flag_startr==1)//如果开始渲染了
{
if(MyGame.flag_view=="first"||MyGame.flag_view=="third")
{
physics20170725(MyGame.player);
}
if(MyGame.flag_view=="free")
{
pathgoto20170808(MyGame.player);
}
}
});
function pathgoto20170808(obj)//obj是player
{
if(true)
//if(obj.standonTheGround==1)//站在地面上时考虑将质量设为0?
{
if(obj.path_goto!="sleep"&&obj.path_goto!="lose")
{
var len_x=MyGame.arena.len_x;
var len_y=MyGame.arena.len_y;
var len_s=MyGame.arena.len_s;
var vl_now=obj.mesh.physicsImpostor.getLinearVelocity(); if(obj.path_goto.length>0)
{
var px=obj.mesh.position.x;//全是场景坐标!!
var py=obj.mesh.position.z;
var count_x0=px;
var count_y0=py;
var count_x=obj.path_goto[0][0];
var count_y=obj.path_goto[0][1];
var len=obj.path_goto.length;
var count_x2=obj.path_goto[len-1][0];
var count_y2=obj.path_goto[len-1][1];
var y_obj=obj.mesh.position.y;
if((Math.pow(count_x0-count_x2,2)+Math.pow(count_y0-count_y2,2))<0.25*len_s*len_s)
{//在移动过程中因未知原因跳到距终点0.5以内距离的地方,直接寻找最终点
console.log("在最终格内");
if((Math.pow(count_x0-count_x2,2)+Math.pow(count_y0-count_y2,2))<0.01*len_s*len_s)//到达0.1距离以内的地方,认为到达最终目标,直接定位
{ obj.mesh.position.x=count_x;
obj.mesh.position.z=count_y;
obj.path_goto="sleep";
console.log("到达最终目标:["+obj.mesh.position.x+","+obj.mesh.position.y+","+obj.mesh.position.z+"]");
obj.mesh.physicsImpostor.setMass(0);//质量设为零将不会下落
obj.mesh_togo.dispose();
obj.mesh.physicsImpostor.setAngularVelocity(new BABYLON.Vector3(0,0,0));//停下
obj.mesh.physicsImpostor.setLinearVelocity(new BABYLON.Vector3(0,0,0)); }
else{
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);
v_temp.y=vl_now.y<=0?vl_now.y:0;//这个单位应该脚踏实地的平稳运动
obj.mesh.physicsImpostor.setLinearVelocity(v_temp);
//obj.mesh.physicsImpostor.setAngularVelocity(new BABYLON.Vector3(0,0,0));//停下
if(obj.path_goto.length>1)
{
obj.path_goto=[obj.path_goto[len-1]];//只剩一个最终目标
}
}
}
else if((Math.pow(count_x0-count_x,2)+Math.pow(count_y0-count_y,2))>4*len_s*len_s)
{//在移动过程中因未知原因跳到距下个目标格2以外距离的地方,需要重新寻路,这种计算可能耗时较大,不能每帧执行!!
obj.path_goto="lose";
//obj.mesh.physicsImpostor.setMass(0);//不掉落
obj.mesh.physicsImpostor.setLinearVelocity(new BABYLON.Vector3(0,0,0));
obj.mesh.physicsImpostor.setAngularVelocity(new BABYLON.Vector3(0,0,0));//停下
return false;
}
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)
{//距离下一寻路格足够近,切换下一寻路格 obj.path_goto.shift();
count_x=obj.path_goto[0][0];
count_y=obj.path_goto[0][1];
console.log("切换下一个寻路单元格:["+count_x+","+count_y+"]");
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);
v_temp.y=vl_now.y<=0?vl_now.y:0;
obj.mesh.physicsImpostor.setLinearVelocity(v_temp);
//obj.mesh.physicsImpostor.setAngularVelocity(new BABYLON.Vector3(0,0,0));//停下
}
else//正常向目标寻路格移动
{
console.log("普通寻路");
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);
v_temp.y=vl_now.y<=0?vl_now.y:0;
obj.mesh.physicsImpostor.setLinearVelocity(v_temp);
//obj.mesh.physicsImpostor.setAngularVelocity(new BABYLON.Vector3(0,0,0));//停下
} }
}
else
{
//obj.mesh.physicsImpostor.setMass(0);//不掉落
}
} }
这里分几种可能发生的运动情况(最常见的几种)分别设置被控物体的线速度,使得物体平稳的沿着路径运动,当物体到达目标时将进入sleep状态,当物体偏离路径时将进入lose状态,程序每秒钟检查一下物体是否lose,如果lose则重新寻路(没有测试过):
_this.currentframet=new Date().getTime();
_this.DeltaTime=_this.currentframet-_this.lastframet;//取得两帧之间的时间
_this.lastframet=_this.currentframet;
_this.nohurry+=_this.DeltaTime;
if(MyGame&&_this.nohurry>1000)//每一秒进行一次导航修正
{
_this.nohurry=0;
if(_this.player.path_goto=="lose")//发现迷失了路途
{
console.log("发现迷路,重新规划路径");
var len_x=MyGame.arena.len_x;
var len_y=MyGame.arena.len_y;
var len_s=MyGame.arena.len_s;
//场景坐标转化为方格坐标
var count_x0=parseInt(_this.player.mesh.position.x/len_s+len_x/2);
var count_y0=parseInt(-_this.player.mesh.position.z/len_s+len_y/2);
var count_x=parseInt(_this.player.positiontogo[0]/len_s+len_x/2);
var count_y=parseInt(-_this.player.positiontogo[1]/len_s+len_y/2);
var path = finder.findPath(count_x0,count_y0 , count_x,count_y, MyGame.arena.grid.clone());//这些是寻路网格坐标
var len=path.length;
for(var i=0;i<len;i++)
{//把方格坐标转化为场景坐标
var obj=path[i];
obj[0]=(obj[0]-len_x/2)*len_s;
obj[1]=(-obj[1]+len_y/2)*len_s;
}
path.push(MyGame.player.positiontogo);
path.shift();//把第一个出发节点去掉
MyGame.player.path_goto=path;//在使用时在生成高度
console.log("生成路径,起点:["+_this.player.mesh.position.x+","+_this.player.mesh.position.z+"]" +
",终点:["+_this.player.positiontogo[0]+","+_this.player.positiontogo[1]+"]");
}
}
这样,我们就成功的完成了在WebGL场景中寻路的目标,接下来可以尝试修改pathfinding使之能根据不同地形进行加权寻路,以及控制多个单位进行寻路行为。
在WebGL场景中使用2DA*寻路的更多相关文章
- 在WebGL场景中管理多个卡牌对象的实验
这篇文章讨论如何在基于Babylon.js的WebGL场景中,实现多个简单卡牌类对象的显示.选择.分组.排序,同时建立一套实用的3D场景代码框架.由于作者美工能力有限,所以示例场景视觉效果可能欠佳,本 ...
- 在WebGL场景中进行棋盘操作的实验
这篇文章讨论如何在基于Babylon.js的WebGL场景中,建立棋盘状的地块和多个可选择的棋子对象,在点选棋子时显示棋子的移动范围,并且在点击移动范围内的空白地块时向目标地块移动棋子.在这一过程中要 ...
- 在WebGL场景中建立游戏规则
在前三篇文章的基础上,为基于Babylon.js的WebGL场景添加了类似战棋游戏的基本操作流程,包括从手中选择单位放入棋盘.显示单位具有的技能.选择技能.不同单位通过技能进行交互.处理交互结果以及进 ...
- 原生WebGL场景中绘制多个圆锥圆柱
前几天解决了原生WebGL开发中的一个问题,就是在一个场景中绘制多个几何网格特征不同的模型,比如本文所做的绘制多个圆锥和圆柱在同一个场景中,今天抽空把解决的办法记录下来,同时也附上代码.首先声明,圆柱 ...
- 基于 HTML5 WebGL 的 3D 场景中的灯光效果
构建 3D 的场景除了创建模型,对模型设置颜色和贴图外,还需要有灯光的效果才能更逼真的反映真实世界的场景.这个例子我觉得既美观又代表性很强,所以拿出来给大家分享一下. 本例地址:http://www. ...
- MMORPG大型游戏设计与开发(服务器 游戏场景 聊天管道和寻路器)
又快到双十一,又是不少同仁们出血的日子,首先希望大家玩的开心.我曾经想要仔细的剖析场景的的每个组件,就像这里的聊天管道与寻路器,但是仔细阅读别人代码的时候才发现元件虽小但是实现并不简单,因为有些东西还 ...
- THREE.JS(如何想场景中添加物体对象)
这篇主要实现向模型对象中添加头像,并组成一个矩形 一.three.js是什么? 上篇说了点TWEEN这篇又来一根THREE是不是两兄弟啊?还真有点像,当想要做3D动画的时候,可能会考虑用TWEEN的动 ...
- WebGL场景的两种地面构造方法
总述:大部分3D编程都涉及到地面元素,在场景中我们使用地面作为其他物体的承载基础,同时也用地面限制场景使用者的移动范围,还可以在通过设置地块的属性为场景的不同位置设置对应的计算规则.本文在WebGL平 ...
- 第七章 人工智能,7.6 DNN在搜索场景中的应用(作者:仁重)
7.6 DNN在搜索场景中的应用 1. 背景 搜索排序的特征分大量的使用了LR,GBDT,SVM等模型及其变种.我们主要在特征工程,建模的场景,目标采样等方面做了很细致的工作.但这些模型的瓶颈也非常的 ...
随机推荐
- 4.jsp的内置对象
1.jsp有九大内置对象 out request response session application page pagecontext exception config 2.用户发请求 requ ...
- SVN仓库迁移到Git遇到的两个问题和解决办法
OS: CentOS 7.0 准备: git svn git-svn sudo yum install git sudo yum install subversion sudo yum install ...
- Spring MVC 项目搭建 -3- 快速 添加 spring security
Spring MVC 项目搭建 -3- 快速 添加 spring security 1.添加 spring-sample-security.xml <!-- 简单的安全检验实现 --> & ...
- axis1.4开发webservice客户端(快速入门)-基于jdk1.4
写在前面: 对于客户端,服务端开发好了以后,客户端只需要调用就可以了.这里我们讲的是,根据服务的访问地址,来生成客户端所需要用到的代码(听说有几种调用方式,但是用到最常见的就是stub方式,貌似我说的 ...
- maven(一) maven到底是个啥玩意~
我记得在搞懂maven之前看了几次重复的maven的教学视频.不知道是自己悟性太低还是怎么滴,就是搞不清楚,现在弄清楚了,基本上入门了.写该篇博文,就是为了帮助那些和我一样对于maven迷迷糊糊的人. ...
- MapReduce执行流程及程序编写
MapReduce 一种分布式计算模型,解决海量数据的计算问题,MapReduce将计算过程抽象成两个函数 Map(映射):对一些独立元素(拆分后的小块)组成的列表的每一个元素进行指定的操作,可以高度 ...
- Threejs 开发3D地图实践总结
前段时间连续上了一个月班,加班加点完成了一个3D攻坚项目.也算是由传统web转型到webgl图形学开发中,坑不少,做了一下总结分享. 1.法向量问题 法线是垂直于我们想要照亮的物体表面的向量.法线代表 ...
- Day2_数字类型_字符串类型_列表类型_元组_字典_集合_字符编码_文件处理
数字类型: 作用:年纪,等级,薪资,身份证号等: 10进制转为2进制,利用bin来执行. 10进制转为8进制,利用oct来执行. 10进制转为16进制,利用hex来执行. #整型age=10 prin ...
- MySQL学习笔记(三)
--回顾 字段类型(列类型):数值型,时间日期型和字符串类型 数值型:整型和小数型(浮点型和定点型) 时间日期型:datetime,date,time,timestamp,year 字符串类型:定长, ...
- FTP DOS 命令行
1. 在cmd--> 输入ftp 2. 进入ftp输入提示命令行,此时输入open ftp服务器地址,比如我的是本机就: open 127.0.0.1 3. 根据提示输入用户名和密码, 提示登录 ...