[置顶] 游戏开发技术总结(经典之作)第八集 脚踏实地游戏角色自动寻路、路径搜索算法

摘要:
8-1任务让游戏的角色能够自动寻路;并让所有的动物都动起来。8-2设置障碍8-2-1场景中的障碍点游戏中加入了山石、树木场景后,就应该有对游戏角色行走路线的限制。第1行为障碍表行列数。这就是程序设计算法中称为A*搜索算法。A*搜索算法是由7个函数组成的,其中intfindpath()路径寻找函数是搜索算法的主函数。path[i]/map_w;为路径的X坐标path[i]%map_w;为路径的Y坐标map_w为障碍表的宽。
8-1 任务

让游戏的角色能够自动寻路; 并让所有的动物都动起来。另外我们在这一章将介
绍图形光标技术。


8-2 设置障碍


8-2-1 场景中的障碍点

游戏中加入了山石、树木场景后,就应该有对游戏角色行走路线的限制。这种限
制是我们通过在游戏场景中设置障碍点[置顶] 游戏开发技术总结(经典之作)第八集 脚踏实地游戏角色自动寻路、路径搜索算法第1张来实现的。不管是人还是动物,遇到障碍
[置顶] 游戏开发技术总结(经典之作)第八集 脚踏实地游戏角色自动寻路、路径搜索算法第1张,都必须绕道而行。

[置顶] 游戏开发技术总结(经典之作)第八集 脚踏实地游戏角色自动寻路、路径搜索算法第3张

图8-1
在我们不希望角色经过或占据的地方做上一个标记,例如树根处、石头底部。这
些障碍标记可以是我们事先赋予景物的属性,也可以是我们在游戏场景编辑器中单独
设置的。有了障碍标记后,游戏的场景就可以视为图8-2 所示模型。
[置顶] 游戏开发技术总结(经典之作)第八集 脚踏实地游戏角色自动寻路、路径搜索算法第4张
图8-2
在这样的模型里,我们清楚地看到游戏角色可以走的地方是白色的区域块,而黑
色块是就是障碍点。


8-2-2 障碍点的数字化模型

为了在我们的程序中表示这样的障碍情况,我们将这样的障碍模型数字化为以下方式:
“0”对应白色的区域块,“1”对应黑色块障碍点
char map[12][13]={ 0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,1,
0,0,0,0,0,0,0,0,0,0,0,0,1,
0,0,0,0,0,0,0,0,0,0,0,0,1,
0,0,0,0,0,0,0,0,0,0,0,0,1,
0,0,0,0,0,0,0,0,0,0,0,0,1,
1,1,1,1,1,1,0,1,0,0,0,0,1,
0,0,0,0,0,0,0,0,0,0,0,0,1,
0,1,0,0,0,0,0,0,0,0,0,0,1,
1,1,1,0,0,1,1,0,1,1,1,1,1,
0,1,1,1,1,1,1,1,1,1,1,1,1,
0,0,0,0,1,1,0,0,1,0,1,1,1

}
这是一个参照以上模型定义的 、字符型的12X13 二维数组变量。用字符型的原因
是它占内存较小。有了这个数字化障碍模型——数组变量后,我们的角色在场景中的
移动到下一点(x,y)就变成了。
将要走的点(x,y)代入障碍模型变量map [x][y]判断:
当 map [x][y]=0 时,(x,y)点可以走;
当 map [x][y]=1 时,(x,y)点是障碍,不能去,改走其它方向。
这就是程序设计中典型的走迷宫算法。


8-2-3 调入障碍表的函数

在我们的游戏中也有用地图编辑器设置的,这样一个与山石、树木对应的障碍表。
与地图同名的障碍表 *.map 的数据格式 。

[置顶] 游戏开发技术总结(经典之作)第八集 脚踏实地游戏角色自动寻路、路径搜索算法第5张
第1 行为障碍表行列数。
第 2~ n 行为障碍表数据。
下面我们定义一个函数 loadza(CString name)来调入我们事先设置好的障碍表到障碍
模型数组map[i][j]中。
注意,map[i][j]障碍表是在“搜索算法.CPP”的findpt 类里面的(后面马上讲到)。

