[置顶] 游戏开发技术总结(经典之作)第五集 轻松走四方-----游戏角色的移动和动作的切换

 

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 (⋯⋯ )中了。

[置顶] 游戏开发技术总结(经典之作)第五集 轻松走四方-----游戏角色的移动和动作的切换_第1张图片


                                                        图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)。

[置顶] 游戏开发技术总结(经典之作)第五集 轻松走四方-----游戏角色的移动和动作的切换_第2张图片


                                          图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 幅图片。

[置顶] 游戏开发技术总结(经典之作)第五集 轻松走四方-----游戏角色的移动和动作的切换_第3张图片


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

[置顶] 游戏开发技术总结(经典之作)第五集 轻松走四方-----游戏角色的移动和动作的切换_第4张图片

[置顶] 游戏开发技术总结(经典之作)第五集 轻松走四方-----游戏角色的移动和动作的切换_第5张图片


                                                                图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 下图向左偏移一定
数,这样看到的效果就是人以脚为支点的转圈动作。这
样才符合实际的视觉效果。

[置顶] 游戏开发技术总结(经典之作)第五集 轻松走四方-----游戏角色的移动和动作的切换_第6张图片
        在目录“运行程序/图片/人/”下面的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 行,若本动作完成,进行方
位转换。
程序的执行过程是, 在一个时钟周期
里刷新屏幕、进行角色移动、调入对应图
片显示,并对显示坐标进行偏移修正,准
备下一幅图片; 若本组动作图片完成,则
准备新的一组。在任何时候鼠标左键的动
作都会引起角色的移动和方位的转换。
编译运行程序。现在你可以用鼠标指
挥屏幕上的游戏角色东走西跑了。

[置顶] 游戏开发技术总结(经典之作)第五集 轻松走四方-----游戏角色的移动和动作的切换_第7张图片


                           图5-6 程序的主循环
[置顶] 游戏开发技术总结(经典之作)第五集 轻松走四方-----游戏角色的移动和动作的切换_第8张图片


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


5-4 小结


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

 

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(游戏开发)