想做游戏,所以最近看了下算法,看到了常用的 A*算法 索性就学了一学,感觉unity3d上面好像有封装的寻路算法,还是想着去实现一下,大概就看了很多百度,一共用了两天,把它简单实现了下。
前提:
我很久没有用C++了,所以很多概念模糊,也是边查边用的,一直再用JAVA,所以很多传指针,返回指针(我返回的数组),就感觉没JAVA直接返回数组那么顺手。不过东查西查的按照自己的想法,还是简单的实现了一下。如果有错误希望指出,仅供大家参考、交流。
还有就是如果有朋友没看过A*算法的,可以先去百度搜一下,看一下是个怎么回事,才来看我这个小白实现简单A*的代码,毕竟能写代码才是王道。
思路
我的思路是创建一个地图 可以自己设定 具体在#defind MapSize 100(我建的是10*10),地图中给出障碍物、起点和终点。
然后地图分成100块,每一块是一个用一个结构体表示,程序中 struct A_map,里面有几个值
- int Map_symbol(代表是什么东西,1为普通 2为围墙 3为起点 4为钟点)
- int x,y(分别代表地图块的坐标)
- float already_Cost (实际走过的路径的花费,网上的g(n))
- float expect_Cost (估计花费 算法使用那个什么哦,用曼哈顿距离是的,因为简单,地图全都被分成了块状,简单,简单。)
- int Visit_symbol(1是已经访问 2是没访问 3是准备访问 )
- 注释//struct A_map *next;(代码中的这个东西是被我注释了,因为本来想用指针来指一下路径,一个接一个,像链表一样。结果没法,语法错误,想用,可以试试,好像用处也不够大)
- Cost=already_Cost+expect_Cost
———–结构体代码————
//定义地图块结构体
struct A_map{
int Map_symbol;//1为普通 2为围墙 3为起点 4为重点
int x;//坐标 x
int y;//坐标 y
//网上找的g(n)可以看做从start到current所花费的cost,h(n)从current到end的花费。 这里虽然是感觉是多余的,但是这样应该清晰点吧。。。火火火。
float already_Cost;//实际走过的路径的花费
float expect_Cost;//估计花费 算法使用那个什么哦,因为简单,地图全都被分成了块状,简单,简单。
float Cost;//总花费
int Visit_symbol;//1是已经访问 2是没访问 3是准备访问
//struct A_map *next;
};
好了有了上面这个结构体,我们的地图就有了存储信息的功能。
(我看文档里面的都是把东西放入一个数组里面,openlist ……closelist,就是标记那些访问了,那些没访问,那些需要访问的。,我这里用visit_symbol代替咯,意思就是每当我要访问的时候先检测这个A_map的访问标识,是可以访问呢,还是已经访问过了,还是马上需要判断,需要访问)
有了地图,并且知道地图上的每个块可以存储一定的信息,那么,我们初始化地图
init
//初始化地图
void MapInit(A_map maps[],int goods[]){
int i,j=0;//循环变量
for(i=0;i< MapSize;i++){//把所有的地图块全都初始化,Map_symbol=1(平地),visit_symbol=2(没有访问的状态,意思是可访问)
maps[i].Map_symbol=1;//map 1是平地
maps[i].x=i%10;//下一行 给x 值,从0到9,满10归0
maps[i].Visit_symbol=2;//2是没访问的,可访问的
if(i%10==0&&i!=0){
j++;
}
maps[i].y=j;//给Y赋值
}
maps[goods[0]].already_Cost=0;//初始化起点的消耗为0
maps[goods[0]].expect_Cost=0;//初始化
maps[goods[0]].Cost=0;//初始化 Cost=already_Cost+expect_Cost;
i=0;//再把i变为0,重复利用
for(i;i<9;i++){
if(i==0){//开始点 开始点是已访问的 - 0是开始
maps[goods[i]].Map_symbol=3;//3代表起点
maps[goods[i]].Visit_symbol=1;//1代表已访问
}else if(i==8){//结束点 结束点是可访问的。 - 8是结束点,可以改成数组的长度-1,感觉C++没那么方便,我就直接以我知道的给他赋8了,这样是不够灵活的。
maps[goods[i]].Map_symbol=4;//4代表终点
maps[goods[i]].Visit_symbol=2;//2代表可访问
}else{//障碍物 已访问的(不用再访问)。
maps[goods[i]].Map_symbol=2;//2代表障碍物
maps[goods[i]].Visit_symbol=1;//1代表以访问-就是不可访问了
}
}
}
一一解释一下里面的数组到底是什么用
1. maps[]:一百个地图块结构体的集合。index 0-99
2.goods[]:存储起点终点、还有障碍物的集合。具体的讲。goods[index],里面的index是maps[]数组的索引值。意思是 index的范围是0~99下面看goods[]的定义:
int goods[]={12,41,42,43,44,45,46,47,76};//记录 起点,障碍物的排列数字,终点的排列数字
goods[0]:默认为起点
goods[sizeof(goods)/sizeof(int)]:也就是最后一位,默认为终点。
其余的就全是障碍物。
地图初始化完成了,我们就要按照步骤来。
1、先检验起点周围的可以访问的点,放入一个数组。
2、计算每个点的f(n)=g(n)+h(n),就是结构体里面的 cost,选择最小的cost 作为下一步将要去的点。
3、下次去的点赋值给curretMap,然后把没选中的点全部设置为不可访问。
4、又从currentMap,循环上面的123……最后给出到达终点的判定,结束整个寻路。
———说着挺简单——–做着,其实还好,就是我的代码写的有点不好看。
1、检验周围的可访问点,放入一个数组,用指针返回。
//获得周围的8个地图块 传入当前坐标
//currentMap是代表当前所处的地图快。 maps老规矩,是所有地图块的集合。
int * GetExpect_Map(A_map currentMap,A_map maps[]){
static int Map_Index[8];//这里我一如既往的已经给他规定死了8个因为一个地图块周围最多就只有8个是可以前进的。
int index=0;//可行的mps索引值
int i;//循环变量
for(i=0;i< MapSize;i++){//开始循环,找出所有的可行的地图快
if(currentMap.x-1<=maps[i].x&&maps[i].x<=currentMap.x+1&¤tMap.y-1<=maps[i].y&&maps[i].y<=currentMap.y+1){//写出条件(就是将currtMap周围的8个点全部检测)
if(maps[i].Visit_symbol==2){//如果是2的话就是可以访问,
maps[i].Visit_symbol=3;//准备访问,可以访问,我们马上把它变成即将访问3。
Map_Index[index++]=i;//记录可以访问的下标
}
}
}
return Map_Index;//返回数组
}
2、计算每个点的f(n)=g(n)+h(n),就是结构体里面的 cost,选择最小的cost 作为下一步将要去的点,并返回将要去的点的maps的索引值。
//循环计算欲走的路 并输出下一步的map
//Expect_Index[]数组中的东西就是步骤1中返回的Map_Index,里面装了currtMap周围可访问的几个地图块。
//endPoint代表结束点的地图块
//currentMap是当前所在的地图块。
int ReckonTheExpect(int Expect_Index[],A_map maps[],A_map EndPoint,A_map *currentMap){
int index=0;//下一步要走的下标
int AlreadyCost=currentMap->already_Cost;//记录从起点到当前的消耗。
int MHD_distance=0;//曼哈顿距离
//那就先计算消耗吧
while(Expect_Index[index]!='\0'){
//先计算曼哈距离 存入
MHD_distance=GetExpect_Cost(maps[Expect_Index[index]].x,maps[Expect_Index[index]].y,EndPoint.x,EndPoint.y);//传入两点的横坐标纵坐标算出曼哈顿距离的函数GetExpect_Cost(x1,y1,x2,y2);
maps[Expect_Index[index]].expect_Cost=MHD_distance;//算出后,立即将曼哈顿距离放进当前检测的Expect_Index中的地图块。让她的信息保存
if(maps[Expect_Index[index]].x==currentMap->x||maps[Expect_Index[index]].y==currentMap->y){//上下左右直线。 LINE=1
maps[Expect_Index[index]].already_Cost=AlreadyCost+LINE;//直线向加在当前的距离上加上1,为检测的地图块到终点的already距离
}else{//斜线 根号2 大约 1.41 SKEW 为 1.41
maps[Expect_Index[index]].already_Cost=AlreadyCost+SKEW;//斜向加1.41
}
maps[Expect_Index[index]].Cost=maps[Expect_Index[index]].already_Cost+maps[Expect_Index[index]].expect_Cost;//Cost=alreadycost+expectcost;
index++;//下标自增
}
return Expect_Index[CompareToMap(maps,Expect_Index)];//返回下一步的具体Maps索引值。
//比较函数 CompareToMap(maps,expect_index); 上面获取到信息后,由此函数对里面的大小值一一进行对比,对比成功后,返回 expect_index的索引值。
}
//对比地图块的代价
int CompareToMap(A_map maps[],int Expect_Index[]){//这个函数就和比较大小的函数一样的道理,只是里面加了一些条件,如果 cost相同就找 expectcost ,如果expectcost相同就找alreadycost,如果都相同了,那就随便选一个,不变无所谓。
int index=0;
int record_index=0;//记录最小下标
float areadyCost=maps[Expect_Index[index]].already_Cost;//装第一个对比对象
float expectCost=maps[Expect_Index[index]].expect_Cost;
float cost=maps[Expect_Index[index]].Cost;
//对比大小
index=1;
record_index=index;
while(Expect_Index[index]!='\0'){
if(cost>maps[Expect_Index[index]].Cost){//>对比cost
cost=maps[Expect_Index[index]].Cost;
record_index=index;
}else if(cost==maps[Expect_Index[index]].Cost){//=cost相同
if(expectCost>maps[Expect_Index[index]].expect_Cost){//>对比 expect
expectCost=maps[Expect_Index[index]].expect_Cost;
record_index=index;
}else if(expectCost==maps[Expect_Index[index]].expect_Cost){//= expect相同
if(areadyCost>maps[Expect_Index[index]].already_Cost){//>对比 areadycost
areadyCost=maps[Expect_Index[index]].already_Cost;
record_index=index;
}
}
}
index++;
}
return record_index;
}
3、下次去的点赋值给curretMap,然后把没选中的点全部设置为不可访问。
//这里就是跳到已经选定了的方向走了。上面已经把需要的index传过来了,就是这里的形参 nextStep,那么在行走后还需要把所有没选中的地图块改为 visitSymbol = 1(就是已经访问了,不能再访问了。),
//将路径连接起来,并且改变symbol
void NextStep(A_map *currentMap,A_map maps[],int nextStep,int Expect_Index[]){
//前面的位置检测
//cout<<"前一位置:("<<currentMap->x<<","<<currentMap->y<<")\n";
int index=0;
if(currentMap->Visit_symbol==3){//是3就跳1这个主要是用来检测如果当前的地图块是3,就变为1,不可访问。因为当前的已经选定不需要再访问了。
currentMap->Visit_symbol=1;
}
//循环把所有的都变为1,不可访问
while(Expect_Index[index]!='\0'){
maps[Expect_Index[index]].Visit_symbol=1;
index++;
}
//这样的话,这里的currentMap还是原来那个位置,并没有向预定的方向走一步,所以把连以前的位置和它走位的几块可选的位置全部射程1。不可访问。那么cuerrentMap应该在哪里变化,我选择在最外部,等下看main函数就知道了。
}
///我还写了几个打印的函数。
- void PrintMap(A_map everyMap[]):打印地图信息,格式 “数字 - 坐标” 这里的数字是 map_symbol
- void PrintMap2(A_map everyMap[])打印的是地图信息,格式“数字-坐标” 这里的数字是 visible_symbol
- void PrintExpect(int *expect_Point)打印的是,当前点的周围有那些点是可以移动的。
——————————–贴个完整代码吧————–用的VC++6.0,代码赋值粘贴直接跑—————
#include <iostream>
using namespace std;
//函数声明
#define MapSize 100
#define LINE 1//横竖 消耗
#define SKEW 1.41//斜 消耗
//定义地图块结构体
struct A_map{
int Map_symbol;//1为普通 2为围墙 3为起点 4为重点
int x;//坐标 x
int y;//坐标 y
//网上找的g(n)可以看做从start到current所花费的cost,h(n)从current到end的花费。 这里虽然是感觉是多余的,但是这样应该清晰点吧。。。火火火。
float already_Cost;//实际走过的路径的花费
float expect_Cost;//估计花费 算法使用那个什么哦,因为简单,地图全都被分成了块状,简单,简单。
float Cost;//总花费
int Visit_symbol;//1是已经访问 2是没访问 3是准备访问
//struct A_map *next;
};
//函数-计算两个地图之间的 expect_Cost return int 曼哈顿距离???
//传入两点的 xy
int GetExpect_Cost(int x1,int y1,int x2,int y2){
int x=x2-x1;
int y=y2-y1;
if(x<0) x=-x;
if(y<0) y=-y;
return x+y;
}
//获得周围的8个地图块 传入当前坐标
int * GetExpect_Map(A_map currentMap,A_map maps[]){
static int Map_Index[8];
int index=0;
int i;
//测试方法输出
//cout<<currentMap.x<<","<<currentMap.y<<" "<<currentMap.Map_symbol<<" |";
for(i=0;i<MapSize;i++){
if(currentMap.x-1<=maps[i].x&&maps[i].x<=currentMap.x+1&¤tMap.y-1<=maps[i].y&&maps[i].y<=currentMap.y+1){//写出条件
if(maps[i].Visit_symbol==2){//如果是1 就是未被访问。那么要即将访问。
maps[i].Visit_symbol=3;//准备访问
Map_Index[index++]=i;//记录下标
}
}
}
return Map_Index;
}
//初始化地图
void MapInit(A_map maps[],int goods[]){
int i,j=0;
//int index=0;
for(i=0;i<MapSize;i++){
maps[i].Map_symbol=1;//map 1是平地
maps[i].x=i%10;
maps[i].Visit_symbol=2;//2是没访问的
if(i%10==0&&i!=0){
j++;
}
maps[i].y=j;
}
maps[goods[0]].already_Cost=0;
maps[goods[0]].expect_Cost=0;
maps[goods[0]].Cost=0;
i=0;
for(i;i<9;i++){
if(i==0){//开始点 开始点是已访问的
maps[goods[i]].Map_symbol=3;
maps[goods[i]].Visit_symbol=1;
}else if(i==8){//结束点 结束点是可访问的。
maps[goods[i]].Map_symbol=4;
maps[goods[i]].Visit_symbol=2;
}else{//障碍物 已访问的(不用再访问)。
maps[goods[i]].Map_symbol=2;
maps[goods[i]].Visit_symbol=1;
}
}
}
//对比地图块的代价
int CompareToMap(A_map maps[],int Expect_Index[]){
int index=0;
int record_index=0;//记录最小下标
float areadyCost=maps[Expect_Index[index]].already_Cost;
float expectCost=maps[Expect_Index[index]].expect_Cost;
float cost=maps[Expect_Index[index]].Cost;
//对比大小
index=1;
record_index=index;
while(Expect_Index[index]!='\0'){
if(cost>maps[Expect_Index[index]].Cost){//>对比cost
cost=maps[Expect_Index[index]].Cost;
record_index=index;
}else if(cost==maps[Expect_Index[index]].Cost){//=cost相同
if(expectCost>maps[Expect_Index[index]].expect_Cost){//>对比 expect
expectCost=maps[Expect_Index[index]].expect_Cost;
record_index=index;
}else if(expectCost==maps[Expect_Index[index]].expect_Cost){//= expect相同
if(areadyCost>maps[Expect_Index[index]].already_Cost){//>对比 areadycost
areadyCost=maps[Expect_Index[index]].already_Cost;
record_index=index;
}
}
}
index++;
}
return record_index;
}
//循环计算欲走的路 并输出下一步的map
int ReckonTheExpect(int Expect_Index[],A_map maps[],A_map EndPoint,A_map *currentMap){
//要先初始化float类型的变量要不然输出试一串-3242232+008
int index=0;//下一步要走的下标
int AlreadyCost=currentMap->already_Cost;//记录从起点到当前的消耗。
int MHD_distance=0;
//那就先计算消耗吧
//cout<<" Cost:"<<maps[Expect_Index[index]].Cost<<"\n";
while(Expect_Index[index]!='\0'){
//先计算曼哈距离 存入
MHD_distance=GetExpect_Cost(maps[Expect_Index[index]].x,maps[Expect_Index[index]].y,EndPoint.x,EndPoint.y);
maps[Expect_Index[index]].expect_Cost=MHD_distance;
if(maps[Expect_Index[index]].x==currentMap->x||maps[Expect_Index[index]].y==currentMap->y){//上下左右
maps[Expect_Index[index]].already_Cost=AlreadyCost+LINE;//横向加1
}else{
maps[Expect_Index[index]].already_Cost=AlreadyCost+SKEW;//纵向加1.41
}
// maps[Expect_Index[index]].expect_Cost=MHD_distance;
maps[Expect_Index[index]].Cost=maps[Expect_Index[index]].already_Cost+maps[Expect_Index[index]].expect_Cost;
cout<<"MHD_distance:"<<MHD_distance<<" Expect_Index[index]:"<< Expect_Index[index]<<" Cost:"<<maps[Expect_Index[index]].Cost<<"\n";
index++;
}
//测试输出的最佳路子,并且包出下一个节点的坐标
cout<<"最佳路子:"<<Expect_Index[CompareToMap(maps,Expect_Index)]<<" 地图块的位置("<<maps[Expect_Index[CompareToMap(maps,Expect_Index)]].x<<","<<maps[Expect_Index[CompareToMap(maps,Expect_Index)]].y<<")\n";
return Expect_Index[CompareToMap(maps,Expect_Index)];
}
//打印地图----------------------------------------------------------以下 辅助函数------------------------------------------------------
void PrintMap(A_map everyMap[]){
for(int i=0;i<MapSize;i++){
if(i%10==0){
cout<<"\n";
}
cout<<everyMap[i].Map_symbol<<"-"<<"("<<everyMap[i].x<<","<<everyMap[i].y<<") ";
}
cout<<"\n------------------------------- map is already ok ----------------------------\n\n";
}
//打印检测形态
void PrintMap2(A_map everyMap[]){
for(int i=0;i<MapSize;i++){
if(i%10==0){
cout<<"\n";
}
cout<<everyMap[i].Visit_symbol<<"-"<<"("<<everyMap[i].x<<","<<everyMap[i].y<<") ";
}
cout<<"\n------------------------------- ExpectMap is already ok ----------------------------\n\n";
}
//打印即将走的路线
void PrintExpect(int *expect_Point){
int h=0;
while(*(expect_Point+h)!='\0'){
cout<<"index: "<<*(expect_Point+h++)<<" \n";
}
cout<<" count:"<<h<<"\n";
cout<<"\n------------------------------- printExpect is already ok ----------------------------\n\n";
}
//--------------------------------------------------------以上 辅助函数--------------------------------------------------------------------
//将路径连接起来,并且改变symbol
void NextStep(A_map *currentMap,A_map maps[],int nextStep,int Expect_Index[]){
//前面的位置检测
cout<<"前一位置:("<<currentMap->x<<","<<currentMap->y<<")\n";
int index=0;
if(currentMap->Visit_symbol==3){//是3就跳1
currentMap->Visit_symbol=1;
}
//currentMap->next=&maps[nextStep];//当前路径指向下一个选定的路
while(Expect_Index[index]!='\0'){
maps[Expect_Index[index]].Visit_symbol=1;
index++;
}
PrintMap2(maps);//地图下一步要检测的范围检测
}
main(){
cout<<"程序启动,初始化地图\n";
A_map currentMap;
A_map everyMap[MapSize];//定义一个10*10的地图 100个map
int goods[]={12,41,42,43,44,45,46,47,76};//记录 起点,障碍物的排列数字,终点的排列数字
int *expect_Point;//定义指向像一个的指针
//保存路线-定义-开始
int keepStep[50];
//keepStep的索引变量
int keepStep_index=0;
//保存路线-定义-结束
int nextStep=0;//下一步 maps索引值 的记录
//int init_i=0;//初始化循环变量
/*测试结构体是否可用
eveyMap[0].Map_symbol=1;
cout<<"eveyMap[0].Map_symbol: "<<eveyMap[0].Map_symbol;
*/
/*
地图绘制-参考
1 1 1 1 1 1 1 1 1 1
1 1 3 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1
1 2 2 2 2 2 2 2 1 1
1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 4 1 1 1
1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1
1-(0,0) 1-(1,0) 1-(2,0) 1-(3,0) 1-(4,0) 1-(5,0) 1-(6,0) 1-(7,0) 1-(8,0) 1-(9,0)
1-(0,1) 1-(1,1) 3-(2,1) 1-(3,1) 1-(4,1) 1-(5,1) 1-(6,1) 1-(7,1) 1-(8,1) 1-(9,1)
1-(0,2) 1-(1,2) 1-(2,2) 1-(3,2) 1-(4,2) 1-(5,2) 1-(6,2) 1-(7,2) 1-(8,2) 1-(9,2)
1-(0,3) 1-(1,3) 1-(2,3) 1-(3,3) 1-(4,3) 1-(5,3) 1-(6,3) 1-(7,3) 1-(8,3) 1-(9,3)
1-(0,4) 2-(1,4) 2-(2,4) 2-(3,4) 2-(4,4) 2-(5,4) 2-(6,4) 2-(7,4) 1-(8,4) 1-(9,4)
1-(0,5) 1-(1,5) 1-(2,5) 1-(3,5) 1-(4,5) 1-(5,5) 1-(6,5) 1-(7,5) 1-(8,5) 1-(9,5)
1-(0,6) 1-(1,6) 1-(2,6) 1-(3,6) 1-(4,6) 1-(5,6) 1-(6,6) 1-(7,6) 1-(8,6) 1-(9,6)
1-(0,7) 1-(1,7) 1-(2,7) 1-(3,7) 1-(4,7) 1-(5,7) 4-(6,7) 1-(7,7) 1-(8,7) 1-(9,7)
1-(0,8) 1-(1,8) 1-(2,8) 1-(3,8) 1-(4,8) 1-(5,8) 1-(6,8) 1-(7,8) 1-(8,8) 1-(9,8)
1-(0,9) 1-(1,9) 1-(2,9) 1-(3,9) 1-(4,9) 1-(5,9) 1-(6,9) 1-(7,9) 1-(8,9) 1-(9,9)
*/
//goods元素计数-----------饿用到上面吧不知道为什么传入函数数组的长度会变成4能试sizeof出了指针的地址长度
//printf("%d",sizeof(goods)/sizeof(int));
MapInit(everyMap,goods);//地图初始化
PrintMap(everyMap); //地图初始化检验
currentMap=everyMap[goods[0]];//当前位置初始化,初始化的化,地图的当前位置就是起点
//★主要循环体,里面的函数对照上面的说明,可以看。
while(nextStep!=goods[sizeof(goods)/sizeof(int)-1]){
expect_Point=GetExpect_Map(currentMap,everyMap);//寻找下一个圈需要检测的,准备走的路
nextStep=ReckonTheExpect(expect_Point,everyMap,everyMap[goods[sizeof(goods)/sizeof(int)-1]],¤tMap);
cout<<"1 next-Step:"<<nextStep<<"\n";
keepStep[keepStep_index++]=nextStep;//保存路径
NextStep(¤tMap,everyMap,nextStep,expect_Point);
currentMap=everyMap[nextStep];//这个就是让currentMap走到预定好的下一个位置。
cout<<"\n★现在位置:("<<currentMap.x<<","<<currentMap.y<<")\n\n";
}
keepStep_index=0;
//输出打印 (x,y)==》(x,y)==》……的路径格式
while(keepStep[keepStep_index]!='\0'){
cout<<"("<<everyMap[keepStep[keepStep_index]].x<<","<<everyMap[keepStep[keepStep_index]].y<<")==>";
keepStep_index++;
}
return 0;
}
———————————————————第一次写博客就很渣,而且代码写得也不好,所以大家见谅,为了更清楚我给大家做个关系图,看起来就清晰一点了。
————-有问题和错误希望指出————-