//*********************************************
// loadza(CString name)//调入障碍表
// 调入障碍表(.map),障碍表的格式:
// 障碍表第1 行,障碍表的行列值,以后行就是障碍表1 行的数据。
//*********************************************
void gamepro::loadza(CString name)//调入障碍表
{ char cc[256];
FILE *f;
int i,j;
strcpy(cc,name);cc[lstrlen(name)-3]=0; //变换文件名
strcat(cc,"map");
f=fopen(cc,"r");
if(f==NULL) goto aa; //如果没有障碍文件,生成新表。
fscanf(f,"%d,%d\n",&w,&h); //读入障碍表行列数。
SCRP0=w/16; //换成地图倍数
m_fid.map_w=WIDTH*SCRP0/GX;
m_fid.map_h=HEIGHT*SCRP0/GY;
if(w>WIDTH*SCRP/GX||h>HEIGHT*SCRP/GY) //障碍表与地图大小不符。
{SetCurrentDirectory(appdir); //置当前目录
return;
}
for(i=0;i<h;i++)
fgets(&m_fid.map[i][0],w+2,f); //读入障碍表
fclose(f);
aa:for(i=0;i<m_fid.map_w;i++) //规范化障碍表
for(j=0;j<m_fid.map_h;j++)
if(m_fid.map[i][j]!='1') m_fid.map[i][j]='0';
find_p=1; //无搜索0,有搜索1
SetCurrentDirectory(appdir); //置当前目录
}

但是我们这里还不仅仅是让角色从起点绕开障碍,到达目的点。由于在多数游戏
场景中, 从起点到目的点有多条路可走, 所以我们还要考虑选择一条最近的路。这就
是程序设计算法中称为A*搜索算法。


8-3 A*搜索算法

由于对 A*搜索算法的理解需要一定的离散数学和数据结构理论知识,这里我们直
接给出A*搜索算法的类文件,了解它的使用方法。它的原理留给读者在后面的深入学
习中再理解(实际上我们在编程中将直接调用许多Windows 自带的API 函数,我们只要
知道怎么用它就行了,至于它是如何实现的,我们可以不用理会)。
A*搜索算法是由7 个函数组成的,其中int findpath()路径寻找函数是搜索算法的主函数。


8-3-1 findpath() A*搜索

int findpath();//路径寻找主函数
它的出口参数: 0 表示有路,-1 表示无路。
findpath()调用时需要提供角色的起始点、目标点。
起始点 类变量 start_x ,start_y
目标点 类变量 end_x ,end_y
搜索出的最短路径在类整型数组 path[]中。

path[i]/map_w;为路径的X 坐标
path[i]%map_w;为路径的Y 坐标
map_w 为障碍表的宽。
调用方法为:m_fid 为定义的搜索算法对象变量。
m_fid.end_y =man[i].xix/GX; //目标点Y
m_fid.end_x =man[i].xiy/GY; //目标点X
m_fid.start_y=x0; //起始点Y
m_fid.start_x=y0; //起始点X
if(m_fid.findpath()==-1) //A*算法寻路,
{fidf=FALSE;
return-1; //无路返回-1
}

之后我们就可以用for 循环来得到角色的行走路径了

for(int j=1;m_fid.path[j]>0;j++)
{ xx= m_fid.path[j]/m_fid.map_w; //路径的X 坐标
yy= m_fid.path[j]%m_fid.map_w; //路径的Y 坐标
}


8-3-2 加入A*算法寻路

好,现在我们在指挥游戏主角行走的按左键函数中加入A*算法寻路。
我们将新加入的寻路的功能函数加到 gamepro 类里,类文件名为“ game_寻
路.cpp”。它是gamemap 类的继承类(子类)。

class gamepro : public gamemap //继承gamemap 类
leftdown(⋯ )按左键函数
这个函数是将前面主程序中的按左键功能封装在内了。
void CMyDlg::OnLButtonDown(UINT nFlags, CPoint point)
//取针对主角的目标位置,[类向导中定义生成]
{ int x0=point.x,y0=point.y;
CClientDC dc(this); //取客户区设备环境
fidtim=m_game.leftdown(dc.m_hDC,x0,y0); //按左键
⋯
CDialog::OnLButtonDown(nFlags, point);
}

注意,在leftdown(⋯)中的又有一个leftdown(⋯),它是在gamemap 类中的另一
个功能函数(有意同名,是让读者注意区分)。

