■ 新・ゲーム開発講座




■ へっぽこプログラミング入門♪


■第33夜:効果音(WAV)を鳴らす

前回までの内容で選択処理まで出来るようになって、ゲームとしての体裁は随分整ってきました。・・・が、何かまだ足りないものがあります。そう、です♪ 一人静かに黙々とプレイする・・・というスタイルを否定する訳ではありませんが(笑)、やはり音の鳴らないゲームは片手落ちと言っても過言ではないでしょう。そんな訳で、今回は効果音としてWAVを再生してみましょう。サンプルソースはこちらになります。

■マルチメディアって?

なんだか最近は言葉に出すのも恥ずかしくなった?「まるちめでぃあ」なる言葉ですが、どっこいプログラミングの世界ではまだ生きています。オーディオ/ビデオ系の機能を使うためには、このマルチメディアライブラリのお世話になることになります。もちろんWAVを鳴らすことだって立派なマルチメディアです(・・・少なくとも言葉の上では ^^)。

・・・とまあ冗談はさておき、WAVやMIDIを鳴らすにはライブラリの追加が必要です。VC++の統合環境からプロジェクト→設定を選び、開いたダイアログ内でリンクのタブをクリックしてください。するとオブジェクト/ライブラリモジュールと表示されたテキストBOXが現われますので、ここに winmm.lib を追加します。また、このライブラリの中身を使用するためには、ソースファイルの先頭で mmsystem.h のインクルードが必要になります。

※winmm.lib は標準でVC++に付属してくるライブラリですが、ワークスペースを作るときにデフォルトで設定されるリンクライブラリには含まれません。そんな訳で手動で登録してやる必要があります。

■PlaySound()で音を鳴らす

MIDIやCD-DAでは少々手続きが面倒なのですが、 WAVを単発で再生するだけならとても簡単です。とりあえずマルチメディアライブラリ中の



bool PlaySound(LPCSTR pszSound,HMODULE hmod,DWORD fdwSound);

LPCSTR pszSound 再生するサウンド
HMODULE hmod インスタンスハンドル
DWORD fdwSound 再生フラグ


を知っていれば充分だと思います♪ ここで言う単発とは、波形合成して複数のWAVを鳴らすことは出来ないという意味で、ループ再生はフラグ指定で可能です。とりあえずリソースWAVは無視してファイル読みに限定すると、以下のようにすれば即席で音が鳴らせます。

●単発再生
PlaySound( "ABC.wav", NULL, SND_FILENAME | SND_ASYNC );

●ループ再生
PlaySound( "ABC.wav", NULL, SND_FILENAME | SND_ASYNC | SND_LOOP );



ここで ABC.WAV は再生したいWAVファイル名です。また↑ここではインスタンスハンドルにNULLを渡していますが、ここでいうインスタンスはリソースとしてWAVを含んでいるものを指しています。リソースWAVは実行ファイルに取りこまれる形でメモリ上に存在している音声ファイルのことですが、ゲームで使用する効果音をすべてリソースとして取りこんでしまうのはサイズ的に非現実的ですので(笑)、ここではファイル読みに徹して第2引数はNULLにしておくのが無難だと思います。

その後に続くフラグ指定ですが、SND_FILENAME は "WAVはリソースではなくファイル名指定" と言う意味です。また SND_ASYNC は非同期再生を意味します。同期再生にしてしまうと演奏が終るまでプログラムが固まったままになってしまいますので必ず非同期にしておきましょう。フラグには便利なものがもう一つあります。ループ指定の SND_LOOP です。これらを必要に応じて OR することでフラグを形成する訳ですね。 なお演奏を停止する場合は、以下のようにファイル名を NULL にしてもう一度 PlaySound() を呼べばOKです。

●停止
PlaySound( NULL,NULL,0);


■コマンド書式

では、以上の処理をスクリプトコマンドの形でインプリメントしてみましょう。ここではコマンド仕様は以下のようなものにして話を進めます。@一発再生で終わる音、A延々とループしながら流したい音(波、風、虫の声など)の両方に対応したいので、フラグ付きのコマンドにします。

また WAVファイルはキャラクタを喋らせるようなゲームになると飛躍的に量が増えたりしますので、サブフォルダ .\WAV を作ってファイルはそこに入れるようにしましょう。

【WAVを鳴らす】
書式 : #play_wav flag filename

flag :0=単発再生 1=ループ再生
filename:wavファイル名(***.wav)


【WAVを停止する】
書式:#stop_wav


■フラグ

今回は Mode_stat に新たなメンバは加えません(タスク化する必要がありませんので… ^^)。

■演奏ステータス

Mainloopで参照するフラグこそ使いませんが、ここでは演奏ステータスを保持するために以下のような構造体を定義しておきます(Text_Com_03.h 参照)。演奏するだけであればステータスの保持などは不要なのですが、後日解説するSAVE/LOADのために「現在の状態」を保持する仕掛けを用意しておこう、という次第です。海辺のシーンにおける「波」のように延々とループしながらWAVを再生することは良くあることですが、SAVE→LOADしたら演奏ステータスを忘れて音が消えてしまった・・・などというのではカッコ悪いですから…(^0^)



