让游戏的角色能够自动寻路; 并让所有的动物都动起来。另外我们在这一章将介
绍图形光标技术。
游戏中加入了山石、树木场景后,就应该有对游戏角色行走路线的限制。这种限
制是我们通过在游戏场景中设置障碍点 来实现的。不管是人还是动物,遇到障碍
点,都必须绕道而行。
图8-1
在我们不希望角色经过或占据的地方做上一个标记,例如树根处、石头底部。这
些障碍标记可以是我们事先赋予景物的属性,也可以是我们在游戏场景编辑器中单独
设置的。有了障碍标记后,游戏的场景就可以视为图8-2 所示模型。
图8-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)点是障碍,不能去,改走其它方向。
这就是程序设计中典型的走迷宫算法。
在我们的游戏中也有用地图编辑器设置的,这样一个与山石、树木对应的障碍表。
与地图同名的障碍表 *.map 的数据格式 。
第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*搜索算法。
由于对 A*搜索算法的理解需要一定的离散数学和数据结构理论知识,这里我们直
接给出A*搜索算法的类文件,了解它的使用方法。它的原理留给读者在后面的深入学
习中再理解(实际上我们在编程中将直接调用许多Windows 自带的API 函数,我们只要
知道怎么用它就行了,至于它是如何实现的,我们可以不用理会)。
A*搜索算法是由7 个函数组成的,其中int findpath()路径寻找函数是搜索算法的主函数。
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 坐标 }
好,现在我们在指挥游戏主角行走的按左键函数中加入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; } }
在获取游戏主角的目标点时, 我们没有直接调用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; //返回寻路时间 }
这样我们的游戏中的对象们就脚踏实地在场景中行走了。
这一个搜索算法程序是取至
网站上的共享算法,作者稍做了一
点改动。由于理解这个算法需要一定的数据结构知识,我们只是将它的源程序实录于
此,供有兴趣的读者参考研究。
#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; }
#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 可行否 };
在这一章里我们开始让动物们跑动了。游戏主角的运动是靠我们点鼠标来指挥的,
动物们应该怎么跑呢?天知道,那就来个乱跑吧,又怎么个乱法呢?
计算机有个乱法,在港台的程序设计术语中它就叫乱数,我们称为随机数。rand()
是一个Windows 的API 函数,每调用它x=rand()一次,x 就得到一个大于等于0 的任意整
数,并且每次得到的数是不同的。在这里我们就用随机数发生器rand()来模拟,对各个
动物的鼠标点击目标点来实现动物的乱跑,当然乱跑还得加入自动寻径, 否则这些动
物就要上天入地毫无章法了。
下面我们在 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();//随机产生兽的移动目标
好了,这下动物们也跑起来了,整个游戏看起来要生动多了。
一般游戏都设有多个关卡,不同的关卡都对应有不同的地图(场景)。我们这里也
准备了几个不同的地图, 有小场景的(1×1 ), 也有大场景的(12×12)。下面我们在
OnOK()按键消息中来选择调入不同的场景地图。
在 MFC 里,有称为通用对话框类,为我们封装了的许多常用功能。这里我们使用
MFC 提供的“CFileDialog”类,它是为我们在调入文件或存贮文件时选择文件名的公用
对话框。调用时显示如图8-3 所示的对话框:
图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 类辅助成员函数
在 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); }
在一些应用软件中,我们常常看到一些非Windows 系统的个性化光标;在游戏中这
种个性化光标特别重要,从光标的图示上我们可以对用户进行操作提示, 同时又美化
了游戏。
下面我们开始在我们的游戏中引入图形光标。
在 VC 集成编辑器中选择菜单Insert 下的Resource⋯ , 进入Insert Resource 对话框;
选择新建光标Cursor。
我们需要的光标图形可以在 ResourceView(资源视图)中自己画,也可以通过图形
拷贝从其它图形编辑中拷贝到来。
光标变量的定义,必须在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; //当前光标 ⋯⋯ }
调图形光标到光标变量,必需在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; ⋯⋯ }
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); }
当鼠标在场景地图外时,光标设为普通光标。
当鼠标在障碍点和大地图边界时,光标设为禁止光标,否则为允许光标。
详细内容请看本章实例程序:“脚踏实地”。
在这一章里,我们学了以下知识和方法。
1.设置障碍,建立地图障碍数字化模型
2.加入游戏角色的自动寻路的A*搜索算法。游戏中的主角和动物都可以自动寻路。
3.加入其它动物在场景中随机活动的方法。
4.加入选择地图的功能。
5.加入图形光标。