//**************************************************
// int leftdown(HDC hdc,int x,int y)//按左键
// 这是由按鼠标左键调用的。
// A. 在显示区按键,给出主角的目标位置,调A*算法寻路
// B. 在小地图区按键,调定位地图。
// 若是寻路,返回寻路的时间。
//**************************************************
int gamepro::leftdown(HDC hdc,int x,int y)//按左键
{ int fidtim=0;
if(find_p==0) //无搜索0,有搜索1
{gamemap::leftdown(hdc,x,y); //有意同名,注意区分
return fidtim;
}
if(x>0&&x<WIDTH&&y>0&&y<HEIGHT&&edi==0) //在显示区,非编辑态
{int i=mann; //只对主角取目标点
fidtim=FindPath(i,x-2+scrx,y-10+scry); //A*算法寻路,得寻路时间
man[i].p=man[i].m1-1; //中止当前动作
}
if(dingweimap(x,y)==TRUE) //在小地图上点左键,调定位地图
smlmap(hdc); //显示小地图
return fidtim;
}

还要在game.cpp 中增加一个从搜索到的路径中取目标点find_a(int i)的函数。注意我
们在game.cpp 的manmove(int i)活动对象的移动函数中事先预留了一个接口。

if(find_p==1) find_a(i); //没加寻路时不要这句。
find_a(int i)函数从成功搜索到的路径中取目标点作为对象下一步移动的位置。
void game::find_a(int i)//没加寻路时不要。
{//加入自动搜索后,应执行的代码
if(man[i].fid<man[i].pk) //从搜索的路径中取目标点
{man[i].x0=man[i].ph[man[i].fid].x*GX+man[i].w/2;
man[i].y0=man[i].ph[man[i].fid].y*GY+man[i].h/2;
man[i].fid++;
if(man[i].x0<GX) man[i].x0=GX;
if(man[i].y0<GX) man[i].y0=GY;
bianfw(i); //方位转换
}
else //搜索路径取完
{man[i].x0=man[i].fx;
man[i].y0=man[i].fy;
}
}


8-3-3 FindPath(q,x,y) A*算法寻路

在获取游戏主角的目标点时, 我们没有直接调用A*算法寻路findpath(),而是调用
的FindPath(q,x,y)。FindPath(… )是一个调用A*算法寻路的上层函数,因为我们在调用
A*算法寻路的前后还有一些事要做。
A. 我们要想知道A*算法的执行时间。
B. 滤去无效点,如障碍点、目标点就是起始点
C. FindPath(… )调用后还要将各个动物的路径分别保存,并取得路径的起始点。
D. 将取得的各个动物路径进行优化。
FindPath(q,x,y) A*算法寻路

//**************************************************
// int FindPath(int i,int x,int y) A*算法寻路
// A. 由(x,y)目标点先滤出无效点。
// B. 设置起点、目标点调A*算法寻路
// C. 将寻得路径装入对象的路径中
// D. 取寻得路径的第一个点,作为对象移动的目标。
// 返回寻路时间或寻路信息。
//**************************************************
int gamepro::FindPath(int i,int x,int y)//A*算法寻路
{ if(find_p==0) return 0;
if(fidf==TRUE) return -4; //搜索路径正忙。
// A. 由(x,y)目标点先滤出无效点。
if(x<=0||y<=0) return -3; //无路。
int x0=x/GX,y0=y/GY;
if(m_fid.map[x0][y0]=='1')
{fidf=FALSE;return -1;} //目标点是障碍点。
if(x0==man[i].xix/GX&&y0==man[i].xiy/GY)
{fidf=FALSE;return -2;} //目标点是起始点。
if(x0<1||y0<1)
{fidf=FALSE;return -10;} //左上边界。
if((x0+1)>=WIDTH*SCRP0/GX||(y0+1)>=HEIGHT*SCRP0/GY)
{fidf=FALSE;return -20;} //右下边界。
// B. 设置起点、目标点调A*算法寻路
fidf=TRUE; //置搜索路径正忙
int tim=timeGetTime(); //进入时间。
m_fid.end_y =man[i].xix/GX; //目标点。
m_fid.end_x =man[i].xiy/GY; //
m_fid.start_y=x0; //起始点。
m_fid.start_x=y0;
if(m_fid.findpath()==-1) //A*算法寻路。
{fidf=FALSE;
return-1; //无路返回-1。
}
man[i].pk=zlpath(); //重组路径。
if(man[i].pk<0)
{fidf=FALSE; return -3;} //无路返回。
if(man[i].pk>250)
{ man[i].pk=0;
fidf=FALSE;
return -5; //路太远,超界。
}
// C. 将寻得路径装入对象的路径中
for(int j=0;j<man[i].pk;j++)
man[i].ph[j]=pathn[j]; //路径保存到对应的对象(i)。
man[i].fx=x;man[i].fy=y; //保留目标点。
// D. 取寻得路径的第一个点,作为对象移动的目标。
man[i].fid=1; //取路径计数。
if(man[i].pk>1) //取路径初值
{man[i].x0=man[i].ph[man[i].fid].x*GX+man[i].w/2;
man[i].y0=man[i].ph[man[i].fid].y*GY+man[i].h/2;
man[i].fid++;
}
fidf=FALSE; //取消搜索路径正忙
return timeGetTime()-tim; //返回寻路时间
}

