FMOD快速上手
声音一向是游戏中不可或缺的一部份,不管是动人的背景音乐或营造气氛的音效,少了声音就完全没有玩游戏的感觉,甚至许多游戏的重点就放在声音上,比如说利用声音来判断敌人的位置等等。
从游戏开发者的角度来看,在声音方面至少有以下的需求:从游戏开发者的角度来看,在声音方面至少有以下的需求:
1. 读取音乐/声音文件。读取音乐/音效档案。 当然,音乐数据不一定存在档案上,也可能放在CD 音轨上,甚至使用streaming 的方式由网络传输(很少见,但也并非不可能)。当然,音乐资料不一定存在档案上,也可能放在CD音轨上,甚至使用streaming的方式由网路传输(很少见,但也并非不可能)。
2. 背景播放。背景播放。 除了把声音放出来,播放的同时也要能让游戏继续执行。除了把声音放出来,播放的同时也要能让游戏继续执行。
3. 混音。混音。 对于硬件来说,同一时间只能播放一个声音。对于硬体来说,同一时间只能播放一个声音。 有些硬件内建混音的功能,因此可以同时播放多道声音,不过数量还是有上限。有些硬体内建混音的功能,因此可以同时播放多道声音,不过数量还是有上限。 超过上限时就需要把所有正在播出的声音进行混合后,再送到声卡上。超过上限时就需要把所有正在播出的声音进行混合后,再送到声卡上。
(在Windows 上,送到声卡之前似乎会自行帮你混音,所以这点对Windows 开发者也许不是太大的问题?) (在Windows上,送到声卡之前似乎会自行帮你混音,所以这点对Windows开发者也许不是太大的问题?)
4. 各种声音效果,比如设定声源的位置以仿真方向与远近的不同,或是设定速度来仿真运动物体的都卜勒效应(Dopplar effect),甚至各种DSP 处理等等。各种声音效果,比如设定声源的位置以模拟方向与远近的不同,或是设定速度来模拟运动物体的都卜勒效应(Dopplar effect),甚至各种DSP处理等等。
而我今天要讲的,称之为FMOD 的音效函式库,正具备了以上所有功能。而我今天要讲的,称之为FMOD的音效函式库,正具备了以上所有功能。 除此之外,它也有跨平台的优点,除了Windows 外,也支持MacOS、Linux 甚至所有的次世代主机,如PS3、Xbox 360、Wii 等等。除此之外,它也有跨平台的优点,除了Windows外,也支持MacOS、Linux甚至所有的次世代主机,如PS3、Xbox 360、Wii等等。 而且在非商业用途下完全免费,是个很理想的音效解决方案。而且在非商业用途下完全免费,是个很理想的音效解决方案。
废话不多说,下面就来讲FMOD 的使用。废话不多说,下面就来讲FMOD的使用。
系统简介 系统简介
在FMOD 中主要有三种对象:System、Sound 以及Channel。在FMOD中主要有三种对象:System、Sound以及Channel。 因为我才用一个晚上而已,所以像是ChannelGroup、DSP 及Geometry 的部份就不深入了…因为我才用一个晚上而已,所以像是ChannelGroup、DSP及Geometry的部份就不深入了…
• System 管理与系统相关的工作,比如说从文件系统中读取声音檨本、设定输出的声卡(FMOD 可以同时控制多张声卡)、设定DSP buffer 大小、或是加载其它的plug-in 等。 System管理与系统相关的工作,比如说从档案系统中读取声音檨本、设定输出的声卡(FMOD可以同时控制多张声卡)、设定DSP buffer大小、或是载入其它的plug-in等。
• Sound 则是一段声音的样本(sample),已经准备好送往声卡播出。 Sound则是一段声音的样本(sample),已经准备好送往声卡播出。 Sound 的内部格式可能有三种:无压缩、压缩或串流: Sound的内部格式可能有三种:无压缩、压缩或串流:
o 无压缩 :最单纯的格式,直接把所有信息存在内存中,播放时几乎不需要CPU 资源,但也很吃内存。 无压缩 :最单纯的格式,直接把所有资讯存在记忆体中,播放时几乎不需要CPU资源,但也很吃记忆体。 适合长度短、会重复播放许多次的音效。适合长度短、会重复播放许多次的音效。
o 压缩 :如果使用mp3 或ogg 之类的格式储存声音,可以把数据以尚未解压缩的方式先读进内存中,播放时再实时解压缩。 压缩 :如果使用mp3或ogg之类的格式储存声音,可以把资料以尚未解压缩的方式先读进记忆体中,播放时再即时解压缩。 这会需要多一点CPU 资源,但可以减少内存用量,适合中等长度的声音。这会需要多一点CPU资源,但可以减少记忆体用量,适合中等长度的声音。
o 串流 :在串流模式下,读取和播放是同时进行的,而内存中只会保留一小部分的样本,因此几乎不吃内存,但缺点是无法重复播放,必需重开档案或重新联机才能再播放一次。 串流 :在串流模式下,读取和播放是同时进行的,而记忆体中只会保留一小部分的样本,因此几乎不吃记忆体,但缺点是无法重复播放,必需重开档案或重新连线才能再播放一次。 适合很长的背景音乐或是由网络传输的声音。适合很长的背景音乐或是由网路传输的声音。
• Channel 代表正在播放声音的实体。 Channel代表正在播放声音的实体。 对于Channel,你可以设定它的音量、播放的时间点、或是它在3D 空间中的位置与速度。对于Channel,你可以设定它的音量、播放的时间点、或是它在3D空间中的位置与速度。 一个Channel 只能播放一个Sound,但同一个Sound 可以让由不同的Channel 共享并同时播放。一个Channel只能播放一个Sound,但同一个Sound可以让由不同的Channel共享并同时播放。
马上就来写一个简单的音乐播放程序。马上就来写一个简单的音乐播放程序。
播放声音 播放声音
一开始我们要先产生一个System 对象,这可以用FMOD_Create_System达成:一开始我们要先产生一个System对象,这可以用FMOD_Create_System达成:
FMOD_SYSTEM * sys ; FMOD_RESULT r = FMOD_Create_System ( & sys ) ; if ( r != FMOD_OK ){
cerr << " Unable to create FMOD system: " << FMOD_ErrorString ( r ) << endl ; exit ( 1 ) ; } cerr << " Unable to create FMOD system: " << FMOD_ErrorString ( r ) << endl ; exit ( 1 ) ; }
所有FMOD 的函式都会传回FMOD_RESULT作为结果,我们可以检查它是否等于FMOD_OK来判断是否有错误发生。所有FMOD的函式都会传回FMOD_RESULT作为结果,我们可以检查它是否等于FMOD_OK来判断是否有错误发生。 为了简单起见,下面的程序代码中我会把这些检查省略。为了简单起见,下面的程序码中我会把这些检查省略。 (开始写游戏时可别省啊!) (开始写游戏时可别省啊!)
接下来是对System 初始化,并且载入一段mp3 声音:接下来是对System初始化,并且载入一段mp3声音:
FMOD_System_Init ( sys , 32 , FMOD_INIT_NORMAL , 0 ) ; FMOD_SOUND * sound ; FMOD_System_CreateSound ( sys , " test.mp3 " , FMOD_DEFAULT , 0 , & sound ) ; FMOD_System_Init ( sys , 32 , FMOD_INIT_NORMAL , 0 ) ; FMOD_SOUND * sound ; FMOD_System_CreateSound ( sys , " test.mp3 " , FMOD_DEFAULT , 0 , & sound ) ;
在FMOD_System_Init中,第一个参数是先前产生的System 对象,第二个32 则表示这个系统最多可以有32 的Channel,也就是最多可以同时播放32 道声音。在FMOD_System_Init中,第一个参数是先前产生的System对象,第二个32则表示这个系统最多可以有32的Channel,也就是最多可以同时播放32道声音。 第三和第四个参数可以设定一些进阶的选项,在这个例子中都使用默认值即可。第三和第四个参数可以设定一些进阶的选项,在这个例子中都使用预设值即可。
而FMOD_System_CreateSound则会产生一个Sound 对象。而FMOD_System_CreateSound则会产生一个Sound物件。 第一个参数同样是System,第二个则是我们要加载的声音档名称,FMOD 会自行判断文件格式并把它解压缩。第一个参数同样是System,第二个则是我们要载入的声音档名称,FMOD会自行判断档案格式并把它解压缩。 如果想用压缩或串流的方式读档,则需要设定第三个参数,这边我们就先使用默认值。如果想用压缩或串流的方式读档,则需要设定第三个参数,这边我们就先使用预设值。
最后就是把声音放出来:最后就是把声音放出来:
FMOD_CHANNEL * channel ; FMOD_System_PlaySound ( sys , FMOD_CHANNEL_FREE , sound , 0 , & channel ) ; FMOD_CHANNEL * channel ; FMOD_System_PlaySound ( sys , FMOD_CHANNEL_FREE , sound , 0 , & channel ) ;
FMOD_System_PlaySound会让某个channel 开始播放声音。 FMOD_System_PlaySound会让某个channel开始播放声音。 其中第二个参数可以指定我们要拿哪一个Channel 来播放,传入FMOD_CHANNEL_FREE则是叫FMOD 自行找一个可使用的Channel 来播放。其中第二个参数可以指定我们要拿哪一个Channel来播放,传入FMOD_CHANNEL_FREE则是叫FMOD自行找一个可使用的Channel来播放。
但,程序并非到此就结束,别忘了声音播放是在背景执行的,如果在这边就结束程序,声音也会马上停止。但,程序并非到此就结束,别忘了声音播放是在背景执行的,如果在这边就结束程序,声音也会马上停止。 因为这只是个简单的播放程序,因此只要进入一段循环等声音播完即可:因为这只是个简单的播放程序,因此只要进入一段回圈等声音播完即可:
FMOD_BOOL playing = 1 ; while ( playing ){
#ifdef WIN32
Sleep ( 10 ) ; #else
usleep ( 10000 ) ; #endif
FMOD_Channel_IsPlaying ( channel , & playing ) ; FMOD_System_Update ( sys ) ; } FMOD_Channel_IsPlaying ( channel , & playing ) ; FMOD_System_Update ( sys ) ; }
我使用了Unix 上的usleep ,因此循环中的程序代码每0.01 秒会执行一次。我使用了Unix上的usleep ,因此回圈中的程序码每0.01秒会执行一次。 Windows 上则改用Sleep 。 Windows上则改用Sleep 。
FMOD_Channel_IsPlaying可以拿来检查Channel 是否已经放完全部的声音,因此声音播放完就可以跳出循环。 FMOD_Channel_IsPlaying可以拿来检查Channel是否已经放完全部的声音,因此声音播放完就可以跳出回圈。 FMOD_System_Update 则是更新System,包括呼叫callback 或是设定声音的3D 位置等。 FMOD_System_Update则是更新System,包括呼叫callback或是设定声音的3D位置等。 在这例子中可能不太重要,但若使用到其它功能时就一定要呼叫update 了。在这例子中可能不太重要,但若使用到其它功能时就一定要呼叫update了。
最后播放完,则使用FMOD_Sound_Release与FMOD_System_Release释放系统资源:最后播放完,则使用FMOD_Sound_Release与FMOD_System_Release释放系统资源:
FMOD_Sound_Release ( snd ) ; FMOD_System_Release ( sys ) ;
这么一来这个简单的播放程序就完成了!这么一来这个简单的播放程序就完成了!
加入3D 效果 加入3D效果
接下来我们加上一点3D 效果吧。接下来我们加上一点3D效果吧。 首先我们要在建立Sound 时指定3D 的功能:首先我们要在建立Sound时指定3D的功能:
FMOD_System_CreateSound (
sys , " test.mp3 " , FMOD_LOOP_OFF | FMOD_3D | FMOD_HARDWARE , 0 , & sound sys , " test.mp3 " , FMOD_LOOP_OFF | FMOD_3D | FMOD_HARDWARE , 0 , & sound
) ;
这边看起来有点复杂,但其实只是指定这个声音并不重复播放( FMOD_LOOP_OFF )、加入3D 效果( FMOD_3D ) 以及使用硬件加速( FMOD_HARDWARE )。这边看起来有点复杂,但其实只是指定这个声音并不重复播放( FMOD_LOOP_OFF )、加入3D效果( FMOD_3D )以及使用硬体加速( FMOD_HARDWARE )。 先前所使用的FMOD_DEFAULT其实只是把3D 改成2D 而已,其它选项完全相同。先前所使用的FMOD_DEFAULT其实只是把3D改成2D而已,其它选项完全相同。
接下来我们可以用FMOD_Channel_Set3DAttributes来设定Channel 在空间中的位置及速度:接下来我们可以用FMOD_Channel_Set3DAttributes来设定Channel在空间中的位置及速度:
FMOD_VECTOR pos ; pos . x = 0.0 f ; pos . y = 0.0 f ; pos . z = 2.0 f ; FMOD_Channel_Set3DAttributes ( channel , & pos , NULL ) ; pos ; pos . x = 0.0 f ; pos . y = 0.0 f ; pos . z = 2.0 f ; FMOD_Channel_Set3DAttributes ( channel , & pos , NULL ) ;
第二个参数即为位置,用户默认是位于(0,0,0) 的地方,前方为正Z轴,左方为正X,上方为正Y。第二个参数即为位置,用户预设是位于(0,0,0)的地方,前方为正Z轴,左方为正X,上方为正Y。 第三个参数则是速度,如果不需要仿真都卜勒效应,给NULL即可。第三个参数则是速度,如果不需要模拟都卜勒效应,给NULL即可。
上面的程序代码会把音源放在用户前方两个单位的位置。上面的程序码会把音源放在使用者前方两个单位的位置。 当然,如果声源不会移动,就没有意思了:当然,如果声源不会移动,就没有意思了:
while ( playing ){
#ifdef WIN32
Sleep ( 10 ) ; #else
usleep ( 10000 ) ; #endif
FMOD_Channel_IsPlaying ( channel , & playing ) ; unsigned FMOD_Channel_IsPlaying ( channel , & playing ) ; unsigned int msec ; FMOD_Channel_GetPosition ( channel , & msec , FMOD_TIMEUNIT_MS ) ; float msec ; FMOD_Channel_GetPosition ( channel , & msec , FMOD_TIMEUNIT_MS ) ; float angle = 3.1415926 f * msec / 4000.0 f ; pos . x = 2.0 f * sin ( angle ) ; pos . z = 2.0 f * cos ( angle ) ; FMOD_Channel_Set3DAttributes ( channel , & pos , NULL ) ; FMOD_System_Update ( sys ) ; } angle = 3.1415926 f * msec / 4000.0 f ; pos . x = 2.0 f * sin ( angle ) ; pos . z = 2.0 f * cos ( angle ) ; FMOD_Channel_Set3DAttributes ( channel , & pos , NULL ) ; FMOD_System_Update ( sys ) ; }
这边我们会让声源绕着使用者转圈圈,使用FMOD_Channel_GetPosition可以得到目前播放的时间点,由这个时间乘上速度(八秒钟一圈,也就是四秒钟所转动的弧度π),即可得到转动角,再套上sin / cos就可以得到新的位置了。这边我们会让声源绕着使用者转圈圈,使用FMOD_Channel_GetPosition可以得到目前播放的时间点,由这个时间乘上速度(八秒钟一圈,也就是四秒钟所转动的弧度π),即可得到转动角,再套上sin / cos就可以得到新的位置了。
完整的程序代码在这里: sample.cpp 。完整的程序码在这里: sample.cpp 。 这支程序加了一些错误处理,并且使用命令行参数当作播放档案。这支程序加了一些错误处理,并且使用命令行参数当作播放档案。 执行时要先等一段时间(因为使用非压缩的方式),然后应该能听到声音在绕着使用者转圈圈。执行时要先等一段时间(因为使用非压缩的方式),然后应该能听到声音在绕着使用者转圈圈。
结语 结语
FMOD 在使用上还满简单的,复杂的解压缩、混音、硬件控制等全部都被包装起来,因此游戏制作人员不需要再花额外的功夫处理。 FMOD在使用上还满简单的,复杂的解压缩、混音、硬体控制等全部都被包装起来,因此游戏制作人员不需要再花额外的功夫处理。 唯一不满的地方就是它的C++ interface 实在设计不良,完全没有用到C++ 威力(连OOP 都沾不上边),这也是为什么我都使用C interface 的原因:两者并没有明显的不同。唯一不满的地方就是它的C++ interface实在设计不良,完全没有用到C++威力(连OOP都沾不上边),这也是为什么我都使用C interface的原因:两者并没有明显的不同。
撇开这不谈,FMOD 功能够强、跨平台又免费,的确很适合制作游戏。撇开这不谈,FMOD功能够强、跨平台又免费,的确很适合制作游戏。 当然早有许多商业或免费游戏使用FMOD 来播放声音。当然早有许多商业或免费游戏使用FMOD来播放声音。