游戏开发技术总结(经典之作)第九集 狩猎谋生-----声音播放和游戏角色的碰撞检测

 源代码下载地址---------->第九集源代码

9-1 任务


        在本章我们将加入人与动物相遇时的打斗效果,打斗时还应有打斗声。另外我们
在这一章还要介绍一种半透明的显示技术。


9-2 碰撞检测


       游戏中的打斗是基于游戏的角色相遇时的下一步处理。我们将游戏角色的相遇判
断称为碰撞检测。现在我们来看怎么实现这种碰撞检测。这里我们将碰撞检测做成一
个功能函数,安排在OnTimer(⋯ )时钟函数中调用。

void CMyDlg::OnTimer(UINT nIDEvent)//时钟函数,[类向导中定义生成]
{if(nIDEvent==1) //动画刷屏
{tim=timeGetTime(); //开始时间
CClientDC dc(this); //取客户区设备环境
m_game.mlmap(); //地图块移动拼接
int i;
for(i=0;i<m_game.rs;i++)
{m_game.setobj(i); //对象显示
m_game.lookit(i); //角色碰撞
}
BitBlt(dc.m_hDC,2,10,WIDTH,HEIGHT,m_game.BkDC1,0,0,SRCCOPY);
if(m_game.rs>1) m_game.smlmap(dc.m_hDC); //显示小地图
tim=timeGetTime()-tim; //显示时间=结束时间-开始时间
}
⋯⋯
}



每一个角色在显示时,都要进行碰撞检测,由此来控制游戏中各个角色的相互行为。


9-2-1 碰撞检测的原理


      根据人与动物的相对位置,检测人与动物是否相碰;检测到碰撞时, 调整双方的
方位并将动作转为打斗,同时播放打斗的WAV 声音。
碰撞检测功能函数(行号是为了注释而加的):

//**************************************************
// lookit(int i)角色碰撞
// 只判断人是否与动物相遇;若相遇,动物停下来。
// 由位置差使双方面对面(位置调整),开始打斗(动作变换)。
// 播放打斗的WAV 声音。
//**************************************************
void gamepro::lookit(int i)//角色碰撞
1{if(man[i].lb!=0) return; //不是人,返回
2 for(int q=0;q<rs;q++)
3 {if(q==i) continue; //是自己
4 if(man[q].lb==2) continue; //是景
5 int x=man[i].xix-man[q].xix; //取q,i 对象的位置差
6 int y=man[i].xiy-man[q].xiy; //
7 if(abs(x)<man[q].w && abs(y)<man[q].h) //相遇
8 { if(man[i].lb!=man[q].lb) //不同类
9 { man[q].x0=man[q].fx=man[q].xix; //目标=当前
10 man[q].y0=man[q].fy=man[q].xiy;//
11 man[q].fid=man[q].pk; // 动物停下来
12 //双方面对面
13 if(x==0&&y<0) {man[i].fw=0;man[q].fw=4;} //北
14 if(x>0&&y< 0) {man[i].fw=1;man[q].fw=5;} //东北
15 if(x>0&&y==0) {man[i].fw=2;man[q].fw=6;} //东
16 if(x>0&&y> 0) {man[i].fw=3;man[q].fw=7;} //东南
17 if(x==0&&y>0) {man[i].fw=4;man[q].fw=0;} //南
18 if(x<0&&y> 0) {man[i].fw=5;man[q].fw=1;} //西南
19 if(x<0&&y==0) {man[i].fw=6;man[q].fw=2;} //西
20 if(x<0&&y< 0) {man[i].fw=7;man[q].fw=3;} //西北
21 man[q].zs=dw[man[q].js][3];man[q].zd=1; //开打
22 man[i].zs=3; man[i].zd=1; //开打
23 if(man[i].p==man[i].m1-2)
24 sndPlaySound("砍1.wav",SND_ASYNC); //声音
25 if(man[q].p==man[q].m1-8)
26 sndPlaySound("羊.wav",SND_ASYNC); //声音
27 break;
28 } //end 不同类
29 } //end 相遇
30 } //end for
31}


 