这样我们的游戏中的对象们就脚踏实地在场景中行走了。


8-3-4 搜索算法.cpp

这一个搜索算法程序是取至

www.GameRes.com

网站上的共享算法,作者稍做了一
点改动。由于理解这个算法需要一定的数据结构知识,我们只是将它的源程序实录于
此,供有兴趣的读者参考研究。

#include "stdafx.h"
#include "搜索算法.h"
////////////////////////////////
findpt:: findpt(){} //构造函数
findpt::~findpt(){} //析构函数
////////////////////////////////
// 初始化队列
void findpt::init_queue()
{ queue=(LINK)malloc(sizeof(*queue));
queue->node=NULL;
queue->f=-1;
queue->next=(LINK)malloc(sizeof(*queue));
queue->next->f=MAXINT;
queue->next->node=NULL;
queue->next->next=NULL;
}
// 待处理节点入队列, 依靠对目的地估价距离插入排序
void findpt::enter_queue(TREE node,int f)
{ LINK p=queue,father,q;
while(f>p->f)
{ father=p; p=p->next;}
q=(LINK)malloc(sizeof(*q));
q->f=f,q->node=node,q->next=p;
father->next=q;
}
// 将离目的地估计最近的方案出队列
TREE findpt::get_from_queue()
{ TREE bestchoice=queue->next->node;
LINK next=queue->next->next;
free(queue->next);
queue->next=next;
stack[stacktop++]=bestchoice;
return bestchoice;
}
// 释放申请过的所有节点
void findpt::freetree()
{ int i;
LINK p;
for (i=0;i<stacktop;i++) free(stack[i]);
while (queue)
{ p=queue;
free(p->node);
queue=queue->next;
free(p);
}
free(queue);
}
// 估价函数,估价 x,y 到目的地的距离,估计值必须保证比实际值小
int findpt::judge(int x,int y)
{ int distance;
distance=abs(end_x-x)+abs(end_y-y);
return distance;
}
// 尝试下一步移动到 x,y 可行否
int findpt::trytile(int x,int y,TREE father)
{ TREE p=father;
int h;
if (map[y][x]!='0') return 1; //如果(x,y)处是障碍,失败
h=father->h+1;
if (h>=dis_map[y][x]) return 1; //如果曾经有更好的方案移动到(x,y)失败
dis_map[y][x]=h; // 记录这次到 (x,y) 的距离为历史最佳距离
// 将这步方案记入待处理队列
p=(TREE)malloc(sizeof(*p));
p->father=father;
p->h=father->h+1;
p->tile=tile_num(x,y);
enter_queue(p,p->h+judge(x,y));
return 0;
}
// 路径寻找主函数
int findpt::findpath()
{ TREE root;
int i,j;
stacktop=0;
for (i=0;i<map_h;i++)
for (j=0;j<map_w;j++)
dis_map[i][j]=MAXINT;
init_queue();
root=(TREE)malloc(sizeof(*root));
root->tile=tile_num(start_x,start_y);
root->h=0;
root->father=NULL;
enter_queue(root,judge(start_x,start_y));
for (;;)
{ int x,y,child;
root=get_from_queue();
if (root==NULL)
{*path=-1;
free(root); freetree(); //释放root
return -1;
}
x=tile_x(root->tile);
y=tile_y(root->tile);
if (x==end_x && y==end_y) break;//达到目的地成功返回
child =trytile(x, y-1,root); //向北 移动
child&=trytile(x+1,y-1,root); //向东北移动
child&=trytile(x+1,y, root); //向东 移动
child&=trytile(x+1,y+1,root); //向东南移动
child&=trytile(x, y+1,root); //向南 移动
child&=trytile(x-1,y+1,root); //向西南移动
child&=trytile(x-1,y, root); //向西 移动
child&=trytile(x-1,y-1,root); //向西北移动
if (child!=0) free(stack[--stacktop]);
//如果8 个方向均不能移动,释放这个死节点(释放栈顶节点)
}
// 回溯树,将求出的最佳路径保存在 path[] 中
for (i=0;root;i++)
{ path[i]=root->tile; root=root->father;}
path[i]=-1;
free(root); freetree(); //释放root
return 0;
}


