消灭星星游戏程序设计【连载十一】——在线程中解决音效卡顿问题
大家每次都可以在页面中下载本节内容的实现代码,一步一步从简单开始,逐步完成游戏的各种功能,如果大家有任何问题也欢迎留言交流。
游戏整体效果展示:
1、本节要达到的效果
前面的章节,我们添加了小星星爆炸时的音效,游戏效果得到了极大的提升。但是我们现在又发现一个新的问题,每次在播放爆炸音效时,小星星在移动时都会出现卡顿的情况。而且这种卡顿的情况影响比较严重,计算机在图片的显示上显卡处理的速度非常快不会有问题,问题就出在音效的播放上,所以这个声音播放的卡顿问题亟待解决。
2、用线程去解决音效卡顿
这里我们就到线程的概念,因为我们每次声音的播放只存在刚刚开始时0.0几秒的卡顿,从耳朵听是听不出来差别,但从视觉上就会感觉到图像动画不连续的问题。因此,我们在线程中处理声音,声音播放卡顿是在线程中卡顿,卡顿的0.0几秒它不会影响到游戏效果,最主要的主程序中图像效果不会受到声音播放卡顿的影响,可以完美解决问题。
3、线程的具体操作
要使用线程,我们需要做的就是在主程序中创建一个线程,这个线程我们需要通过创建函数告诉需要程序,在线程中运行的一个自定义循环操作函数,然后我们根据自己的需求,在这个循环函数中完成声音的操作。
//定义全局线程对象,用以管理、操作音效资源
HANDLE hMusicThread=NULL;
//定义全局线程同步对象,用以使用线程时确保音效资源数组有效操作
HANDLE hMusicMutex=NULL;
case WM_CREATE:
......
//在线程中处理音效的加载、播放、设置、删除
hMusicThread=CreateThread(0,0,(LPTHREAD_START_ROUTINE)GameMusicThread,NULL,0,0);
//创建线程同步对象,使用互斥对象保证同一时刻只允许一个线程操作音效资源数组
hMusicMutex=CreateMutex(NULL,FALSE,NULL);
......
同时需要自定义一个循环操作函数,并在这个循环体内处理我们需要音乐的操作。这里我们要注意一个问题,加入线程后,所有的声音操作,有声音的加载、播放、暂停、卸载操作必须要全部在线程中处理。不能出现在主程序中加载声音,在线程中播放声音的操作,这样的操作也是播放不出来声音的。因此我们对声音的实质性操作必须放在线程中。简单一点说,音乐的加载、播放、停止操作必须在这个线程函数中处理。这里使用了CreateMutex和WaitForSingleObject和ReleaseMutex函数,主要是为了解决不同线程中避免同时对某些变量进行操作而导致的操作冲突。
//在线程中处理音效的加载、播放、设置、删除
void GameMusicThread()
{
//进入处理音效的循环
while(true)
{
//锁定音效资源数组操作
WaitForSingleObject(hMusicMutex,INFINITE);
//在线程中自动处理音效的各种操作,包括加载、播放、停止等操作
......
//关闭锁定音效资源数组操作
ReleaseMutex(hMusicMutex);
//防止系统过载运行
Sleep(1);
}
}
4、线程中音效资源的操作
为了能够统一操作音效资源,我们采用一个指针数组来存储所有的Music类指针,方便统一操作。同时在Music类的构造函数中自动将指针保存到指针数组中,省去了后期逐个添加音效指针到数组中的重复操作。
//设置所有的音乐类的最大个数
#define MUSICSAVERMAXNUM 30
//保存所有的音乐类,便于统一管理
Music *ptMusicSaver[MUSICSAVERMAXNUM];
同时取消在原构造函数中加载音效资源的操作,后边将改到线程函数中加载资源。
Music::Music(char *szTempFilename,int iTempTrackMaxNum,int iTempVolume)
{
....
for(int i=0;i<MUSICSAVERMAXNUM;i++)
{
if(ptMusicSaver[i]==NULL)
{
ptMusicSaver[i]=this;
break;
}
}
//初始化音乐分量,并命名音乐分量
//LoadFromFile();
//设置音量大小
//SetVolume(iTempVolume);
}
接下来我们修改线程操作函数GameMusicThread(),这里我们使用一个全局变量tagAlreadyLoadMusicResource来标记资源是否已经被加载,如果音效加载状态为false则加载资源;如果状态为true则资源已被加载则不要重复加载,直接进行后续的音效处理即可。
//标记是否已经记载音效资源
bool tagAlreadyLoadMusicResource=false;
//在线程中处理音效的加载、播放、设置、删除
void GameMusicThread()
{
//进入处理音效的循环
while(true)
{
//锁定音效资源数组操作
WaitForSingleObject(hMusicMutex,INFINITE);
//在线程中自动处理音效的各种操作
if(tagAlreadyLoadMusicResource==false)
{
//标记已经加载音效资源状态
tagAlreadyLoadMusicResource=true;
//在线程中加载音效资源
for(int i=0;i<MUSICSAVERMAXNUM;i++)
{
if(ptMusicSaver[i]!=NULL)
{
if(strlen(ptMusicSaver[i]->szFilename)!=0)
{
ptMusicSaver[i]->LoadFromFile();
}
}
}
}
//关闭锁定音效资源数组操作
ReleaseMutex(hMusicMutex);
//防止系统过载运行
Sleep(1);
}
}
这样就可以将音效资源在线程中完成加载,游戏进入主界面也不会有卡顿的情况出现。
5、怎样在主线程中播放线程的音效呢?
我们刚刚说过,不能在主程序中直接播放线程中加载的音效资源,那如果主程序中想尝试播放或停止声音,又该怎么做呢?我们可以采用在主程序中定义声音的状态变量,当主程序需要播放或停止声音时,就改变这个状态变量的值;另一方面,在线程中不断检测状态信息的变化情况,当状态信息发生改变,就立即按照状态信息内容进行相应的播放或停止操作,并及时重置状态信息。我们可以对音乐类添加以下用于指令控制的状态变量字符串:
class Music
{
......
public:
char szThreadCommand[1024]; //用于在线程中操作的指令
......
public:
......
//在线程中播放音效,参数为是否循环播放
void PlayInThread(bool tagLoop=false);
//在线程中停止音效
void StopInThread();
......
};
void Music::PlayInThread(bool tagLoop)
{
//锁定音效资源数组操作
WaitForSingleObject(hMusicMutex,INFINITE);
//设置不同的命令,以便在线程中去进行相应的操作
if(tagLoop==true)
{
strcpy(szThreadCommand,"Play Music Loop");
}
else
{
strcpy(szThreadCommand,"Play Music");
}
//关闭锁定音效资源数组操作
ReleaseMutex(hMusicMutex);
}
void Music::StopInThread()
{
//锁定音效资源数组操作
WaitForSingleObject(hMusicMutex,INFINITE);
//设置不同的命令,以便在线程中去进行相应的操作
strcpy(szThreadCommand,"Stop Music");
//关闭锁定音效资源数组操作
ReleaseMutex(hMusicMutex);
}
这里我们使用PlayInThread和StopInThread方法去代替以前的Play和Stop方法,PlayInThread和StopInThread方法的实质就是只对szThreadCommand进行操作指令的赋值,不进行音效的实质性播放和停止操作;待线程中判断szThreadCommand当前的指令,在根据指令的不同进行Play和Stop方法的操作。这样音效的加载、播放、停止操作就同时放在了线程中,同时主程序中也可以方便的操作线程音效的播放,更重要的是,线程中音效的播放不会产生卡顿影响。
主线程操作如下:
//旧的播放方式会卡,将被线程播放函数取代
//mWelcome.Play();
//在线程播放声音文件
mWelcome.PlayInThread();
我们这里有以下命令需要处理,声音的播放,声音的循环播放,声音暂停,在我这个小游戏里,已经基本上够用了。如果后期还需要更复杂的操作,比如控制循环播放的次数,设置音量的大小,包括在3d游戏里面声音随玩家距离的改变而改变音效音量大小等,可以进行更复杂的操作,后期我还会在我的专题OpenGL实现3D游戏编程中详细描述,有兴趣可以了解一下。
6、线程中音效的播放、停止操作
接下来,我们就要在线程中处理音效的播放、停止等操作了。我们通过前期对szThreadCommand赋予不同字符串"Play Music",“Play Music Loop”,"Stop Music"来区分不同操作指令,当线程中读取到以上指令后,立即重置指令为空,并调用以前的Play和Stop方法在线程中播放音效。
//在线程中处理音效的加载、播放、设置、删除
void GameMusicThread()
{
//进入处理音效的循环
while(true)
{
//锁定音效资源数组操作
WaitForSingleObject(hMusicMutex,INFINITE);
//在线程中自动处理音效的各种操作
if(tagAlreadyLoadMusicResource==false)
{
//标记已经加载音效资源状态
tagAlreadyLoadMusicResource=true;
//在线程中加载音效资源
for(int i=0;i<MUSICSAVERMAXNUM;i++)
{
if(ptMusicSaver[i]!=NULL)
{
if(strlen(ptMusicSaver[i]->szFilename)!=0)
{
ptMusicSaver[i]->LoadFromFile();
}
}
}
}
else
{
//在线程中加载音效资源
for(int i=0;i<MUSICSAVERMAXNUM;i++)
{
if(ptMusicSaver[i]!=NULL)
{
if(strlen(ptMusicSaver[i]->szFilename)!=0)
{
//不同的操作:音乐播放一次
if(strcmp(ptMusicSaver[i]->szThreadCommand,"Play Music")==0)
{
strcpy(ptMusicSaver[i]->szThreadCommand,"");
ptMusicSaver[i]->Play();
}
//不同的操作:音乐的循环播放
if(strcmp(ptMusicSaver[i]->szThreadCommand,"Play Music Loop")==0)
{
strcpy(ptMusicSaver[i]->szThreadCommand,"");
ptMusicSaver[i]->Play(true);
}
//不同的操作:音乐的停止
if(strcmp(ptMusicSaver[i]->szThreadCommand,"Stop Music")==0)
{
strcpy(ptMusicSaver[i]->szThreadCommand,"");
ptMusicSaver[i]->Stop();
}
}
}
}
}
//关闭锁定音效资源数组操作
ReleaseMutex(hMusicMutex);
//防止系统过载运行
Sleep(1);
}
}
设计了以上的操作模式,声效的实际操作就变得简单了,你也可以直接解决掉音效卡顿的问题,经过实际测试游戏已经变得非常丝滑,再也没有卡顿的问题。