9-2-2 碰撞检测程序注释


第 1 行不是人,返回。
第 2 行对所有对象循环。i 为人,q 为动物。
第 3、4 行是自己、是景跳过。
第 5、6 行取 q,i 对象的位置差。
第 7 行当位置差的绝对值小于动物图片大小时,说明人与动物相碰。
第 8 行不同类相遇。
第 9~ 11 行动物停下来。
第 13~ 20 行根据相对位置差调整双方方位,使双方面对面。
第 21、22 行双方动作设为斗,即开打。
第 23~ 26 行是播放模拟打斗的 WAV 声音。
第 27 行本次检测结束。


9-3 半透明显示的技术


       现在游戏中景物都做得比较大 ,当游戏的角色在大景物后面时就看不见了。早期
的游戏采用了显示景物背后角色轮廓的方法。现在的游戏则采用了半透明显示的技术,
得到了比较好的效果。

                                 图 9-1


9-3-1 半透明函数AlphaBlend()


AlphaBlend()是Windows 的API 函数中已有的半透明处理函数。

AlphaBlend( hDC0, 目标设备场景
                    x0,y0, 目标左上角坐标
                    w0,h0, 目标宽、高
                    hDC1, 源设备场景
                    x1,y1, 源左上角坐标
                    w1,h1, 源宽、高
                   rBlend 半透明参数
                  );



其中半透明参数rBlend 是一个BLENDFUNCTION 类型的结构。
RBlend 设置如下:
BLENDFUNCTION rBlend;
rBlend.SourceConstantAlpha=1 28;
//透明度 0-源全透明,源仿佛不存在;255-源不透明,目标仿佛不存在。
rBlend.BlendOp = AC_SRC_OVER; //
rBlend.BlendFlags = 0; //
rBlend.AlphaFormat= 0; //


9-3-2 半透明的实例说明


     半透明显示技术也叫通道合成技术,是计算机图形处理的重要技术。图9 -2 是对
“1.BMP” 、“ 2.BMP” 两幅图片进行通道合成的例子;其中rBlend.SourceConstantAlpha
的值分别为0、128、255。
游戏开发技术总结(经典之作)第九集 狩猎谋生-----声音播放和游戏角色的碰撞检测_第1张图片
                                                  图9-2
如果将“2.BMP” 换成一幅深蓝色的图片,我们再来看是什么效果?

游戏开发技术总结(经典之作)第九集 狩猎谋生-----声音播放和游戏角色的碰撞检测_第2张图片
                                                   图9-3
哈!天黑了,游戏中的天气效果原来就是这样做出来的呀。有了这个技术,看来
制作打雷、闪电应该没问题了吧?


9-3-3 Alphasetobj(⋯)对象显示


        好,现在来看我们这个游戏中隐藏在树、石后面的对象怎么呈半透明方式显示的。
我们在 game.cpp 中加入一个新的函数--Alphasetobj(int q,int a) 对象显示,这个函
数就是原来的 setobj(int q) 对象显示函数改写而得到的(改写点见加黑部份)。

