vc++游戏编程指南.docx
- 文档编号:30309189
- 上传时间:2023-08-13
- 格式:DOCX
- 页数:54
- 大小:131.04KB
vc++游戏编程指南.docx
《vc++游戏编程指南.docx》由会员分享,可在线阅读,更多相关《vc++游戏编程指南.docx(54页珍藏版)》请在冰豆网上搜索。
vc++游戏编程指南
第七章我没有想好名字
如果你只靠上面几章所讲述的知识编了个游戏,喜欢的人恐怕会不多,为什么?
因为没有人会玩一个控制不流畅而且声音效果不佳的游戏。
为了在游戏中更好地管理各种输入设备,我们需要使用DirectInput。
而通过使用DirectXAudio可以在游戏中实现各种更逼真的音乐效果。
它们都是DirectX的重要组成部分。
使用DirectInput前我们需要#include
7.1读取键盘数据
首先,我们必须创建一个DirectInput8对象(DirectX9.0并没有对DInput和DAudio做多大改动),就像这样:
LPDIRECTINPUT8pInput;
DirectInput8Create(GetModuleHandle(NULL),
DIRECTINPUT_VERSION,
IID_IDirectInput8,
(void**)&pInput,
NULL);
然后,我们需要创建一个DirectInput设备:
LPDIRECTINPUTDEVICE8pDev;
pInput->CreateDevice(GUID_SysKeyboard,&pDev,NULL);
设置好它的数据格式:
pDev->SetDataFormat(&c_dfDIKeyboard);
设置它的协作级,这里设为独占设备+前台:
pDev->SetCooperativeLevel(hwnd,DISCL_EXCLUSIVE|DISCL_FOREGROUND);
获取设备:
pDev->Acquire();
像上面那样初始化后,我们就已经把Windows对键盘的控制权剥夺了,以后的键盘消息将不会被送入消息循环,我们可以把消息循环中处理键盘消息的语句拿掉了。
当然,这时我们需要在程序的适当地方,比如说在刷新游戏时,加入对键盘数据进行读取和处理的语句,就像下面的一段程序:
#defineKEYDOWN(key)(buffer[key]&0x80)//定义一个宏,方便处理键盘数据
charbuffer[256];//键盘数据
pDev->GetDeviceState(sizeof(buffer),(LPVOID)&buffer);//得到键盘数据
if(KEYDOWN(DIK_XXX))//如果XXX键被按下…(请参阅附录二)
{
……//处理之
}
……//处理其它键
哈哈,真是挺方便的。
有时候真的有点怀疑DirectX是不是一种回到遥远的可爱的DOS时代的“倒退”。
因为无论是DirectInput还是DirectDraw都是太像DOS下的做法了。
7.2读取鼠标数据
读取鼠标数据和读取键盘数据的步骤差不多,首先也是要创建设备:
pInput->CreateDevice(GUID_SysMouse,&pDev,NULL);
设置数据格式:
pDev->SetDataFormat(&c_dfDIMouse);
设置协作级:
pDev->SetCooperativeLevel(hwnd,DISCL_EXCLUSIVE|DISCL_FOREGROUND);
获取设备:
pDev->Acquire();
那么怎样读取鼠标数据呢?
如果要取得鼠标的当前状态,这样即可:
DIMOUSESTATEmouse_stat;//鼠标状态
//得到鼠标状态
pDev->GetDeviceState(sizeof(DIMOUSESTATE),(LPVOID)&mouse_stat);
得到的mouse_stat是一个DIMOUSESTATE类型的结构,它有四个成员:
lX,lY,lZ和rgbButtons[4]。
其中lX、lY和lZ分别是自上次调用此函数以来鼠标在X轴、Y轴和Z轴(滚轮)方向上移动的距离,而不是鼠标此时的坐标;其距离单位不是像素,但你完全可以把它看做以像素为单位。
所以,我们需要定义两个变量mousex=0和mousey=0,然后把lX和lY累加上去即可。
这样做的好处是鼠标坐标不再受屏幕的制约,而且屏幕中心的mousex和mousey值可以永远是0,不随屏幕分辨率而改变。
rgbButtons是一个存储哪些鼠标键被按下的数组,我们可以这样做来读取它:
//定义一个宏,方便处理鼠标数据
#defineMOUSEBUTTONDOWN(b)(mouse_stat.rgbButtons[b]&0x80)
if(MOUSEBUTTONDOWN(0))//如果左键被按下…
{
……//处理之
}
……//处理右键
(1)和中键
(2)
7.3恢复和关闭DirectInput
7.3.1恢复DirectInput设备
就像在DirectDraw中那样,使用DirectInput的程序被最小化时DirectInput设备会出现"丢失"现象。
恢复的办法很干脆:
先关闭DirectInput再重新初始化即可。
7.3.2关闭DirectInput
关闭DirectInput也是非常简单的(SAFE_RELEASE的定义在4.8节):
pDev->Unacquire();
SAFE_RELEASE(pDev);
SAFE_RELEASE(pInput);
7.4初始化和关闭DirectXAudio
7.4.1初始化DirectXAudio
使用DirectXAudio前,按规矩还是要先初始化。
在下面的这段初始化程序中要用到三个DXAudio提供的对象:
IDirectMusicLoader8、IDirectMusicPerformance8和IDirectMusicSegment8。
IDirectMusicLoader8顾名思义是用来调入音乐的,IDirectMusicPerformance8可以认为是音频设备,而IDirectMusicSegment8就是代表音乐。
#include
IDirectMusicLoader8*pLoader=NULL;
IDirectMusicPerformance8*pPerf=NULL;
IDirectMusicSegment8*pSeg=NULL;
CoInitialize(NULL);//初始化COM
CoCreateInstance(CLSID_DirectMusicLoader,NULL,
CLSCTX_INPROC,IID_IDirectMusicLoader8,
(void**)&pLoader);//创建pLoader对象
CoCreateInstance(CLSID_DirectMusicPerformance,NULL,
CLSCTX_INPROC,IID_IDirectMusicPerformance8,
(void**)&pPerf);//创建pPerf对象
pPerf->InitAudio(
NULL,//这里可以是一个指向IDirectMusic*对象的指针
NULL,//这里可以是一个指向IDirectSound*对象的指针
hwnd,//窗口句柄
DMUS_APATH_SHARED_STEREOPLUSREVERB,//AudioPath类型
//这里打开了立体声及混响,效果很不错
64,//音乐通道数
DMUS_AUDIOF_ALL,//使用声卡的所有特性
NULL//可以指向一个DMUS_AUDIOPARAMS对象,更详细地说明各种参数
);
7.4.2关闭DirectXAudio
关闭DXAudio还是老思路,先按7.5.3节的办法停止音乐,然后Release即可:
pPerf->CloseDown();//关闭
SAFE_RELEASE(pLoader);//释放对象
SAFE_RELEASE(pPerf);
SAFE_RELEASE(pSeg);
CoUninitialize();//停止使用COM
7.5播放MIDI和WAV音乐
MIDI音乐和WAV音乐在游戏编程中经常用到。
其中前者一般是用作背景音乐,而后者多是用在各种音效方面,如发射导弹等等。
虽然我们可以用3.4.4节的方法,但使用DXAudio可以更充分地利用硬件资源,从而实现更少的CPU占用率。
方法如下:
7.5.1调入MIDI和WAV文件
在播放音乐之前,第一步当然是调入音乐文件:
CHARstrSoundPath[MAX_PATH];//存储音乐所在路径
GetCurrentDirectory(MAX_PATH,strSoundPath);//得到程序所在路径
strcat(strSoundPath,"\\Sounds");//这里设置音乐在程序路径下的Sounds子目录
WCHARwstrSoundPath[MAX_PATH];//存储UNICODE形式的路径
//将路径转为UNICODE形式
MultiByteToWideChar(CP_ACP,0,strSoundPath,-1,wstrSoundPath,MAX_PATH);
pLoader->SetSearchDirectory(
GUID_DirectMusicAllTypes,//搜索所有支持的格式
wstrSoundPath,
FALSE
);
WCHARwstrFileName[MAX_PATH];//存储UNICODE形式的文件名
//将文件名转为UNICODE形式
MultiByteToWideChar(CP_ACP,0,"a.mid",-1,wstrFileName,MAX_PATH);
pLoader->LoadObjectFromFile(
CLSID_DirectMusicSegment,//文件类型
IID_IDirectMusicSegment8,//目标对象类型
wstrFileName,//文件名,同样应为UNICODE形式
(LPVOID*)&pSeg//目标对象
);
7.5.2播放MIDI和WAV文件
调入完音乐之后,在适当的时候可以开始播放音乐:
pSeg->SetRepeats(音乐要重复的次数);//DMUS_SEG_REPEAT_INFINITE为无限次
pSeg->Download(pPerf);//将音乐数据传给pPerf
pPerf->PlaySegmentEx(
pSeg,//要播放的音乐
NULL,//现在只能是NULL
NULL,
0,
0,
NULL,
NULL,
NULL//Audiopath,现在先不要管它是什么
);
7.5.3停止播放
停止播放音乐也是非常简单的:
pPerf->Stop(
NULL,//停止哪个通道,NULL代表所有通道
NULL,
0,//经过多少时间才停止播放
0
);
7.6在3D空间中播放音乐
我们的下一个问题是如何使音乐更逼真,最好能使音乐3D化,这将大大加强真实性。
要实现这个其实也不难,首先我们要设定所谓的AudioPath,它可以指定音乐的播放方式。
IDirectMusicAudioPath8*pPath;//AudioPath
pPerf->CreateStandardAudioPath(//创建AudioPath
DMUS_APATH_DYNAMIC_3D,//3D的
64,//通道数
TRUE,//是否立即激活AudioPath
&pPath//要创建的AudioPath
);
然后,我们要从AudioPath中得到一个"3D缓存",它将存储音乐的播放位置等信息。
IDirectSound3DBuffer8*pBuffer;//3D缓存
pPath->GetObjectInPath(
DMUS_PCHANNEL_ALL,//搜索全部通道
DMUS_PATH_BUFFER,//为DirectSound缓存
0,
GUID_NULL,
0,
IID_IDirectSound3DBuffer8,//缓存类型
(LPVOID*)&pBuffer//要创建的缓存
);
下一步是设定音乐在3D空间的何处播放。
例如:
pBuffer->SetPosition(-0.1f,0.0f,0.0f,DS3D_IMMEDIATE);
这条指令把音乐的播放位置设为(-0.1f,0.0f,0.0f),由于默认的听众位置在坐标(0,0,0)处,脸朝着Z轴的正方向,头朝着Y轴正方向,所以其效果将是听众的右耳听得到声音但左耳听不到声音,就像音乐是从自己的正右方发出。
如果把最后一个参数设为DS3D_DEFERRED,此操作将被挂起直到调用IDirectSound3DListener8:
:
CommitDeferredSettings()为止,这样可以一次处理多个设定防止反复计算。
我们还可以设置音乐源的速度及其播放角度:
pBuffer->SetVelocity(vx,vy,vz,DS3D_IMMEDIATE);//设置在x,y,z轴方向的速度
//设置播放角度大小(度),inncone为内角度,outcone为外角度
//音乐在内角度范围内不衰减,在内外角度之间慢慢衰减,超出外角度时消失
pBuffer->SetConeAngles(inncone,outcone,DS3D_IMMEDIATE);
pBuffer->SetConeOrientation(x,y,z,DS3D_IMMEDIATE);//设置朝哪个方向播放
那么我们如何设定听众的位置呢?
可以这样:
IDirectSound3DListener8*pListener;
pPath->GetObjectInPath(//创建听众
0,
DMUS_PATH_PRIMARY_BUFFER,
0,
GUID_NULL,
0,
IID_IDirectSound3DListener8,
(LPVOID*)&pListener
);
//设置听众面向(x1,y1,z1),头朝着(x2,y2,z2)
pListener->SetOrientation(x1,y1,z1,x2,y2,z2,DS3D_IMMEDIATE);
pListener->SetPosition(x,y,z,DS3D_IMMEDIATE);//听众位置
pListener->SetVelocity(vx,vy,vz,DS3D_IMMEDIATE);//听众速度
7.7播放MP3音乐
MIDI音乐的问题是对声卡的依赖性过大,好声卡和差声卡的播放效果实在相差太远。
WAV音乐虽然绝对足够精确,但占用的空间之大不可小视。
MP3恐怕是一个较好的解决方案。
值得注意的是,播放MP3并不需要DirectXAudio,需要的是DirectShow。
所以,我们要#include
7.7.1调入MP3文件
下面把初始化DirectShow和调入MP3合起来说说吧。
首先,我们要定义三个对象,其中IGraphBuilder*类型的可以认为是媒体播放设备,IMediaControl*类型的变量负责媒体的播放控制,而IMediaPosition*类型的变量负责媒体的播放位置设定。
IGraphBuilder*pGBuilder;
IMediaControl*pMControl;
IMediaPosition*pMPos;
CoInitialize(NULL);//初始化COM
//创建各个对象
CoCreateInstance(CLSID_FilterGraph,NULL,
CLSCTX_INPROC,IID_IGraphBuilder,(void**)&pGBuilder);
pGBuilder->QueryInterface(IID_IMediaControl,(void**)&pMControl);
pGBuilder->QueryInterface(IID_IMediaPosition,(void**)&pMPos);
CHARstrSoundPath[MAX_PATH];//存储音乐所在路径
WCHARwstrSoundPath[MAX_PATH];//存储UNICODE形式的路径
GetCurrentDirectory(MAX_PATH,strSoundPath);
strcat(strSoundPath,"\\Sounds\\");
strcat(strSoundPath,"a.mp3");//假设要播放的是Sounds子目录下的a.mp3
MultiByteToWideChar(CP_ACP,0,strSoundPath,-1,wstrSoundPath,MAX_PATH);
pGBuilder->RenderFile(wstrSoundPath,NULL);//调入文件
7.7.2播放MP3文件
播放MP3的方法十分简单:
pMPos->put_CurrentPosition(0);//移动到文件头
pMControl->Run();//播放
7.7.3停止播放和释放对象
最后,我们要停止播放音乐并释放各个对象:
pMControl->Stop();//停止播放
//释放对象
SAFE_RELEASE(pMControl);
SAFE_RELEASE(pMPos);
SAFE_RELEASE(pGBuilder);
CoUninitialize();//释放COM
第八章支撑游戏的基石
在这一章中,我们将看看数据结构,算法和人工智能在游戏中的应用。
我想每个人都知道“人工智能”这个字眼吧,但数据结构和算法是干什么的呢?
说简单点,数据结构就是在程序中各种数据的组织形式,而算法就是处理这些数据的方法。
NiklausWirth曾说过“数据结构+算法=程序”,可见其重要性。
8.1链表
链表是一种灵活的数据结构,它可以说是把指针用到了极致。
最简单的链表是由一个个像这样的节点组成的:
structnode//节点
{
intdata;//节点数据
node*next;//指向下一个节点的指针
};
一个个链表的节点就像一节节火车车厢一样通过next指针一个接一个地连接着,当我们在链表中查找数据时,我们也要一个接一个地往下找。
可以想象,在链表的任何位置添加新节点都是十分简单的,而删除链表中的某个节点时也只要把它的父节点指向它的子节点即可。
正因为链表有这些特点,它被广泛地应用于各种元素的个数或是元素的排列顺序经常需要改变的场合。
我们还可以使用双向链表,即再使用一个指向上一个节点的指针。
这将使链表变得更加方便——可以从后往前查找节点,但同时也增大了链表的大小。
链表在游戏编程中有不少应用,例如组织游戏中像精灵(Sprite,指游戏中会移动的东西)这样的经常需要修改的元素。
8.2哈希表
使用哈希表(HashTable)可以大大减少查找工作的时间。
举一个简单的例子,如果你要在一本字典中找某单词,那你应该怎样做呢?
如果不使用哈希表,那么你似乎只能一个个找下去。
当然,我们知道字典是排好序的,所以大可使用二分查找等更快的方法。
但如果是职工编号等完全无序的数据呢?
这时,我们需要一张哈希表。
怎么建立哈希表呢?
所谓哈希表,其实是一个很简单的东西,它可以说是为数据建立了一个索引。
还是上面那个例子,我们首先应该通过某一个函数的变换,把字典里的所有单词变成一些尽量不相同的数。
如果能做到完全不相同的话,这个函数就是一个完美的哈希函数。
当然,这显然比较难。
一个比较糟糕的哈希函数——我们给它起个名字叫f(x)——就是按单词的头一个字母,把单词转换成0到25之间的数,就像我们平常查字典时那样。
好一点的解决方案是把单词的所有字母都这样转换一下,然后再加起来,对某一个大数取模。
下一步,我们建立一个大数组HashTable,它的大小要能容纳所有哈希函数的可能取值,对于f(x),我们要开一个HashTable[26]。
然后我们把这个数组的每一个元素都变成一个链表,把对应的单词一个接一个地放进去(其实把单词转换成数后就应该立刻把它放进数组)。
此时HashTable[0]的内容就像这样:
"a""Aachen""Aalborg""aardvark"…
现在大家看出来了吧,是的,我们只要把我们要找的单词通过f(x)转换成一个数,然后再在HashTable[f(x)]中查找即可。
哈希函数取得越好,相同哈希函数的单词就越少,我们的查找就越快。
然而不容忽视的是,数组很可能也变大了。
所以哈希表是用空间换时间。
关于哈希函数的选取,和如果有两个元素哈希函数值相同时的处理,现在都研究得比较多。
比如说有一种办法是:
在HashTable[f(x)]已被其它元素占据时,看看HashTable[g(f(x))]是否是空的,如果还不行就看HashTable[g(g(f(x)))],直到行为止。
g(x)可以是x+1之类。
显然,如果使用这种办法,HashTable[]的大小要比元素的个数多。
这种办法避免了链表的使用,但增加了计算g(x)的花费。
总之,一切要根据实际情况选择。
最后给一个比较好用的Hash函数给大家吧,它能将一个单词转为一个整数:
intHashf(constchar*s)
{
inthash,i,l;
hash=0;
l=strlen(s);
for(i=0;i hash=(hv*26+s[i]-'a')%size;//size为hash表大小 returnhv; } 8.3快速排序 最常用的算法之一是排序算法。 由于对排序算法的速度要求较高,我们通常使用快速排序。 其算法如下: voidQuickSort(intbegin,intend)//对数组a排序,start为开始排序的位置,end为排 {//序结束位置,例如a[10]则start=0,end=9。 intp=begin; intq=end; intmid=a[p];//标准数 inttemp; while(p { while(a[p] while(a[q]>mid)q--;//数组右边的数大于等于标准数 if(p<=q) { temp=a[p]; a[p]=a[q]; a[q]=temp;//交换a[p]、a[q]。 p++;q--; } } if(q>begin)QuickSort(begin,q);//继续对前半部分排序 if(p } 其实快速排序的思路是不难理解的,首先在头尾设置两个指针然后向中间移动,发现
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- vc 游戏 编程 指南