D3D游戏编程系列(三):自己动手编写即时战略游戏之寻路
说起即时战略游戏,不得不提的一个问题是如何把一个物体从一个位置移动到另一个位置,当然,我说的不是瞬移,而是一个移动的过程,那么在这个移动的过程中我们如何来规划路线呢,这就不得不提到寻路了。
我所了解到的寻路算法有很多,当然我还是向大家推荐A*算法,这个应该是目前在八个方向上效率最高的寻路算法了吧,在这里,我不准备详细的去介绍这个算法的原理,给大家一个链接,http://www.cnblogs.com/technology/archive/2011/05/26/2058842.html,这是我在网上看到的我个人认为有关A*算法最好的讲解了。
好了,废话不多说,我给出在4个方向上的算法代码(上下左右,因为我的游戏里就是在四个方向上移动,八个方向类似)。
void MyWin::FindPath(sEleObj* Ele) { static sPathNode EleMem[1024*1024]; CPoint desPoint=Ele->DesPos; if((m_MapInfo[desPoint.y][desPoint.x]<200 && m_MapInfo[desPoint.y][desPoint.x]!=Ele->ID) || (m_MapInfo[desPoint.y+Ele->CurPos.Height()-10][desPoint.x]<200 && m_MapInfo[desPoint.y+Ele->CurPos.Height()-10][desPoint.x]!=Ele->ID) || (m_MapInfo[desPoint.y][desPoint.x+Ele->CurPos.Width()-10]<200 && m_MapInfo[desPoint.y][desPoint.x+Ele->CurPos.Width()-10]!=Ele->ID) || (m_MapInfo[desPoint.y+Ele->CurPos.Height()-10][desPoint.x+Ele->CurPos.Width()-10]<200 && m_MapInfo[desPoint.y+Ele->CurPos.Height()-10][desPoint.x+Ele->CurPos.Width()-10]!=Ele->ID)) { Ele->VecPath.clear(); m_FindPathEleList.remove(Ele); m_DynamicFindPathEleList.remove(Ele); m_DynamicFindPathEleList.push_back(Ele); return; } sPathNode *node=new sPathNode; node->Cur=Ele->CurPos.TopLeft(); node->F=abs(desPoint.x-node->Cur.x)+abs(desPoint.y-node->Cur.y); node->Par=0; m_OpenListSet.insert(node); int count=0; int pos=0; set<DWORD> PointMap; #define MAKEDWORD( wLow, wHigh ) ((LONG)(((WORD)(wLow)) | ((DWORD)((WORD)(wHigh))) << 16)) while(1) { if(++count==1000) { break; } if(m_OpenListSet.empty()) { break; } sPathNode *MinNode=*m_OpenListSet.begin(); m_OpenListSet.erase(m_OpenListSet.begin()); m_CloseListSet.insert(MinNode); //cout<<MinNode->Cur.x<<" "<<MinNode->Cur.y<<endl; node=MinNode; CPoint TopPoint; TopPoint.x=node->Cur.x; TopPoint.y=node->Cur.y-10; if(TopPoint.y>=0) { CPoint TempPoint; TempPoint.x=TopPoint.x+Ele->CurPos.Width()-10; TempPoint.y=TopPoint.y; if(m_MapInfo[TopPoint.y][TopPoint.x]==200 && m_MapInfo[TempPoint.y][TempPoint.x]==200) { sPathNode *NewNode=&EleMem[pos++]; NewNode->Cur=TopPoint; NewNode->Par=node; NewNode->F=abs(desPoint.x-NewNode->Cur.x)+abs(desPoint.y-NewNode->Cur.y); if(PointMap.find(MAKEDWORD(NewNode->Cur.x,NewNode->Cur.y))==PointMap.end()) { m_OpenListSet.insert(NewNode); PointMap.insert(MAKEDWORD(NewNode->Cur.x,NewNode->Cur.y)); if(NewNode->F==0) { break; } } }else if(TopPoint==desPoint) { break; } } CPoint RightPoint; RightPoint.x=node->Cur.x+10; RightPoint.y=node->Cur.y; if(RightPoint.x+Ele->CurPos.Width()<=1600) { CPoint TempPoint; TempPoint.x=RightPoint.x+Ele->CurPos.Width()-10; TempPoint.y=RightPoint.y+Ele->CurPos.Height()-10; if(m_MapInfo[RightPoint.y][RightPoint.x+Ele->CurPos.Width()-10]==200 && m_MapInfo[TempPoint.y][TempPoint.x]==200) { sPathNode *NewNode=&EleMem[pos++]; NewNode->Cur=RightPoint; NewNode->Par=node; NewNode->F=abs(desPoint.x-NewNode->Cur.x)+abs(desPoint.y-NewNode->Cur.y); if(PointMap.find(MAKEDWORD(NewNode->Cur.x,NewNode->Cur.y))==PointMap.end()) { m_OpenListSet.insert(NewNode); PointMap.insert(MAKEDWORD(NewNode->Cur.x,NewNode->Cur.y)); if(NewNode->F==0) { break; } } }else if(RightPoint==desPoint) { break; } } CPoint BottomPoint; BottomPoint.x=node->Cur.x; BottomPoint.y=node->Cur.y+10; if(BottomPoint.y+Ele->CurPos.Height()<=1200) { CPoint TempPoint; TempPoint.x=BottomPoint.x+Ele->CurPos.Width()-10; TempPoint.y=BottomPoint.y+Ele->CurPos.Height()-10; if(m_MapInfo[BottomPoint.y+Ele->CurPos.Height()-10][BottomPoint.x]==200 && m_MapInfo[TempPoint.y][TempPoint.x]==200) { sPathNode *NewNode=&EleMem[pos++]; NewNode->Cur=BottomPoint; NewNode->Par=node; NewNode->F=abs(desPoint.x-NewNode->Cur.x)+abs(desPoint.y-NewNode->Cur.y); if(PointMap.find(MAKEDWORD(NewNode->Cur.x,NewNode->Cur.y))==PointMap.end()) { m_OpenListSet.insert(NewNode); PointMap.insert(MAKEDWORD(NewNode->Cur.x,NewNode->Cur.y)); if(NewNode->F==0) { break; } } }else if(BottomPoint==desPoint) { break; } } CPoint LeftPoint; LeftPoint.x=node->Cur.x-10; LeftPoint.y=node->Cur.y; if(LeftPoint.x>=0) { CPoint TempPoint; TempPoint.x=LeftPoint.x; TempPoint.y=LeftPoint.y+Ele->CurPos.Height()-10; if(m_MapInfo[LeftPoint.y][LeftPoint.x]==200 && m_MapInfo[TempPoint.y][TempPoint.x]==200) { sPathNode *NewNode=&EleMem[pos++]; NewNode->Cur=LeftPoint; NewNode->Par=node; NewNode->F=abs(desPoint.x-NewNode->Cur.x)+abs(desPoint.y-NewNode->Cur.y); if(PointMap.find(MAKEDWORD(NewNode->Cur.x,NewNode->Cur.y))==PointMap.end()) { m_OpenListSet.insert(NewNode); PointMap.insert(MAKEDWORD(NewNode->Cur.x,NewNode->Cur.y)); if(NewNode->F==0) { break; } } }else if(LeftPoint==desPoint) { break; } } } if(count<1000) { if(!m_OpenListSet.empty()) { node=*m_OpenListSet.begin(); while(node->Par!=0) { Ele->VecPath.push_front(node->Cur); node=node->Par; } }else { if(m_VecSelectTank.size()>0) { Ele->VecPath.clear(); m_FindPathEleList.remove(Ele); m_DynamicFindPathEleList.remove(Ele); m_DynamicFindPathEleList.push_back(Ele); } } } m_OpenListSet.clear(); m_CloseListSet.clear();
}
这里有用到二叉堆的数据结构,这样可以极大的提高效率,下面我给出相关的结构定义:
struct sPathNode; class sort { public: bool operator () (const sPathNode* b1,const sPathNode* b2) const{ return b1->F<b2->F; } }; struct sPathNode { CPoint Cur; sPathNode *Par; int F; sPathNode(){} sPathNode(int x,int y) { Cur.SetPoint(x,y); } }; struct sEleObj { IDirect3DTexture9* Sur; CPoint DesPos; CRect CurPos; list<CPoint> VecPath; bool bMove; byte ID; }; multiset<sPathNode*,sort> m_OpenListSet; multiset<sPathNode*,sort> m_CloseListSet;
这样我们每次添加一个节点时,F值最小的节点便会在最开头的位置。在此我说下关于该算法的一些优化,我在函数的开头便定义了static sPathNode EleMem[1024*1024];这样的话我们每次就可以直接从这个数组里分配节点,而不用去new和delete一个新的节点,节约了操作内存的时间,并且,我在算法里用的容器都是Set,因为set内部是红黑二叉树实现的,所以在查找上速度十分快。还有一点是,千万不要同时为多个物体寻路,你应该把需要寻路的物体放在一个list里面,每一帧或者每过几帧(这根据你的需求)依次取出来做寻路,这样在速度上会大大提高,就算有几十到几百ms的延迟,用户也很难察觉。
还有我不得不提的是,动态碰撞怎么办,对,就是在你规划路径好了以后,在你的路径上出现了其他物体该怎么解决呢,那么我们就需要去做动态寻路,在我们遇到障碍物后,会根据当前物体的id,决定是否等待,如果等待,则障碍物如果是移动状态,则重新去寻路,规划一条新的路径,如果障碍物已经是停止状态,则当前物体重新寻路,规划出一条新的路径。
最后一点是,当我们操作多个物体移动到同一地点时,因为不可能重叠,所以这些物体不可能到达同一位置,这就是说,当物体的目的地不可到达时,我们该怎么处理呢?其实很简单,就是以目的地为中心向外搜索,找到一个物体可以到达的位置作为新的目的地,然后去寻路这个目的地就可以了。
说起来简单,但是这其中的代码量和繁琐程度还是很大的,大家可以参考一下我上传的代码,有什么问题一起交流下。
本文有不足之处,还望大家多多指正。