//**************************************************
// Alphasetobj(int q,int a) 对象显示
// 调入由man[q].lb 指出的,不同的man[q].p 图片,和偏移值。
// 将对象q 在各自的当前位置x,y 上以透明方式TransparentBlt2 显示。
// 加入了对象在景物背后的半透明显示。
// 之后进行移动或方位转换的功能。
// 为防止闪烁,所有对象先显示在暂存区BkDC1,一屏完成后再翻转到主屏上。
//**************************************************
void game::Alphasetobj(int q,int a)//对象显示
{
1 if(a==1&&man[q].lb==2) return;
CString cc;
int x=man[q].xix-scrx-man[q].w/2; //x 当前位置
int y=man[q].xiy-scry-man[q].h; //y 当前位置
if(inscreer(q))//在显示区?
{if(man[q].lb==0) {cc="人";}
if(man[q].lb==1) {cc="兽";}
if(man[q].lb==2) {cc="景";}
if(getpic(cc,man[q].p)==FALSE) return;
if(man[q].lb!=2) //调角色的偏移位置
{int x0=0,y0=0;
if(man[q].lb==0) {x0=rbufx[man[q].p];y0=rbufy[man[q].p];}
if(man[q].lb==1) {x0=sbufx[man[q].p];y0=sbufy[man[q].p];}
if(man[q].fw>4) x0=w-x0; //是东北、东、东南方位
x=man[q].xix-scrx-x0;
y=man[q].xiy-scry-y0;
}
2 if(a==0)
3 {TransparentBlt2(BkDC1,x,y,w,h,MemDC,0,0,w,h,RGB(255,255,255));
4 return;
}
5 else
6 {BitBlt(BK,0,0,w,h,BkDC1,x,y,SRCCOPY);
7 TransparentBlt2(BkDC1,x,y,w,h,MemDC,0,0,w,h,RGB(255,255,255));
8 AlphaBlend(BkDC1,x,y,w,h,BK,0,0,w,h,rBlend); //半透明处理
}
/////////////////////////////////////////////////////////////
mans++;
}
if(man[q].lb==2) return;
else manmove(q); //活动对象的移动
man[q].p++; //下一动作
if(man[q].p>=man[q].m1) {bianfw(q);} //本动作完成,进行方位转换
}


 


9-3-4 Alphasetobj(⋯)注释


Alphasetobj(int q,int a) 改写部份注释:
第 2 行当入口参数 a=0 时;在第3 行透明显示当前对象后(图9-4A),在第4 行直
接返回。
如果,入口参数a!=0 时;从第1 行看到此时只显示人和动物;在第6 行将上次显
示的对象块拷贝到BK(图9-4A), 在第7 行透明显示当前人和动物后,这时的人和动
物是显示在树、石的上面(图9-4B),在第8 行将BK 中的图形半透明拷贝到BkDC1(图
9-4C)。

游戏开发技术总结(经典之作)第九集 狩猎谋生-----声音播放和游戏角色的碰撞检测_第3张图片
                                 图9-4
        从Alphasetobj(int q,int a) 的注释看, 加入了半透明显示的对象显示函数,需要在
OnTimer(⋯) 中两次调用,才能完成其功能。

void CMyDlg::OnTimer(UINT nIDEvent) //时钟函数,[类向导中定义生成]
{ if(nIDEvent==1)//动画刷屏
{tim=timeGetTime(); //开始时间
CClientDC dc(this); //取客户区设备环境
m_game.mlmap(); //地图块移动拼接
int i;
for(i=0;i<m_game.rs;i++)
m_game.Alphasetobj(i,0); //对象半透明显示
for(i=0;i<m_game.rs;i++)
{m_game.Alphasetobj(i,1); //对象半透明显示
m_game.lookit(i); //角色碰撞
}
BitBlt(dc.m_hDC,2,10,WIDTH,HEIGHT,m_game.BkDC1,0,0,SRCCOPY);
if(m_game.rs>1) m_game.smlmap(dc.m_hDC);//显示小地图
tim=timeGetTime()-tim; //显示时间=结束时间-开始时间
}
⋯⋯
}


 


9-4 MIDI 背景音乐播放方法


        用以下方法我们可以打开一个 mid 文件作为游戏的背景音乐。我们的mid 文件
“Music1.mid”放在getdir 当前图片目录下的。

1 name.Format(“%s/Music1.mid”,getdir);
2 hMCI = MCIWndCreate( NULL, NULL,WS_POPUP
3 |MCIWNDF_NOPLAYBAR
4 |MCIWNDF_NOMENU
5 ,name);
6 MCIWndPlay(hMCI);