8-3-5 搜索算法.h

#include "常数定义.h"
#define MAXINT 8192 //定义一个最大整数, 地图上任意两点距离不会超过它
#define STACKSIZE 40000 //保存搜索节点的堆栈大小
#define tile_num(x,y) ((y)*map_w+(x)) //将 x,y 坐标转换为地图上块的编号
#define tile_x(n) ((n)%map_w) //由块编号得出 x,y 坐标
#define tile_y(n) ((n)/map_w)
//定义结构
typedef struct node *TREE; // 树结构
struct node
{int h; int tile; TREE father;};
typedef struct node2 *LINK; // 树结构
struct node2
{ TREE node; int f; LINK next;};
class findpt
{public: findpt(); //构造函数
virtual~findpt(); //析构函数
public: //公有,外部可调用
int path[MAXINT];
char map[WIDTH*SCRP/GX+2][HEIGHT*SCRP/GY+2];//地图障碍格数据
short int dis_map[WIDTH*SCRP/GX+2][HEIGHT*SCRP/GY+2];
//保存搜索路径时,中间目标地最优解
int map_w,map_h; //地图障碍格宽和高
int start_x,start_y,end_x,end_y; //起点坐标,终点坐标
int findpath(); //路径寻找主函数
private: //私有,类内部使用
LINK queue; //保存没有处理的行走方法的节点
TREE stack[STACKSIZE]; //保存已经处理过的节点(搜索完后释放)
int stacktop;
void init_queue(); // 初始化队列
void enter_queue(TREE node,int f);
//待处理节点入队列,对目的地估价距离插入排序
TREE get_from_queue(); //将离目的地估计最近的方案出队列
void freetree(); //释放申请过的所有节点
int judge(int x,int y);
//估价函数,估价x,y 到目的地的距离,估计值必须保证比实际值小
int trytile(int x,int y,TREE father);//尝试下一步移动到x,y 可行否
};

8-4 动物在场景中随机运动

在这一章里我们开始让动物们跑动了。游戏主角的运动是靠我们点鼠标来指挥的,
动物们应该怎么跑呢?天知道,那就来个乱跑吧,又怎么个乱法呢?


8-4-1 随机数发生器rand()

计算机有个乱法,在港台的程序设计术语中它就叫乱数,我们称为随机数。rand()
是一个Windows 的API 函数,每调用它x=rand()一次,x 就得到一个大于等于0 的任意整
数,并且每次得到的数是不同的。在这里我们就用随机数发生器rand()来模拟,对各个
动物的鼠标点击目标点来实现动物的乱跑,当然乱跑还得加入自动寻径, 否则这些动
物就要上天入地毫无章法了。


8-4-2 randxy() 随机产生动物的移动目标点

下面我们在 for 循环里对所有的动物随机产生目标点。
实事上要哪个动物跑也是有条件的。
如果搜索路径正忙(第1 行)、正在移动和打斗(第3、4 行),那就continue 跳过。
rand()%20 是产生一个0~20 的随机数,第2 行的意思是20 个动物跳过19 个,不要
移动太频繁。因为randxy()函数产生动物移动目标是很快的。
滤去以上条件后第 5、6 行就生成了第q 个动物的随机目标点。其中WIDTH 是可视
场景的宽,HEIGHT 是可视场景的高;第5、6 行产生的随机目标点是以它原来位置点为
准,不超过可视场景高宽一半的范围(不加这个限制,动物就是长途迁移了)。
第 7~ 10 行是地图最大边界的检测,不能超过它的范围,否则程序就要出错。
第 11 行调FindPath(q,x,y)得到这个动物的行走路径man[q].ph。

//**************************************************
// randxy()//随机产生兽的移动目标
//**************************************************
void gamepro::randxy()//随机产生兽的移动目标
{ for(int q=0;q<rs;q++)
1 {if(fidf==TRUE) return; //搜索路径正忙返回。
2 if(rand()%20>0) continue; //20 个跳过19 个,不要移动太频繁。
3 if(man[q].lb!=1||man[q].fid<man[q].pk) //不是兽或正在移动
continue; //跳过。
4 if(man[q].zd==1) continue; //正在打斗,跳过。
5 int x=man[q].x0+WIDTH/2-rand()%WIDTH; //随机产生兽的x 位移。
6 int y=man[q].y0+HEIGHT/2-rand()%HEIGHT; //随机产生兽的y 位移。
7 if(x<GX) x=GX; //左边界检测。
8 if(y<GY) y=GY; //上边界检测。
9 if(x>WIDTH *SCRP0-GX) x=WIDTH *SCRP0-GX;//右边界检测。
10 if(y>HEIGHT*SCRP0-GY) y=HEIGHT*SCRP0-GY;//下边界检测。
11 if(FindPath(q,x,y)<0) continue; //A*算法寻路,搜索路径在man[q].ph 中。
12 man[q].p=man[q].m1-1; //中止当前动作。
}
}

