本章内容包括:
- 如何使用 Win32 API 播放波形声音
- 如何将波形声音合并到现有的游戏中
接上文 游戏编程入门(9):开发 Henway(小鸡过马路) 游戏
播放波形声音
Win32 API 提供了对播放波形声音的高级支持。高级支持是指可以使用Win32 API播放声音而不必关心波形声音格式的细节或者处理内存中的原始声音数据。
Win32 API 中的高级波形声音支持的一个缺点是,它不允许波形混合(波形混合是指混合波形声音,从而同时播放多个声音)。这意味着在任何特定时刻都只能播放一个波形声音。因为有时候可能需要同时播放两个或者更多的声音。
在需要解决一次只能播放一个声音的问题时,可以选择以下方法:
- 中断当前正在播放的声音,以便播放下一个声音
- 允许每一个声音播放结束,并拒绝那些试图中断当前所播放声音的声音
注:可以使用DirectX 游戏编程API来混合波形声音
无论怎样解决只能播放一个声音的限制问题,使播放波形声音称为可能的高级Win32 API函数名为PlaySound( ),如下所示:
BOOL PlaySound(LPCSTR szSound, HMODULE hModule, DWORD dwSound);
PlaySound( ) 函数的3个参数决定了各个不同的方面,例如是从一个波形文件还是从内存中(作为波形资源)播放声音。此外,这个函数还允许控制在播放一个声音时是否能够中断它,是否应该循环播放它。
播放文件中的波形声音
使用PlaySound( ) 函数的最简单方式是直接从本地硬盘驱动器上播放一个波形文件。在播放文件中的一个波形声音时,PlaySound( ) 函数的第1个参数szSound标识了波形文件的名称,第2个参数hModule只适用于播放波形资源,因此在播放波形文件时可以对第2个参数传递NULL。第3个参数dwSound用来确定播放波形声音的具体方式。
dwSound参数可以包含一个或多个不同的标志,它们控制了播放波形声音的不同方面。例如,SND_FILENAME 标志指定了是播放一个文件中的波形声音。另外还有两个标志:SND_SYNC和SND_ASYNC,它们决定了是同步还是异步播放一个波形声音。
对于同步波形,PlaySound( ) 函数不允许程序在完成播放之前继续操作,而在播放异步波形时,还允许程序继续其操作。
读者可能已经猜到了,因为我们不希望整个游戏在播放一个波形声音时暂停,所以同步波形在游戏中几乎是不可能的。因此,在使用PlaySound( ) 函数播放波形声音时,需要使用SND_ASYNC 标志。
下面是使用PlaySound( ) 函数播放一个波形文件的例子:
PlaySound(“Explosion.wav”,NULL,SND_ASYNC|SND_FILENAME);
这行代码,从磁盘上加载一个波形文件,在计算机上通过扬声器播放这个文件,同时还允许程序继续操作。
另一个值得一提的是PlaySound( ) 函数标志是SND_NOSTOP,它使一个声音更“尊重”正在播放的另一个声音。更具体的说。如果在播放一个波形声音时指定了SND_NOSTOP标志,那么这个声音不会中断另一个正在播放的声音。
这个标志的缺点是,最终就永远不会播放这个声音。如果想要确保无论如何都要播放一个声音,则不要指定SND_NOSTOP标志。没有指定SND_NOSTOP标志时,要播放的声音会中断当前正在播放的声音。
播放作为资源的波形声音
在本章前面,我提到了播放作为资源的波形声音相对于播放波形文件的好处——可以将波形资源组合到主程序文件中。因为不需要打开文件并从中读取文件,而是直接从内存中访问资源,所以这种方法也更为有效。
PlaySound( ) 函数包括了一个标志,允许指定正在播放一个波形资源而不是波形文件。SND_RESOURCE标志指出波形声音是一个资源,PlaySound( ) 函数的hInstance参数是一个资源标识符,因为必须从一个可执行文件中加载波形资源,所以必须在PlaySound( ) 函数的第2个参数中指定程序的模块句柄。本系列的游戏和示例都将这个句柄存储在g_hInstance全局变量中。
下面是使用PlaySound( ) 函数播放作为资源的波形声音的例子。
PlaySound((LPCSTR) IDW_EXPLOSION,g_hInstance,SND_ASYNC | SND_RESOURCE);
正如向游戏中添加新的位图一样,也需要在资源脚本中为在游戏中使用的每一个波形声音都创建一个资源ID和一个项目。
循环播放波形声音
在某些情况下,可能希望重复播放一个声音。PlaySound( ) 函数支持一个用来循环播放波形声音的标志,这意味着将不断播放这个声音,直到使用另一个声音中断它,或者PlaySound( ) 函数明确停止它为止。这个标志就是SND_LOOP,指定它就会导致反复不断的播放一个波形声音。
下面是播放一个循环的波形资源的例子。
PlaySound((LPCSTR) IDW_BACKBEAT,g_hInstance,SND_ASYNC | SND_RESOURCE| SND_LOOP);
因为同步循环播放一个声音是没有意义的,所以SND_LOOP标志还要求使用SND_ASYNC标志。记住,循环的声音会继续无休止地循环播放,直到播放另一个波形声音以停止它。或者使用对PlaySound( )的另一个调用来停止循环播放的波形声音。
停止播放波形声音
在停用游戏或者需要进入静音模式时停止播放波形时,可以在PlaySound( ) 函数中使用SND_PURGE标志来停止播放波形声音。
SND_PURGE标志要求像播放声音时那样使用PlaySound( ) 的前两个参数。例如,如果在播放一个波形资源时提供了一个模块句柄,那么在停止播放这个波形声音时仍然需要提供这个句柄。
下面是停止在上一小节中播放的循环声音的例子:
PlaySound((LPCSTR) IDW_BACKBEAT, g_hInstance, SND_PURGE | SND_RESOURCE);
这个例子说明了如何停止播放一个单独的波形声音,还可以停止播放任何声音,方法是向PlaySound( )传递NULL作为第一个参数,如下所示:
PlaySound(NULL,NULL,SND_PURGE);
这一段代码将停止当前正在播放的所有声音,而不管它是哪一个声音或者如何播放它的。
开发Brainiac 2 (记忆对对碰)示例程序
Brainiac 2 和Brainiac 的区别就是应用了PlaySound( )函数增加了游戏音效。
注意:若出现编译错误,请在项目设置->连接->对象/库模块中 加入 msimg32.lib winmm.lib
Brainiac 2 目录结构和效果图
Brainiac 2 目录结构:
Brainiac 2 效果图:
Brainiac 代码分析
MouseButtonDown( )
回顾一下,在Brainiac 游戏中,有关游戏逻辑的大量代码都位于MouseButtonDown( )函数中,无论玩家何时在游戏屏幕上单击鼠标,都将调用这个函数,并且所有方块配对工作都是在这里进行的。
//按下鼠标事件
void MouseButtonDown(int x, int y, BOOL bLeft)
{
//
int iTileX = x / g_pTiles[0]->GetWidth();
int iTileY = y / g_pTiles[0]->GetHeight();
// 确定了单击哪一个方块
if (!g_bTileStates[iTileX][iTileY])
{
// 确保这个方块还没有配对
if (g_ptTile1.x == -1)
{
// 播放方块选择声音
PlaySound((LPCSTR)IDW_SELECT, g_hInstance, SND_ASYNC | SND_RESOURCE);
// 设置第一个方块选择
g_ptTile1.x = iTileX;
g_ptTile1.y = iTileY;
}
else if ((iTileX != g_ptTile1.x) || (iTileY != g_ptTile1.y))
{
if (g_ptTile2.x == -1)
{
// 播放方块选择声音
PlaySound((LPCSTR)IDW_SELECT, g_hInstance, SND_ASYNC | SND_RESOURCE);
// 增加尝试次数
g_iTries++;
// 设置第二个方块选择
g_ptTile2.x = iTileX;
g_ptTile2.y = iTileY;
// 查看是否配对成功
if (g_iTiles[g_ptTile1.x][g_ptTile1.y] == g_iTiles[g_ptTile2.x][g_ptTile2.y])
{
// 播放方块配对成功声音
PlaySound((LPCSTR)IDW_MATCH, g_hInstance, SND_ASYNC | SND_RESOURCE);
// 设置方块状态,指出已经配对
g_bTileStates[g_ptTile1.x][g_ptTile1.y] = TRUE;
g_bTileStates[g_ptTile2.x][g_ptTile2.y] = TRUE;
// 清空方块选择
g_ptTile1.x = g_ptTile1.y = g_ptTile2.x = g_ptTile2.y = -1;
// 更新配对的计数并检查是否获胜
if (++g_iMatches == 8)
{
// 播放胜利声音
PlaySound((LPCSTR)IDW_WIN, g_hInstance, SND_ASYNC | SND_RESOURCE);
TCHAR szText[64];
wsprintf(szText, "You won in %d tries.", g_iTries);
MessageBox(g_pGame->GetWindow(), szText, TEXT("Brainiac"), MB_OK);
}
}
else
// 播放配对错误的声音
PlaySound((LPCSTR)IDW_MISMATCH, g_hInstance, SND_ASYNC | SND_RESOURCE);
}
else
{
// 清空方块选择
g_ptTile1.x = g_ptTile1.y = g_ptTile2.x = g_ptTile2.y = -1;
}
}
// 强制重新绘制以便更新方块
InvalidateRect(g_pGame->GetWindow(), NULL, FALSE);
}
}
对PlaySound( )函数的前两个调用是为了响应方块选择事件。回顾一下,在游戏的每一次配对中都必须选择两个方块,因此对于这两次方块的选择,都要播放IDW_SELECT声音。无论何时配对了两个方块,都将播放IDW_MATCH声音。类似的,无论何时选择了两个不能配对的方块,都将播放IDW_MISMATCH声音。最后,无论何时配对了所有方块,都将播放IDW_WIN波形声音。
Resource.h
因为波形声音是类似于位图和图标的资源,所以必须声明它们的资源ID,并在游戏的资源脚本中包括它们。对于Brainiac 2 游戏来说,在Resource.h头文件中声明了波形资源ID。
//-----------------------------------------------------------------
// Brainiac Resource Identifiers
// C++ Header - Resource.h
//-----------------------------------------------------------------
//-----------------------------------------------------------------
// Icons Range : 1000 - 1999
//-----------------------------------------------------------------
#define IDI_BRAINIAC 1000
#define IDI_BRAINIAC_SM 1001
//-----------------------------------------------------------------
// Bitmaps Range : 2000 - 2999
//-----------------------------------------------------------------
#define IDB_TILEBLANK 2000
#define IDB_TILE1 2001
#define IDB_TILE2 2002
#define IDB_TILE3 2003
#define IDB_TILE4 2004
#define IDB_TILE5 2005
#define IDB_TILE6 2006
#define IDB_TILE7 2007
#define IDB_TILE8 2008
//-----------------------------------------------------------------
// Wave Sounds Range : 3000 - 3999
//-----------------------------------------------------------------
#define IDW_SELECT 3000
#define IDW_MATCH 3001
#define IDW_MISMATCH 3002
#define IDW_WIN 3003
Brainiac.rc
在Resource.h头文件中声明了波形资源ID后,表明现在就可以将它们放在Brainiac.rc资源脚本中了。
//-----------------------------------------------------------------
// Brainiac Resources
// RC Source - Brainiac.rc
//-----------------------------------------------------------------
//-----------------------------------------------------------------
// Include Files
//-----------------------------------------------------------------
#include "Resource.h"
//-----------------------------------------------------------------
// Icons
//-----------------------------------------------------------------
IDI_BRAINIAC ICON "Res\\Brainiac.ico"
IDI_BRAINIAC_SM ICON "Res\\Brainiac_sm.ico"
//-----------------------------------------------------------------
// Bitmaps
//-----------------------------------------------------------------
IDB_TILEBLANK BITMAP "Res\\TileBlank.bmp"
IDB_TILE1 BITMAP "Res\\Tile1.bmp"
IDB_TILE2 BITMAP "Res\\Tile2.bmp"
IDB_TILE3 BITMAP "Res\\Tile3.bmp"
IDB_TILE4 BITMAP "Res\\Tile4.bmp"
IDB_TILE5 BITMAP "Res\\Tile5.bmp"
IDB_TILE6 BITMAP "Res\\Tile6.bmp"
IDB_TILE7 BITMAP "Res\\Tile7.bmp"
IDB_TILE8 BITMAP "Res\\Tile8.bmp"
//-----------------------------------------------------------------
// Wave Sounds
//-----------------------------------------------------------------
IDW_SELECT WAVE "Res\\Select.wav"
IDW_MATCH WAVE "Res\\Match.wav"
IDW_MISMATCH WAVE "Res\\Mismatch.wav"
IDW_WIN WAVE "Res\\Win.wav"
图形化表示: