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

    8-1 任务


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


    8-2 设置障碍


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


           游戏中加入了山石、树木场景后,就应该有对游戏角色行走路线的限制。这种限
    制是我们通过在游戏场景中设置障碍点 来实现的。不管是人还是动物,遇到障碍
    ,都必须绕道而行。


                                             图8-1
              在我们不希望角色经过或占据的地方做上一个标记,例如树根处、石头底部。这
    些障碍标记可以是我们事先赋予景物的属性,也可以是我们在游戏场景编辑器中单独
    设置的。有了障碍标记后,游戏的场景就可以视为图8-2 所示模型。

                                                   图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 的数据格式 。


    第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 所示的对话框:

                                          图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 类辅助成员函数


    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-4


    8-7-2 画光标


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


                                               图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);
    }



    当鼠标在场景地图外时,光标设为普通光标
    当鼠标在障碍点和大地图边界时,光标设为禁止光标,否则为允许光标
    详细内容请看本章实例程序:“脚踏实地”。


    8-8 小结


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

  • 相关阅读:
    shell eval命令
    嘟嘟嘟
    07 linkextractor的基本用法
    rabbitmq消息队列
    5. 哨兵集群
    4.主从同步
    3. redis持久化存储
    2. redis 安全
    1.redis基础
    06. scrapy的Request对象
  • 原文地址:https://www.cnblogs.com/xinyuyuanm/p/3073182.html
Copyright © 2011-2022 走看看