首先,我只是把自己的经验写出来,有不足之处还望包涵,此外,我并不打算写教程,所以建议想学习游戏引擎开发的人还是找本书从浅入深学习。
很久之前我写过两篇关于多线程使用mciSendString方法的文章,为了便于开发,后面又将其扩展之后写成了动态链接库,由于跨度有些大,有些东西须要先从简说明一下。
首先是重载new和delete,用可自增的独立堆来存放同类对象,这个方法是从《Windows核心编程》上学来的,它能解决一些我遇到过的问题——线程栈空间不够,对象共享。
之前曾在线程中使用了new方法创建对象,因为开线程时线程栈是默认大小,空间很有限,运行一会儿就会报错,而且这样创建的对象,不利于线程间共享,所以那之后我在自己写的类里面都使用了这个重载操作符方法,另,子类如果不再重载一次的话,默认会使用父类的那个堆,基本代码如下:
头文件:
//使用独立堆存储对象
protected:
static HANDLE m_Heap;
static volatile UINT m_ObjectNum;
public:
void* operator new (size_t size);
void operator delete (void* p);
源文件(Music是类名):
//使用独立堆存储对象
volatile UINT Music::m_ObjectNum=0;
HANDLE Music::m_Heap=NULL;
void* Music::operator new (size_t size)
{
if(m_Heap==NULL)
{
//堆不存在,创建堆
m_Heap=HeapCreate(0,0,0);
//创建堆失败
if(m_Heap==NULL)
return(NULL);
}
//分配内存空间
void* p=HeapAlloc(m_Heap,0,size);
if(p!=NULL)
{
//成功创建对象后对象计数器+1
InterlockedExchangeAdd(&m_ObjectNum,1);
}
return (p);
}
void Music::operator delete (void* p)
{
if(HeapFree(m_Heap,0,p))
{
//成功释放内存后对象计数器-1
InterlockedExchangeAdd(&m_ObjectNum,-1);
}
if(m_ObjectNum==0)
{
//没有对象存在时销毁堆
if(HeapDestroy(m_Heap))
{
m_Heap=NULL;
}
}
}
其次是动态链接库的生成,我主要是想把类封装到dll中,方便在其它程序中使用。
首先是头文件中一些基本的宏定义:
#define DLLC extern "C"
#ifdef MYDLL
#define MYDLLAPI __declspec(dllexport)
#else
#define MYDLLAPI __declspec(dllimport)
#endif
之后在源文件中包含头文件前定义“MYDLL”就行。
须要导出自定义结构体,写成这样:
DLLC MYDLLAPI typedef struct tagPOINTEX{
}POINTEX;
须要导出自定义枚举型,写成这样:
MYDLLAPI typedef enum HPEN_STYLE{
};
须要导出类,写成这样:
DLLC class MYDLLAPI Music{
};
到此为止,准备工作告一段落,接下来进入正题,音乐播放。
与之前的相比,主要更改了两个地方:
一个是类别,从原来的一个Music类改为MusicBGM与MusicBGS两个类,MusicBGM与原来的Music类效果一样,MusicBGS主要是用来播放音效,区别在于一个MusicBGS对象会打开数次同一个音乐文件(默认为10次),而且不会响应循环播放等操作,主要是用于游戏中音效重叠播放效果。
其次是音乐管理类的循环机制,之前是每一次循环都会检测所有音乐对象的状态,在游戏开发中发现音乐对象多了之后,会非常占用CPU时间,即便它是摆在一个子线程中,所以改为一次循环只检测一个音乐对象的状态,当然,有消息会先响应消息,这么做之后发现确实能够缓解CPU压力,而且也不会影响游戏帧频了。
除此之外,对音乐管理类的消息队列增加了互斥锁,便于在多线程中使用,主要是为了应对多线程中游戏精灵发送音效播放消息的情况。
为了模拟多线程发送消息的情景,我写了一个DEMO,首先是动态链接库的导入:
头文件中:
#include "MusicLib\\MusicAPI.h"
源文件中:
#pragma comment(lib,"MusicLib\\MCIMusic.lib")
当然,DEMO中只有一个源文件,所以直接写成:
#include "MusicLib\\MusicAPI.h"
#pragma comment(lib,"MusicLib\\MCIMusic.lib")
然后是模拟多线程中游戏精灵发送音效播放消息,这些线程的代码如下:
//模拟线程数量
#define THREAD_NUM 10
volatile bool g_bExitFlag=false; //通知线程退出的标志
HANDLE g_hThread[THREAD_NUM];
//用于模拟游戏精灵在多线程中发送音效消息
unsigned __stdcall ThreadForPlayMusic(LPVOID lpParameter)
{
Sleep(1000);
srand(timeGetTime()); // 初始化随机数种子
while(true)
{
Sleep(rand()%1000);
MUSIC_PARAM MP;
MP.m_ID=g_pMusicBGS[rand()%MUSICBGS_NUM]->GetID();
MP.m_Play=true;
g_pMusicManager->AddMessage(MP);
if(g_bExitFlag==true)
break;
}
return 0;
}
因为音效不止一个,所以用了随机数,向随机音效对象发送播放消息,你也可以通过更改宏定义的值来改变模拟线程数量。
当然,DEMO中也载入了一个BGM,用户可以输入消息对BGM进行操作,这些操作包括play(播放),replay(重放),stop(暂停),loop(循环播放),normal(普通播放),kill(失去焦点),set(重获焦点),exit(退出程序),运行效果:
代码下载:
http://download.csdn.net/detail/daeba/8824935
代码中包含了DEMO项目和DLL项目。
PS:
话说之前游戏的帧数问题找到病根所在了,那是因为MCI对wav文件支持性不佳造成的,如果把用到的音乐文件全部改为mp3,就不会出现帧数问题,至于开启千千静听后帧数恢复正常的情况,应该是开启千千静听时会同时开启wav音频的加速插件,看来MCI还是有些老了,估计今后会去用DirectSound,现在的话就先将就着用吧。