struct _WAV_stat {
int WAV_play_stat; //WAVを再生中か? ON=再生中 OFF=再生していない
int WAV_play_flag; //WAV:loop再生かどうかのフラグ
char WAV_fname[256]; //WAV:再生中のファイル名
};

extern _WAV_stat WAV_stat; //実体は Text_Com_03.cpp の中にあります。


■コマンド呼び出し部

TextEngine.cpp の Command_call() に以下の記述を追加します。う〜ん、コマンドも随分充実してきましたねぇ(^0^)

void Command_call()
{

//省略

//コマンド名の評価 → 処理関数呼び出し
if( strcmp(com_name,"delay" )==0 ){ Com_delay(); flag=ON;} //遅延
if( strcmp(com_name,"wait" )==0 ){ Com_cursor_blink(); flag=ON;} //カーソルブリンク
if( strcmp(com_name,"w" )==0 ){ Com_cursor_blink(); flag=ON;} //カーソルブリンク
if( strcmp(com_name,"halt" )==0 ){ Com_halt(); flag=ON;} //終了
if( strcmp(com_name,"page" )==0 ){ Com_page(); flag=ON;} //改ページ
if( strcmp(com_name,"g_load" )==0 ){ Com_g_load(); flag=ON;} //BMPのLOAD
if( strcmp(com_name,"g_copy" )==0 ){ Com_g_copy(); flag=ON;} //BMPのCOPY
if( strcmp(com_name,"g_change")==0 ){ Com_g_change(); flag=ON;} //画面切替
if( strcmp(com_name,"flag_on" )==0 ){ Com_flag_on(); flag=ON;} //イベントフラグON
if( strcmp(com_name,"flag_off")==0 ){ Com_flag_off(); flag=ON;} //イベントフラグOFF
if( strcmp(com_name,"disp_flag")==0 ){ Com_disp_flag(); flag=ON;} //イベントフラグ表示
if( strcmp(com_name,"if_flag" )==0 ){ Com_if_flag(); flag=ON;} //フラグによる分岐
if( strcmp(com_name,"jump" )==0 ){ Com_jump(); flag=ON;} //ラベルジャンプ
if( strcmp(com_name,"file_change")==0 ){Com_file_change(); flag=ON;} //ファイル間ジャンプ
if( strcmp(com_name,"select3" )==0 ){ Com_select3(); flag=ON;} //3択
if( strcmp(com_name,"play_wav")==0 ){ Com_play_wav(); flag=ON;} //WAV演奏開始
if( strcmp(com_name,"stop_wav")==0 ){ Com_stop_wav(); flag=ON;} //WAV演奏停止

//省略

}


■コマンド実行部

【WAVを演奏する】

まずこれまでと同じ要領で、kaiseki_10()Kaiseki_TextStr() を用いてパラメータ解析を行います。パラメータの値に応じて PlaySound() に渡すフラグの値を決めて「一発演奏」か「ループ演奏」かを振り分ける訳ですね。その後はステータスを保持して PlaySound() を呼んでいます。パラメータ解釈の部分はほとんど定型作業なので他のコマンド記述部からカット&ペーストしても全然OKですね♪。



_WAV_stat WAV_stat;//ステータス保持用構造体

int Com_play_wav()

{

char f_name[256];
DWORD flag;

TEXT++;
switch( kaiseki_10() ) {
case 0: flag=SND_FILENAME | SND_ASYNC;
//↑ファイル名指定+非同期
break;
case 1: flag=SND_FILENAME | SND_ASYNC | SND_LOOP;
//↑ファイル名指定+非同期+ループ再生
break;
}
while( *TEXT==',' || *TEXT==' ' )TEXT++; //空白or「,」を飛ばす

strcpy(f_name,WAV_PATH ); //パスは main.h にて定義しています
strcat(f_name,Kaiseki_TextStr()); //ファイル名切出し

//フラグその他の設定
WAV_stat.WAV_play_stat = ON; //演奏ON
WAV_stat.WAV_play_flag = flag; //flag内容を保存
strcpy(WAV_stat.WAV_fname,f_name);

PlaySound( f_name, NULL, flag );

return 0;

}


【演奏を停止する】

演奏の停止はパラメータ解釈がないので超カンタンです。ファイル名に NULL を指定して PlaySound() を呼び、演奏を停止します。あとはステータスをクリアしておしまい。らくちんです♪


int Com_stop_wav()

{
PlaySound( NULL, NULL, 0 ); //ファイル名に NULL を指定すると演奏が止まるのよん♪

//フラグ類の解除
WAV_stat.WAV_play_stat = OFF; //演奏OFF
WAV_stat.WAV_play_flag = 0; //flag内容をゼロクリア
strcpy(WAV_stat.WAV_fname,""); //ファイル名クリア

return 0;
}


そんな訳で、今回はおしまいです。

なお今回ソースに同梱したWAVファイルは
某所某氏(ただいまインドの山奥で修行中につき情報秘匿中 ^^;)に提供して頂きました。 ご協力頂きありがとうございます〜