需求背景
最近着手了一个使用Unity构建的2D手机游戏项目,项目服务端需要设计简单的AI,AI的基础是寻路,寻路依赖地图数据。
问题分析
Unity的3d开发环境中,原生自带了Navigation的组件,该组件可以快速的在Unity3D项目中生成NavMesh数据,并且同时支持Unity内置的寻路功能。但是对于Unity2D项目并没有类似的功能,所以得要自己想办法、造轮子。
需求也很明确:
1、能导出0/1方格地图数据提供给A*寻路,0表示区域可行走,1表示区域有障碍;(文末会提到为何不沿用NavMesh数据格式)
2、对于地图的变更,仅修改参数,不用修改代码,一键操作生成需要的地图数据文件;
3、能够兼容一些需求变更,比如击碎物,机关门之类;
着手解决
在Unity的AssetStore中搜索A*会出现很多相关的内容,下面经过实际操作并且推荐的是:
(写这篇博文时该插件售价100刀…)好在它的官网上有免费版本,最后经过使用后发现免费版本已经可以满足需求。
具体对样例操作步骤可以参考如下(演示环境是Unity_Windows64bit_5.3.5f1)。
一、Unity的下载、2D项目创建、导入我们下载的免费插件(详细步骤略),最终在Assets目录下多出目录:
二、接着我们选取里面自带的2D样例,双击打开Scene:
三、在Hierarchy标签页下选取A*这个GameObject,在Inspector这个标签页下会出现Astar Path (Script):
需要注意的是,若嵌套在自己的项目,例如你的项目地图信息都是挂载在Level(不要纠结于名字,Level只是官方的教程中经常使用来挂载的地图信息的GameObject名)下面,那么需要给Level增加一个Astar Path的脚本。或者你也可以参考样例中做法,新建一个名字为A*的GameObject,把脚本挂到A*下面。
第一次使用需要点击Add New Graph->Grid Graph来增加一个基于Grid的地图数据(上面的样例已经是添加好的)。下面说一下脚本里需要关心的配置:
Width(nodes) – 宽方向有多少节点(样例中的x轴方向);
Depth(nodes) – 高方向有多少节点(样例中的y轴方向);
node size – 节点的大小,默认是1m,用来调节精细度的主要指标;
Aspect Ratio – 节点的伸缩放,配合Isometric Angle使用,比较适合于Isometric Game配置,一般正交镜头2D项目默认1即可;
Center、Rotation – 用来标记在Scene中显示地图信息的位置,需要和项目地图在同一平行面(样例中就是与x,y轴的面平行,z轴可以随意调整);
Connections – 默认8即可,表示方格的8个连接方向;
Use 2D Physics – 勾选上;
Collider Type – 我在实际项目中选择了Sphere,因为默认的Ray出现了“意想不到的缝隙”,导致的原因应该是地图拼接的不太好,具体未查;
Diameter – 碰撞检测求的直径,若场景不太规则,可以把默认值1调小;
Mask – 设置成和地图的layer一样,默认是Default;
四、点击一下Astar Path脚本最下方的“Scan”按钮,出现红色的网格图,红点代表不可行走,红线代表可以行走:
五、地图数据的烘焙已经做完,接下来取出我们需要的数据:
导出的原理就是 – 既然红色的网格图插件都画出来了,那么它肯定以自定义的数据结构持有该地图数据,所以我们可以从自定义结构体中找到地图数据,然后将数据处理成需要的格式。所幸官网文档比较全(赞),里面对于自定义结构体的文档比较清晰,可以节约不少人力。
下面是导出的C#代码,可以将其放在AstarPathfindingEditor\Editor目录下。
//file:exportMap.cs using UnityEngine; using System.Collections; using UnityEditor; using System.IO; using UnityEngine.SceneManagement; using Pathfinding; public class ExportMap { [MenuItem("Tools/Export Map")] static void Export() { //新建地图文件路径 string tmpPath = Application.dataPath + "/" + SceneManager.GetActiveScene().name + ".txt"; StreamWriter tmpStreamWriter = new StreamWriter(tmpPath); AstarData data = AstarPath.active.astarData; GridGraph grid = data.gridGraph; //文件第一行:录入文件说明 x列有多少格子,y行有多少格子,每个格子的长x,每个格子的高y tmpStreamWriter.WriteLine(grid.width + " " + grid.depth + " " + grid.nodeSize + " " + grid.nodeSize); //文件后续行:录入节点信息 string linecontent = ""; for (int i = 0; i < grid.CountNodes(); i++) { GridNode node = grid.nodes[i]; if ((i + 1) % grid.width == 0) { linecontent += (node.Walkable ? "0" : "1") + " "; tmpStreamWriter.WriteLine(linecontent); linecontent = ""; } else { linecontent += (node.Walkable ? "0" : "1") + " "; } } tmpStreamWriter.WriteLine(); tmpStreamWriter.Flush(); tmpStreamWriter.Close(); } }
六、点击Tools->Export Map生成地图文件
进入项目文件夹会发现Assets目录下会出现2D.txt,该文件就是我们需要的地图数据。
关于NavMesh
NavMesh的核心数据结构在Unity中是NavMeshTriangulation,其包含3大数据块:
areas – 区块数组,数组长度等于indices的个数;
indices – 连接图,表示那些顶点组成了一个区块;
vertices – 顶点信息,核心下标索引与x,y,z的坐标;
这是一个很明显连接图,用来做AI寻路是没有问题的,但是一旦涉及到了障碍物打碎之类的需求感觉有点棘手 – 障碍物打碎会打乱原来的区块与顶点,这些在unity中可以动态烘焙生成新的连接图,但是非Unity服务端很难处理(得要研究一系列关于NavMesh相关的算法)。若是0/1方格地图,理想情况下我把障碍物的坐标点从1->0就完成了修改,这是另一个重要的原因选择了0/1方格地图。
至此地图数据导出的工作就完成了,后续你有100种方法把地图数据文件生成/移动到服务端目录,也有至少10种方法读取需要的数据然后使用,这里就不累述了。
(全文结束)
转载文章请注明出处:漫漫路 - lanindex.com