现在我们在“脚踏实地Dlg.cpp”的时钟函数OnTimer(… )中加入
m_game.randxy();//随机产生兽的移动目标
好了,这下动物们也跑起来了,整个游戏看起来要生动多了。


8-5 选择地图

一般游戏都设有多个关卡,不同的关卡都对应有不同的地图(场景)。我们这里也
准备了几个不同的地图, 有小场景的(1×1 ), 也有大场景的(12×12)。下面我们在
OnOK()按键消息中来选择调入不同的场景地图。
在 MFC 里,有称为通用对话框类,为我们封装了的许多常用功能。这里我们使用
MFC 提供的“CFileDialog”类,它是为我们在调入文件或存贮文件时选择文件名的公用
对话框。调用时显示如图8-3 所示的对话框:
[置顶] 游戏开发技术总结(经典之作)第八集 脚踏实地游戏角色自动寻路、路径搜索算法第6张
图8-3
这样我们就可以方便地,在不同的磁盘和目录中选择我们需要的地图文件了。

void CMyDlg::OnOK()
{ KillTimer(1);KillTimer(2); //停止游戏主流程时钟和显示信息时钟
//选择文件对话框
CString name;
CString filt="dat (*.dat)|*.dat";
CFileDialog fileDlg(TRUE,"*.dat","*.dat",NULL,filt,this);
fileDlg.m_ofn.Flags|=OFN_FILEMUSTEXIST;
fileDlg.m_ofn.lpstrTitle="调入地图文件";
if(fileDlg.DoModal()==IDOK)
{ name=fileDlg.GetPathName(); //取包含有全路径文件名
m_game.loadmap(name); //调入地图
m_game.loadza (name); //调入障碍表
m_game.getsmap(); //生成小地图
}
SetTimer(1,TIMER,NULL);SetTimer(2,1000,NULL);
//重新设置启动游戏主流程时钟和显示信息时钟。
}

CFileDialog 类的说明,其中:
CFileDialog fileDlg(TRUE,"*.dat","*.dat",NULL,filt,this);
为 Open(打开"*.dat"类型文件)对话框。
CFileDialog fileDlg(FALSE,"*.dat","*.dat",NULL,filt,this);
为Save As(保存"*.dat"类型文件)对话框。
CFileDialog 类辅助成员函数
[置顶] 游戏开发技术总结(经典之作)第八集 脚踏实地游戏角色自动寻路、路径搜索算法第7张


8-6 OnTimer(⋯)时钟函数中的时钟段

在 OnTimer(⋯) 中我们分有3 个时钟段。
if(nIDEvent==1) 中为游戏的主流程,调用周期TIMER 是在常数定义.h 中定义为TIMER
毫秒。
if(nIDEvent==2) 显示信息,随机产生动物的移动目标。调用周期在OnOK()中设置为
1 秒。
if(nIDEvent==3) 启动时调用文件选择,调用周期在OnInitDialog()中设置,调入文件后
就不再使用。

