zoukankan      html  css  js  c++  java
  • [置顶] 游戏开发技术总结(经典之作)第五集 轻松走四方游戏角色的移动和动作的切换

    5-1 任务


            我们在这章的任务是,用鼠标人为地控制游戏角色,在地图场景中向不同的方位
    (北、东北、东、东南、南、西南、西、西北,8 个方位) 行走。


    5-2 角色的移动


           我们让角色在屏幕上透明显示函数;
           TransparentBlt2(dc.m_hDC, x, y,w,h,MemDC,0,0,RGB(0,0,0));//角色透明显示
    其中(x,y) 分别是角色在屏幕上的显示位置,它的值是决定角色在屏幕上的显示
    位置,所以它的变化,就会形成角色的位置在屏幕的移动。
    我们编程获取在场景中的鼠标按键点{x0,y0},将按键点(x0,y0)作为角色移动的目
    标位置。在时钟消息OnTimer()中将角色的当前位置(x,y)向角色目标位置(x0,y0)靠
    近, 并每次只移动一定的量。那么当前位置( x,y ) 的变化反映到角色显示
    TransparentBlt2(⋯⋯)上就形成了角色追逐鼠标按键的结果。


    5-2-1 建立鼠标左键按键消息


             和建立时钟消息一样,在类向导中选择WM_LBUTTONDOWN, 双击后,在成员功能栏
    (Member functions)可以看到已生成的左键按键消息函数ON_WM_LBUTTONDOWN。再按编
    辑代码(Edit Code), 就进入到左键按键消息函数OnLButtonDown (⋯⋯ )中了。


                                                            图5-1
          程序运行时只要是在窗口内按鼠标左键,都会执行OnLButtonDown(… … )里面的
    程序。OnLButtonDown(UINT nFlags, CPoint point)的入口参数point 为点数据类型,它就
    是在当前窗口中鼠标按键的坐标( point.x,point.y)。

    void CMyDlg::OnLButtonDown(UINT nFlags, CPoint point)
    //按左键消息,[类向导中定义生成]
    {
    CDialog::OnLButtonDown(nFlags, point);
    }


     


    5-2-2 角色定义


           下面开始 ,我们的游戏角色将按控制的要求做一些复杂的动作,所以我们必须对
    角色赋予一些特征值。
            游戏的角色根据要求,应有多个同时存在的特征属性,例如,它的当前位置、移
    动的目标位置、是谁、什么动作、面向方位等等。可能有的游戏角色的特征属性达几
    十种,如何表示复杂众多的特征属性?这里我们要用到C++的一种复杂数据的表示方法
    ____结构类型。
    现在我们定义一个名为MAN 的结构类型,以此来表示游戏角色的特征。

    typedef struct
    { short int jisu; //序号
    short int xix,xiy; //角色座标
    short int x0,y0; //目标位置
    short int p; //计数
    short int m0,m1; //位置初值、终值
    short int zs; //动作:人[0 站1 走2 刺3 劈4 倒]
    short int js; //角色:人[0 男1 女]
    short int fw; //方位: [0 南1 西南2 西3 西北4 北5 东北6 东7 东南]
    } MAN; //对象结构



     

          结构类型定义后,就可以用我们自己的新结构来定义一个结构变量表示游戏的角
    色了。
          我们定义:MAN man[3]; //用MAN(自定义结构类型)定义一个角色变量数组。
    这里定义的角色变量数组 man[3]下标为3;可以表示3 个角色,man[0],man[1],man[2]。
    有了角色变量数组 man[]我们就可以表示角色的一些特征了。
    结构的用法也不难,对照MAN 的定义看下面。
    man[0].xix=45 表示第 0 个角色的当前x 位置为45。
    man[0].zs=1 表示第 0 个角色的动作为走。
    man[2].fw=3 表示第 2 个角色的方位,也就是动作的面向朝西。
    现在我们在按键消息 OnLButtonDown(… … )加入获取角色目标位置的程序。

    void CMyDlg::OnLButtonDown(UINT nFlags, CPoint point)
    //取针对主角的目标位置,[类向导中定义生成]
    1 { int x=point.x,y=point.y;
    2 man[0].x0=x; //获得目标位置x
    3 man[0].y0=y; //获得目标位置y
    4 man[0].p=man[0].m1-1; //中止当前动作
    CDialog::OnLButtonDown(nFlags, point);
    }



    程序注释:
    第 1 行, point 就是在当前窗口内按键的坐标点(point.x,point.y)
    第 2、3 行,将获取的按键坐标记录在角色变量的目标位置分量中。
    第 4 行,中止当前动作(当前动作中止后,在进行下一动作时获取目标位置)。


    5-2-3 角色移动


           角色的移动算法基本思想是:根据
    角色的当前位置(x0,y0) 与目标位置
    (xix,xiy) 计算的位置差(x,y),来判
    断角色的移动方向;并在这个方向上移
    动一个规定的步长(stx,sty)。


                                              图5-2
    角色移动在游戏中是常用的操作,我们也把它做成一个功能函数manmove(int i)。

    A.manmove(int i)角色移动功能函数
    //**************************************************
    // manmove(int i) 活动对象的移动
    // 由当前、目标位置的差,计算当前位置向不同方位的改变。
    //**************************************************
    void manmove(int i) //活动对象的移动
    1{ int stx,sty,qx,qy;
    2 switch(man[i].zs) //动作
    3 {case 1: {stx=4;sty=2;break;} //走步长
    4 case 2: {stx=9;sty=6;break;} //跑步长
    5 default:{stx=2;sty=1;break;}
    6 }
    7 qx=man[i].x0-man[i].xix; //x 方向当前位置、目标位置差
    8 qy=man[i].y0-man[i].xiy; //y 方向当前位置、目标位置差
    9 if (qx==0&&qy==0) return ; //到达目标点,返回
    10 int qxa=abs(qx); //x 方向位置差绝对值
    11 int qya=abs(qy); //y 方向位置差绝对值
    12 if(qxa<stx) stx=qxa; //位置差不足步长,步长取位置差的值
    13 if(qya<sty) sty=qya; //
    14 if(qx>0) man[i].xix+=stx; //向东走;若下面qy>0,就是东南方向了。
    15 if(qy>0) man[i].xiy+=sty; //向南走
    16 if(qx<0) man[i].xix-=stx; //向西走
    17 if(qy<0) man[i].xiy-=sty; //向北走
    }
    


     

    B.活动对象的移动的注释
    第 1 行,定义移动步长、位置差。
    第 2行,判断动作。
    第 3行,设走步长
    第 4 行,设跑步长
    第 7~ 8 行,求当前位置、目标位置差
    第 9 行,如果到达目标点,返回
    第 10~ 11 行,求位置差绝对值
    第 12、13 行,当位置差小于设置的步长时,步长应改为位置差。
    例如,9 米,每步跨2 米,最后到达的一步就只有1 米了。
    后 4 行看起来只写了4 个方位,但当第14、15 行都条件满足执行后,就是向东南
    方向行走了(同时有向东走的移动量+stx 和向南走的移动量+sty)。


    5-3 角色的动作变化


    5-3-1 动作转换函数


           角色在移动的时候,面向要随之改变,向东走,面要朝东, 这可以由当前、目标
    位置的差,计算活动图形的方向取向。还有姿势也要有变化:走,手要甩、脚要迈。
    下面我们就来编这段程序。

    bianfw(int q)角色动作、方位转换
    //**************************************************
    // bianfw(int q)角色q 的动作、方位转换
    // 由当前、目标位置的差,计算活动图形的方向取向。
    //**************************************************
    void bianfw(int q) //方位转换
    1{ int qx=man[q].x0-man[q].xix; //x 当前,目标位置差
    2 int qy=man[q].y0-man[q].xiy; //y 当前,目标位置差
    3 if(qx==0&&qy==0){man[q].zs=0;goto aa;} //为0,动作为站,方位保留
    4 man[q].zs=1;
    5 if(qx<0&&qy>0) {man[q].fw=1;goto aa;} //取西南向
    6 if(qx<0&&qy<0) {man[q].fw=3;goto aa;} //取西北向
    7 if(qx>0&&qy<0) {man[q].fw=5;goto aa;} //取东北向
    8 if(qx>0&&qy>0) {man[q].fw=7;goto aa;} //取东南向
    9 if (qy>0) {man[q].fw=0;goto aa;} //取南向
    10 if(qx<0) {man[q].fw=2;goto aa;} //取西向
    11 if (qy<0) {man[q].fw=4;goto aa;} //取北向
    12 if(qx>0) {man[q].fw=6;goto aa;} //取东向
    13 aa: man[q].m0=man[q].js*400+zjdz[man[q].zs].qi
    14 +man[q].fw*zjdz[man[q].zs].bc; //位置初值
    15 man[q].m1=zjdz[man[q].zs].bc+man[q].m0; //位置终值
    16 man[q].p=man[q].m0; //数量计数
    }



    第1、2 行,求当前位置和目标位置差
    第3 行,位置差为0 ,动作为站(停下来) ,方位保留。程序跳到第13 行。
    第 4 行,设动作为走。
    第 5~ 12 行,根据位置差分别取方向。结合角色移动的程序和前面的坐标方位图(图
                          5-2),这段程序也是好理解的。
    第 13~ 16 行,这是根据游戏的角色js、方位fw、姿势zs 在角色图片序列中选择合
                        适的一组动画(在后面我们将它单独作为一个函数)。


    5-3-2 人物图形规律分析


    1.人物图形规律


           这里我们使用的游戏角色的图片序列有以下规律:
    在“图片\人\”目录下共有400*2 幅人物图片。
    ●角色js;每个角色400 幅,有2 个角色;角色取值为js={0,1}。
                     记为 js*400 至( js+1)*400-1。
                    当 js=0, 为角色男的400 幅图片; 0*400 至1*400-1 幅图片。
                    当 js=1, 为角色女的400 幅图片; 1*400 至2*400-1 幅图片。
    ●方位fw;每个角色有8 个方位。
                    fw={0,7}(0 南1 西南2 西3 西北4 北5 东北6 东7 东南)。
    ●姿势zs;每个角色在一个方位就有6 个姿势。
                     zs={0,5}(0 站,1 走, 2 刺, 3 劈, 4 倒,5 尸体)。
    例:角色js=0 的详细情况见下表,表中数字是对应方位姿势的图片序列。
    例如:方位北、动作走;80-90。即为c080.bmp-c090.bmp 这10 幅图片。


                                                                            图5-3
    下面是角色js=0 的人物站姿(站,每个方位有5 幅)的8 个方位图片


                                                                    图5-4


    2.每个动作的起点和步数


    根据(图5-3)表所示,我们定义结构类型JCDZ,以记录每个动作的起点和步数。

    typedef struct
    {int qi; //动作起点
    unsigned short bc; //动作步数
    } JCDZ; //动作结构



     

           用结构类型JCDZ 定义结构数组zjdz[6] ,表示人的动作结构并赋值。
    JCDZ zjdz[6]={ 0,5 40,10 120,10, 200,10, 280,10, 360,5 };
    // 0 站, 1 走, 2 刺, 3 劈, 4 倒, 5 尸
    说明,例如角色(js=0)向西( fw=2)走(zs=1)。
    下面我们来看需要的图片是怎么计算出来的。
    由角色 q 的动作zs、方位fw,代入转换函数bianfw(int q)的第13、14 行。

    13 aa: man[q].m0=man[q].js*400+zjdz[man[q].zs].qi
    14 +man[q].fw*zjdz[man[q].zs].bc; //位置初值
    15 man[q].m1=zjdz[man[q].zs].bc+man[q].m0; //位置终值
    16 man[q].p=man[q].m0; //数量计数



    第13、14 行公式(这两行实为一行):
    现在我们设人向西走,man[0].zs=1;man[0].fw=2;
    第一步:计算向西走的图片初值
    man[0 ].m0= zjdz[man[0].zs].qi+man[0].fw*zjdz[man[0].zs].bc;//位置初值。
    man[0 ].m0= zjdz[1].qi+2*zjdz[1].bc;//姿势为走时有zs=1,所以有zjdz[1]。
    从 JCDZ zjdz[6]={0,5, 40,10,120,10,200,10,280,10,360,5};
    查得 zjdz[1]的分量: zjdz[1].qi=40 起点, zjdz[1].bc=10 步数;
    所以有 man[0].m0=40+2*10=60,从第60 幅图片开始。
    第二步:计算向西走的图片终值
    man[q].m1=zjdz[man[q].zs].bc+man[q].m0; //位置终值
    man[0 ].m1= zjdz[1].bc+ man[0].m0=10+60=70;结束在第70 幅图片。
    向西走就是有
    p= man[0].m0(=60);
    p< man[0].m1(<70)。
    即向西走是从“c060.bmp”开始到“c069.bmp”结束的10 幅图形。


                                                                                    图5-5


    5-3-3 动物图形偏移值

     
           现在游戏中的图形量是越来越多,为了减少图形空间无效占有数,在动画序列中
    一般都引入了图形偏移值。图形偏移值的原理介绍如下。


    1.图形偏移值的原理


             看图 A 两幅图,它是人向左右挥斧的动作;如果我们都按图的左上角为准显示(并
    表示人挥斧转圈的动作),将得到什么效果呢?视觉效果一定是,以人手为中心的脚也
    在滑的挥斧转圈动作,显然这是不对的。
    真实的应该是以B 上图为准,B 下图向左偏移一定
    数,这样看到的效果就是人以脚为支点的转圈动作。这
    样才符合实际的视觉效果。


            在目录“运行程序/图片/人/”下面的800 幅人的动作图形(*.bmp),有800 个与之
    对应的文本文件(*.txt ),文本文件里的数据就是对应图形显示时的X、Y 方向的位置偏
    移量。
    所以我们在读取图片时还要读入对应的偏移量。
    在 getpic(CString cc,int p) 调图片到相关位图的函数中我们加入一个读入外部数据的
    程序段来读入偏移量。
    这里使用了一种读入外部数据的最简单的方法(注意,读入外部数据的标准方式)。


    2.读入外部数据的方法


    这是一个典型的读入外部文件数据到内存变量的程序。

    FILE *f; //定义文件句柄
    f=fopen("game.dat","r"); //读方式打开文件“game.dat”
    fscanf(f,"%d\n",&rs); //读一个数据到整形变量rs 中
    fclose(f); //关闭文件


     


    3.getpic(⋯)调图片中的读偏移量


           现在我们在原来的 getpic(CString cc,int p) 调图片到相关位图这个函数中,加入从与
    图形文件相关的偏移量文件中读入位置偏移量的程序。

    //**************************************************
    // getpic(CString cc,int p) 调图片到相关位图
    // 由p 得到将调的图形文件名。
    // 在指定目录中调入图形到相关位图bit
    // 调入动物的图形偏移值
    //**************************************************
    BOOL getpic(CString cc,int p)//调图片到相关位图
    { char name[256];
    //A.调cc 指定的图形
    SetCurrentDirectory(appdir); //置当前目录
    sprintf(name,"%s%s/c%05d.bmp",dir,cc,p); //生成将调的图形文件名
    loadbmp(name); //调BMP 图片
    //B.调cc 指定的图形的偏移值。
    1 sprintf(name,"%s%s/c%05d.txt",dir,cc,p);
    2 FILE *f;
    3 f=fopen(name,"r");
    4 if(f!=NULL)
    5 {if(cc=="人") fscanf(f,"%d,%d",&rbufx[p],&rbufy[p]);//人的偏移量
    6 if(cc=="兽") fscanf(f,"%d,%d",&sbufx[p],&sbufy[p]);//兽的偏移量
    7 fclose(f);
    }
    return TRUE;
    }



    第1行,生成与图形相关的偏移量文件名name
    第 2行,定义文件句柄
    第 3行,读方式打开文件name
    第 4行,打开文件成功
    第 5行,是人,读对应的人偏移量到rbufx[p]、rbufy[p]
    第 6行,是兽,读对应的兽偏移量到sbufx[p]、sbufy[p]
    第 7行,关闭文件


    5-3-4 在时钟消息中加入角色移动和动作转换


     

            对照上一章 4-3 中时钟函数OnTimer()的程序,这里加入了结构变量man,在第3
    行的刷屏改为整屏刷新的,同时我们还加入了调角色的偏移位置第8~16 行。
    注意,我们所用的《帝国2》的人、动物图片,原图片为了节约图形量只有北、西
    北、西、西南、南5 个方位。我们用其它工具将西北、西、西南三个方位的图形180
    度的旋转生成了东北、东、东南3 个方位。而东北、东、东南3 个方位的偏移值没有
    转换(仍然是西北、西、西南三个方位的)。我们在这里用第13 行程序来转换。

    void CMyDlg::OnTimer(UINT nIDEvent) //时钟函数,[类向导中定义生成]
    { CClientDC dc(this); //客户区设备环境
    //A.用地图刷新窗口;将DCBak 指向的位图拷贝到dc.m_hDC 指向的当前窗口。
    1 int wid=rect.Width(); //当前窗口宽
    2 int hei=rect.Height(); //当前窗口高
    3 BitBlt(dc.m_hDC,0,0,wid,hei,DCBak,0,0,SRCCOPY); //用地图刷新窗口
    //B.角色移动
    4 manmove(0); //角色移动
    //C.调角色图片到MemDC
    5 if(getpic("人",man[0].p)==FALSE) //调角色图片
    6 {AfxMessageBox(cc+"没找到!");return;}
    7 SelectObject(MemDC,bit); //调入的位图关联到角色设备
    场景
    //D.调角色的偏移位置
    8 int x,y;
    9 if(man[0].lb!=2) //不是景,景是静物没有偏移位
    置
    10 {int x0=0,y0=0;
    11 if(man[0].lb==0) {x0=rbufx[man[0].p];y0=rbufy[man[0].p];}
    12 if(man[0].lb==1) {x0=sbufx[man[0].p];y0=sbufy[man[0].p];}
    13 if(man[0].fw>4) x0=w-x0; //是东北、东、东南方位
    14 x=man[0].xix-x0; //对象显示的x 位置
    15 y=man[0].xiy-y0; //对象显示的y 位置
    }
    //E.透明显示
    16 TransparentBlt2(dc.m_hDC,x,y,w,h,MemDC,0,0,w,h,RGB(255,255,255));
    //F.下一动作
    17 man[0].p++; //下一动作
    18 if(man[0].p>=man[0].m1) //若本动作完成
    19 bianfw(0); //进行方位转换
    20 CDialog::OnTimer(nIDEvent);
    }



    OnTimer(⋯)时钟函数注释
    A.用地图刷新窗口
    第 1、2 行,取当前窗口宽、高。
    第 3行,用地图刷新窗口,将DCBak 指向的位图拷贝到dc.m_hDC 指向的当前窗口。
    B.角色移动
    第 4行,角色移动。
    C.调角色图片到MemDC
    第 5、6 行,调角色图片, 出错返回。
    第 7 行,调入的角色位图关联到角色设备场景。
    D.调角色的偏移位置
    第 8行,定义对象显示的位置变量。
    第 9行,不是景,才加入偏移量;景是静物没有偏移位置。
    第 10 行,定义暂存位置变量。
    第 11 行,是人, 取人的偏移量。
    第 12 行,是动物,取动物的偏移量。
    第 13 行,是东北、东、东南方位,转换偏移量。
    第 14、15 行,最终的对象显示的位置。
    第 16 行,透明显示。
    第 17 行,下一动作。
    第 18、19 行,若本动作完成,进行方
    位转换。
    程序的执行过程是, 在一个时钟周期
    里刷新屏幕、进行角色移动、调入对应图
    片显示,并对显示坐标进行偏移修正,准
    备下一幅图片; 若本组动作图片完成,则
    准备新的一组。在任何时候鼠标左键的动
    作都会引起角色的移动和方位的转换。
    编译运行程序。现在你可以用鼠标指
    挥屏幕上的游戏角色东走西跑了。


                               图5-6 程序的主循环


                                                             图5-7
    详细内容请看本章实例程序:“走四方”。


    5-4 小结


    在这一章里我们学了以下知识和方法:
    1.建立鼠标左键按键消息。
    2.定义一个名为MAN 的数据结构来表示游戏的角色。
    3.角色移动功能函数。
    4.角色动作转换函数。
    5.角色图形规律分析。
    6.在时钟消息中加入角色移动和动作转换。
    7.加入了对象显示的校正值,使对象的显示不再跳动。

  • 相关阅读:
    超链接标签、链接地址、锚文本及图片标签
    有序无序列表,div盛放逻辑版块,table表格
    函数的默认值与动态参数arguments的总结
    浏览器中常见的html语义化标签
    html基本介绍,了解html与css,html语法和结构
    js函数与作用域,了解函数基本概念
    JavaScrip流程控制之switch选择,for循环
    JavaScript之if流程控制演练,if写在区间内怎么解决
    JavaScript数据类型typeof()和转换
    C++走向远洋——60(十四周阅读程序、STL中的简单容器和迭代器)
  • 原文地址:https://www.cnblogs.com/javawebsoa/p/3060085.html
Copyright © 2011-2022 走看看