第1 行得到 mid 文件的全文件名。
第 2~ 5 行创建一个 mid 播放句柄;第3 行调用的mid 播放器不要界面框。第4 行
调用的mid 播放器不要目录菜单(你可以把这两行注释掉看看效果)。第5 行打开的mid
文件名。
第 6 行开始播放音乐。
在 OnTimer(⋯) 的信息显示中断中我们加入以下程序段,作用是当mid 音乐播放完
后又重复播放。

If(MCIWndGetPosition(hMCI) >=MCIWndGetLength(hMCI))
MCIWndPlay(hMCI);
MCIWndGetLength(hMCI) 为当前 mid 文件的长度位置。
MCIWndGetPosition(hMCI) 为 mid 文件播放位置。



注意,mid 播放句柄hMCI 要在“狩猎谋生Dlg.h” 中定义:HWND hMCI。


9-5 又一种选择调入地图的方法


       在上一章我们采用通用对话框类来选择获取地图文件。这里我们将介绍一种使用
模态对话框来选择获取地图文件的方法。使用这种方法的好处是这样的文件选择框可
以根据游戏的要求美化。


9-5-1 建立对话框资源


      在 VC 编程环境中选择Insert 下的Resource, 进入Insert Resource 选择Dialog, 按New
键,新建一个对话框。

                                              图9-5
       在新建的对话框中添加“下拉列表框”。鼠标选中“下拉列表框” 按右键进入控
件的属性设置;将“下拉列表框” 的类型设为 Simple, 并调整好大小位置。

游戏开发技术总结(经典之作)第九集 狩猎谋生-----声音播放和游戏角色的碰撞检测_第4张图片
                         图9-6
再设置好对话框的确定按钮、取消按钮的大小和名称。
游戏开发技术总结(经典之作)第九集 狩猎谋生-----声音播放和游戏角色的碰撞检测_第5张图片
                                   图9-7


9-5-2 添加资源类文件


     在 View 菜单下选择ClassWizard(类向导),这时进入到Adding a Class(增加一个类),直接按确定按钮,将新建一个类文件。
游戏开发技术总结(经典之作)第九集 狩猎谋生-----声音播放和游戏角色的碰撞检测_第6张图片
                                          图9-8
在这里输入类文件名,我们将新建的类命名为setmap。

游戏开发技术总结(经典之作)第九集 狩猎谋生-----声音播放和游戏角色的碰撞检测_第7张图片
                                       图9-9
这样我们就新建好了一个带对话框资源的类──setmap 类。
游戏开发技术总结(经典之作)第九集 狩猎谋生-----声音播放和游戏角色的碰撞检测_第8张图片
                                                图9-10
      现在,在MFC ClassWizard(类向导)中, 我们可以在Messages 栏选择某一个消息
类型,双击左键建立对应的消息函数。


9-5-3 模态对话框的调用方法


        在“狩猎谋生Dlg.cpp”的首部加入#include "setmap.h",我们就可以用以下方法调
用显示这个对话框了。
setmap Dlg;Dlg.DoModal();//显示模态对话框
用这样的方法调用显示对话框的方式叫模态对话框。它的特点是调用方法简单,
但模态对话框在显示期间是独占的,即模态对话框没有退出时, 程序的其它部分都不
会有反映。
模态对话框常用来设置程序参数,调用什么东西。


9-5-4 通过模态对话框调入地图


我们这里建立的模态对话框是为了调入地图资源文件的;参照上一章,我们应该
将它写在OnOK()中。

void CMyDlg::OnOK()
{ ⋯⋯
setmap Dlg;Dlg.DoModal(); //显示模态对话框
if(getdir!="") //地图目录不为空
{CString name;
name.Format("%s/%s",getdir,getfile);
fidtim=0; //寻路时间清0
down=0;dowx=0;dowy=0; //左键按键清0
hMCI=NULL;
m_game.loadmap(name); //调入地图
m_game.loadza (name); //调入障碍表
m_game.getsmap(); //生成小地图
⋯⋯
}
SetTimer(1,TIMER,NULL);
SetTimer(2,1000,NULL);
}