void CMyDlg::OnTimer(UINT nIDEvent) //时钟函数,[类向导中定义生成]
{ if(nIDEvent==1)//动画刷屏
{tim=timeGetTime(); //开始时间
CClientDC dc(this); //客户区设备环境
m_game.mlmap(); //地图块移动拼接
for(int i=0;i<m_game.rs;i++)
m_game.setobj(i); //对象显示
BitBlt(dc.m_hDC,2,10,WIDTH,HEIGHT,m_game.BkDC1,0,0,SRCCOPY);
//用Bk1 刷新窗口
SetTextColor(dc.m_hDC,RGB(0,0,255)); //字色
SetBkMode(dc.m_hDC,TRANSPARENT);
TextOut(dc.m_hDC,200,30,"我现在可以自己找路,再不乱窜了。",32);
if(m_game.rs>1) m_game.smlmap(dc.m_hDC); //显示小地图
tim=timeGetTime()-tim; //显示时间=结束时间-开始时间
}
if(nIDEvent==2)//显示信息
{char cc[255],c1[255];
int q=m_game.mann;
sprintf(cc,"地图[X:%4d Y:%4d] 人[x:%4d y:%4d]",
m_game.scrx,m_game.scry,m_game.man[q].xix,m_game.man[q].xiy);
SetDlgItemText(IDC_STATIC5, cc);
sprintf(cc,"[显示区对象数:%3d] [%3dms/屏] [CPU 占用%3d%]",
m_game.mans,tim,tim*100/TIMER);
SetDlgItemText(IDC_STATIC4, cc);
switch(fidtim)
{case -1: {strcpy(c1,"障碍点"); break;}
case -2: {strcpy(c1,"起始点"); break;}
case -3: {strcpy(c1,"无路"); break;}
case -4: {strcpy(c1,"搜索忙"); break;}
case -5: {strcpy(c1,"太远了"); break;}
case -10: {strcpy(c1,"左上边界"); break;}
case -20: {strcpy(c1,"右下边界"); break;}
default: {sprintf(c1,"搜索时间:%dms",fidtim);}
}
sprintf(cc,"地图%dX%d 障碍%dX%d %s,%d",
WIDTH*m_game.SCRP0,HEIGHT*m_game.SCRP0,
WIDTH*m_game.SCRP0/GX,HEIGHT*m_game.SCRP0/GY,
c1,m_game.man[q].pk);
SetDlgItemText(IDC_STATIC3, cc);
m_game.randxy(); //随机产生兽的移动目标
}
if(nIDEvent==3) //启动延时
{KillTimer(3);
OnOK(); //调地图
}
CDialog::OnTimer(nIDEvent);
}

8-7 图形光标

在一些应用软件中,我们常常看到一些非Windows 系统的个性化光标;在游戏中这
种个性化光标特别重要,从光标的图示上我们可以对用户进行操作提示, 同时又美化
了游戏。
下面我们开始在我们的游戏中引入图形光标。


8-7-1 新建光标

在 VC 集成编辑器中选择菜单Insert 下的Resource⋯ , 进入Insert Resource 对话框;
选择新建光标Cursor。

[置顶] 游戏开发技术总结(经典之作)第八集 脚踏实地游戏角色自动寻路、路径搜索算法第8张


图8-4


8-7-2 画光标

我们需要的光标图形可以在 ResourceView(资源视图)中自己画,也可以通过图形
拷贝从其它图形编辑中拷贝到来。

[置顶] 游戏开发技术总结(经典之作)第八集 脚踏实地游戏角色自动寻路、路径搜索算法第9张
图8-5


8-7-3 定义光标变量

光标变量的定义,必须在CmyApp.h 中进行。下面我们定义了m_Cur0- m_Cur3 四个
光标变量。

class CMyApp : public CWinApp
{ public:
CMyApp();
HCURSOR m_Cur0; //普通光标变量
HCURSOR m_Cur1; //允许光标变量
HCURSOR m_Cur2; //枪头光标变量
HCURSOR m_Cur3; //禁止光标变量
HCURSOR m_Cur; //当前光标
⋯⋯
}


8-7-4 调入图形光标

调图形光标到光标变量,必需在CmyApp.cpp 的InitInstance()中进行。我们用MFC 的

LoadCursor(IDC_n) 将IDC_n 指定的光标调入光标变量。
BOOL CMyApp::InitInstance()
{ AfxEnableControlContainer();
HANDLE hMutex=CreateMutex(NULL,FALSE,"脚踏实地"); //阻止程序二次运行
if(hMutex==NULL||ERROR_ALREADY_EXISTS==::GetLastError())
return FALSE;
m_Cur0 = LoadCursor(IDC_CURSOR0); //调普通光标
if (!m_Cur0) return FALSE;
m_Cur1 = LoadCursor(IDC_CURSOR1); //调允许光标
if (!m_Cur1) return FALSE;
m_Cur2 = LoadCursor(IDC_CURSOR2); //调枪头光标
if (!m_Cur2) return FALSE;
m_Cur3 = LoadCursor(IDC_CURSOR3); //调禁止光标
if (!m_Cur3) return FALSE;
⋯⋯
}


8-7-5 图形光标的使用

