上篇介绍了InfluenceMap,如果不使用这些信息,那么也是浪费的,这篇我们来看一个InfluenceMap的应用。A*寻路——我们经常遇到的一个问题是,如何让AI从一个点到另一个点。通常两点之前线段最短在这里并不适用,有可能我们需要避开那些讨厌的障碍,有可能我们需要希望分散开来夹击敌人,我们需要避开影响图上友方信息密集的区域..............这时候我们就需要用到了A*寻路算法。
我们可以想象一下,你在棋盘的一个格子上,你要到棋盘的另一个格子上,你会怎么做?由于你没有看到全局的能力,多半你会选择,移动到当前收益最高的相邻点,如此递归下去,最终到达目的地。
如此我们得到了第一个A*寻路需要的概念——代价。每一个节点都会有一个代价,在每次移动操作中,我们都会倾向移动到代价最小的那个点。
好的,算法大致如上叙述。让我们用比较严谨的语言来叙述一下。
一开始我们维护一个开始节点,一个目标节点,一个开始列表,一个关闭列表。
1.添加开始节点进入进入开始列表。
2.如果开始列表中有节点。出列,检查是否是关闭列表中的节点,若是,重复这一过程。
3.检查该节点是否是目标节点,若是,退出。
4.计算该节点的相邻节点的代价,并将其添加到开始列表中。
5.添加该节点到目标路径中。
6.退回第2步。
来看看代码:
public class AStarNodeItem
{
public Vector3 pos;
public int x, y;
public int gCost;
public int hCost;
public int fCost
{
get
{
return gCost + hCost;
}
}
public AStarNodeItem parent;
public AStarNodeItem(Vector3 pos, int x, int y)
{
this.pos = pos;
this.x = x;
this.y = y;
}
public AStarNodeItem()
{
}
}
注意这里我们将代价定义成两部分,gcost,和相邻节点的距离,注意到九宫格中心离其他点距离有相邻和对角线的区别,另一个部分才是我们计算的主体。
再来看看,寻路部分的代码:
public Queue<Vector3> AStarFindPath(Vector3 src,Vector3 des,AStarComputer pAStarComputer)
{
PriorityQueue<AStarNodeItem> openList = new PriorityQueue<AStarNodeItem> (0,new AStarNodeItemCamparer());
List<AStarNodeItem> closedList = new List<AStarNodeItem> ();
Queue<Vector3> pathLast = new Queue<Vector3> ();
Vector2 start = getTilefromPosition (new Vector2(src.x,src.z));
Vector2 end = getTilefromPosition (new Vector2(des.x,des.z));
if (!(start.x == end.x && start.y == end.y))
{
AStarNodeItem AStarforStart = new AStarNodeItem (src,(int)start.x,(int)start.y);
AStarforStart.gCost = 0;
AStarforStart.hCost = pAStarComputer (src,des);
openList.Push (AStarforStart);
}
while(openList.Count!=0)
{
AStarNodeItem tAStarNodeItem = openList.Pop ();
while (closedList.Contains(tAStarNodeItem)&&!isWall(new Vector2(tAStarNodeItem.x,tAStarNodeItem.y)))
tAStarNodeItem = openList.Pop ();
if (tAStarNodeItem.x == end.x && tAStarNodeItem.y == end.y)
{
pathLast.Enqueue (tAStarNodeItem.pos);
return pathLast;
}
pathLast.Enqueue (tAStarNodeItem.pos);
closedList.Add (tAStarNodeItem);
openList.Clear ();
for (int i = 0; i < NineGrid.Length; i++)
{
Vector2 tv = new Vector2 (tAStarNodeItem.x+NineGrid[i].x,tAStarNodeItem.y+NineGrid[i].y);
Vector3 tp = GetPositionByTile (tv);
AStarNodeItem ttAStarNodeItem = new AStarNodeItem (tp,(int)tv.x,(int)tv.y);
ttAStarNodeItem.gCost = (int)(NineGrid [i].magnitude*10);
ttAStarNodeItem.hCost = pAStarComputer (tp,des);
openList.Push (ttAStarNodeItem);
}
}
return null;
}
此外,我们还提供了另一种参数形式:
public Queue<Vector3> AStarFindPath(Vector3 src,Vector3 des,AStarComputerformula pAStarComputerformula)
{
return AStarFindPath (src, des, pAStarComputerformula.EquationComputer);
}
这是在干什么呢?事实上,在计算hcost的时候,我们需要考虑的因素往往不止一种,所以这种参数形式就可以任何,你指定好一个表达式 des= srcA*A+srcB*B............................的形式,将若干种因素的计算按重要性混融出来,得到结果。
来看看AStarComputerformula的定义:
public class AStarComputerformula
{
List<AStarComputerformulaNode> mAStarComputerList = new List<AStarComputerformulaNode>() ;
float blendfuncSum = 0.0f;
public AStarComputer EquationComputer
{
get
{
return (Vector3 target, Vector3 des) =>
{
float rf = 0.0f;
foreach(var v in mAStarComputerList)
{
float tf = v.mAStarComputer(target,des);
rf+=tf*v.mBlendFunc/blendfuncSum;
}
return rf;
};
}
}
public void Add(AStarComputer pAStarComputer,float pBlendFunc)
{
AStarComputerformulaNode tAStarComputerformulaNode = new AStarComputerformulaNode ();
tAStarComputerformulaNode.mAStarComputer = pAStarComputer;
tAStarComputerformulaNode.mBlendFunc = pBlendFunc;
blendfuncSum += pBlendFunc;
}
};
这里我们使用了拉姆达表达式的用法,很简洁的算出了等价的计算函数。