现在我们通过调用模态对话框,在setmap 类中取得我们选择的文件名。所以在
setmap 类中还得告诉计算机怎么获取文件名,也就是还需要编写setmap.cpp 中的相关程
序。
首先我们在“狩猎谋生Dlg.cpp”的首部定义两个字符串型全局变量。
CString getdir; // 选择的目录
CString getfile; // 选择的文件
在 “setmap.cpp” 的首部也定义这两个字符串型全局变量, 不同的是前面加上
“extern” ,说明它是引用的外部变量;
extern CString getdir; // 选择的目录
extern CString getfile;// 选择的文件
这样“狩猎谋生Dlg.cpp” 和“setmap.cpp”中的这两个变量就是相通的了。


9-5-5 setmap 类的程序


我们还需在类向导的“Member Variables”中设置列表框控件的调用名为m_list。


                                               图9-11
现在我们在“setmap.cpp”中加入程序。
先在“setmap.cpp”中分别建立OnCancel()、OnOK()、OnInitDialog()消息。并在其中加入
相应的程序。

void setmap::OnCancel() //取消键
{ getdir=""; //目录清0
CDialog::OnCancel();
}
void setmap::OnOK() //确定键
{ GetDlgItemText(IDC_COMBO1, getfile); //从下拉框的的选择栏取文件名
CDialog::OnOK();
}
BOOL setmap::OnInitDialog() // setmap 的进入点
{ CDialog::OnInitDialog();
char appdir[256];//
GetCurrentDirectory(256,appdir); // 获取当前目录
getdir.Format("%s/地图",appdir); // 设置地图的目录
findfile0(getdir,"*.dat"); // 遍历文件,装入所有地图名
return TRUE;
}
///////////////////////////////////////////////////////////
// findfile0(CString DirName,CString ext)
//功能:编历文件, 将符合条件的文件名加入列表框
//入口:DirName 目录名,ext 文件名
//出口:p 符合条件的文件个数
///////////////////////////////////////////////////////////
int setmap::findfile0(CString DirName,CString ext)// 遍历文件
{ WIN32_FIND_DATA FindFileData;
HANDLE hFindFile;
SetCurrentDirectory(DirName); //设置当前目录
m_list.ResetContent(); //清除列表框
hFindFile=FindFirstFile(ext,&FindFileData);
CString tFile;
int p=0;
if (hFindFile!=INVALID_HANDLE_VALUE)
{do { tFile=FindFileData.cFileName;
if ((tFile==".")||(tFile=="..")) continue;
if (!(FindFileData.dwFileAttributes==FILE_ATTRIBUTE_DIRECTORY))
{ m_list.AddString(tFile); //将符合条件的文件名加入列表框
p++;
}
}
while (FindNextFile(hFindFile,&FindFileData));
}
FindClose(hFindFile);
return p;
}



setmap 类的执行过程比较简单。当在“ 狩猎谋生
Dlg.cpp”的OnOK()中被调用时,也就是这个模态对话
框被打开时;首先执行OnInitDialog()得到地图的目录,
调用遍历文件函数将这个目录下的所有“*.dat”的地
图文件名装入列表框中(见图9-12)。

游戏开发技术总结(经典之作)第九集 狩猎谋生-----声音播放和游戏角色的碰撞检测_第9张图片
我们选择的地图名进入了被选栏,按确定键后;
在OnOK()消息中,程序从下拉框的选择栏取文件名后
退出。但getdir 选择的目录和getfile 选择的文件是全局变量,于是就在“狩猎谋生Dlg.cpp”
的OnOK()获取完成调入地图的功能。
详细内容请看本章实例程序:“狩猎谋生”。


9-6 小结


在这一章里,我们学了以下知识和方法:
1. 加入对象的碰撞检测,打斗方法,碰撞时动作转换、。
2. 加入对象在障碍物后的半透明显示的技术。
3. WAV 声音、MIDI 音乐播放方法。
4. 又一种调地图的方法──模态对话框

你可能感兴趣的:(游戏,bitmap,游戏开发,游戏引擎,游戏编程)