CMyApp* pApp = (CMyApp*)AfxGetApp();//取类变量,因为光标变量是在CMyApp 中定义的
SetCursor(pApp→ m_Cur3); //指定的图形光标生效
我们这里图形光标是在 OnMouseMove(⋯)鼠标移动的消息中使用。
void CMyDlg::OnMouseMove(UINT nFlags, CPoint point)// 鼠标移动的消息
{ int x=point.x-2-dowx;
int y=point.y-10-dowy;
int x0=(x+m_game.scrx)/GX,y0=(y+m_game.scry) /GY;
CMyApp* pApp = (CMyApp*)AfxGetApp();
if(x>0&&x<WIDTH&&y>0&&y<HEIGHT)
{if(m_game.m_fid.map[x0][y0]=='1'|| //目标点是障碍点
(x0<1||y0<1)|| //左上边界
((x0+1)>=WIDTH*m_game.SCRP0/GX||(y0+1)>=HEIGHT*m_game.SCRP0/GY)
//右下边界
)
pApp→m_Cur=pApp→m_Cur3; //设为禁止光标
else pApp→m_Cur=pApp→m_Cur1; //设为允许光标
}
else pApp→m_Cur=pApp→m_Cur0; //设为普通光标
SetCursor(pApp→m_Cur); //图形光标生效
CDialog::OnMouseMove(nFlags, point);
}

当鼠标在场景地图外时,光标设为普通光标[置顶] 游戏开发技术总结(经典之作)第八集 脚踏实地游戏角色自动寻路、路径搜索算法第10张
当鼠标在障碍点和大地图边界时,光标设为禁止光标[置顶] 游戏开发技术总结(经典之作)第八集 脚踏实地游戏角色自动寻路、路径搜索算法第11张,否则为允许光标[置顶] 游戏开发技术总结(经典之作)第八集 脚踏实地游戏角色自动寻路、路径搜索算法第12张
详细内容请看本章实例程序:“脚踏实地”。


8-8 小结

在这一章里,我们学了以下知识和方法。
1.设置障碍,建立地图障碍数字化模型
2.加入游戏角色的自动寻路的A*搜索算法。游戏中的主角和动物都可以自动寻路。
3.加入其它动物在场景中随机活动的方法。
4.加入选择地图的功能。
5.加入图形光标。

免责声明:文章转载自《[置顶] 游戏开发技术总结(经典之作)第八集 脚踏实地游戏角色自动寻路、路径搜索算法》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇VScode sftp插件使用【文件修改,重命名,删除均可自动同步】win10系统谷歌浏览器崩溃的解决方法下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

钩子(Hook)专题

基本概念 运行机制 钩子类型 作者 基本概念 钩子(Hook),是Windows消息处理机制的一个平台,应用程序可以在上面设置子程以监视指定窗口的某种消息,而且所监视的窗口可以是其他进程所创建的。当消息到达后,在目标窗口处理函数之前处理它。钩子机制允许应用程序截获处理window消息或特定事件。 钩子实际上是一个处理消息的程序...

C++异常

一、什么是异常处理         一句话:异常处理就是处理程序中的错误。 二、为什么需要异常处理,以及异常处理的基本思想         C++之父Bjarne Stroustrup在《The C++ Programming Language》中讲到:一个库的作者可以检测出发生了运行时错误,但一般不知道怎样去处理它们(因为和用户具体的应用有关);另一...

Android系统中的广播(Broadcast)机制简要介绍和学习计划

  在Android系统中,广播(Broadcast)是在组件之间传播数据(Intent)的一种机制;这些组件甚至是可以位于不同的进程中,这样它就像Binder机制一样,起到进程间通信的作用;本文通过一个简单的例子来学习Android系统的广播机制,为后续分析广播机制的源代码作准备。         在Android系统中,为什么需要广播机制呢?广播机制,...

flex集成IFrame,IFrame集成UnityWebPlayer直接通讯调用解决方式

做Web开发一般是flex与JS交互,UnityWebPlayer与JS交互。 它们之间相互调用比較常见。 /** * Flex调用Javascript函数 * @params functionName:String Javascript函数名称 * @params ...params Javascript函数參数 * @retu...

关于消息(Message)和消息队列(Message Queues)

参考:http://msdn.microsoft.com/en-us/library/windows/desktop/ms644927(v=vs.85).aspx 与基于DOS的应用程序不同,基于Windows的应用程序是事件驱动的。他们不显式指定功能调用(如C运行时库的调用)来获得输入,相反地,他们会等待系统来传递输入。系统为应用程序中的每个窗口传递所...

ASM

在学习汇编之前,我们要介绍一下常用的函数调用约定,以便我们对于一些指令的理解。 函数调用约定 常见的函数调用约定:stdcall , cdecl, fastcall, thiscall, naked call 1, __cdecl(C调用约定.)C/C++ 缺省调用方式 1)压栈顺序: 函数参数从右到左 2)参数栈维护: 由调用函数把参数弹出栈,传送参数的...