■ 新・ゲーム開発講座




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


■第40夜:小窓を開いて文字入力A

前回はリソースエディタの解説で終わってしまいましたので、今回はいよいよダイアログBOXのプログラミングについて解説致します。ボタンが2つ、エディットボックス1つという非常にシンプルな構成ですが、これができるのと出来ないのとではゲームの印象も随分違ってきます。余談になりますが、たとえ話で 「100と1の差は100倍だが、1と0の差は無限大だ」 なんてのもあるように、たとえレベル1のワザでも知らない/出来ないよりは知っている/出来るのほうが断然良い訳です。さあ、頑張りましょう♪


■コマンド呼び出し部

スクリプトからコマンド解釈→処理関数の呼び出しまではいままで連載してきた内容と一緒ですので、さらりと流します。肝心なのはダイアログの起動とイベントハンドラですから、そこを中心に参りましょう。 なお
ソースは第39夜と共通です

そんな訳で、コマンド呼び出し部はあっさりと1行のみ追加♪ (Textengine.cpp)。コマンド処理関数は Com_text_input() とでも名前をつけておきましょう。



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演奏停止
if( strcmp(com_name,"play_midi")==0 ){ Com_play_midi(); flag=ON;} //MIDI演奏開始
if( strcmp(com_name,"stop_midi")==0 ){ Com_stop_midi(); flag=ON;} //MIDI演奏停止
if( strcmp(com_name,"play_mp3")==0 ){ Com_play_mp3(); flag=ON;} //MP3演奏開始
if( strcmp(com_name,"stop_mp3")==0 ){ Com_stop_mp3(); flag=ON;} //MP3演奏停止
if( strcmp(com_name,"set_font_size")==0 ){ Com_set_font_size(); flag=ON;} //フォントサイズ変更
if( strcmp(com_name,"set_text_area")==0 ){ Com_set_text_area(); flag=ON;} //テキストエリア変更
if( strcmp(com_name,"set_text_wait")==0 ){ Com_set_text_wait(); flag=ON;} //テキストウェイト変更
if( strcmp(com_name,"set_text_color")==0 ){ Com_set_text_color(); flag=ON;} //テキストカラー変更
if( strcmp(com_name,"set_str")==0 ){ Com_set_str(); flag=ON;} //文字列変数に値をセット
if( strcmp(com_name,"text_input")==0 ){ Com_text_input(); flag=ON;} //ダイアログを開いてテキスト入力

// ↑
//そのうちここに他のコマンドも追加していきましょう
// ↓

省略

}


■ダイアログの制御

では、いよいよダイアログの制御についてです。ここはソースを見ながら注意して内容を理解してほしい部分です。ただし、見ればおわかりと思いますが、処理のボリュームとしてはそれほど大きなものではありません。

今回のソースでは、ダイアログBOXの制御部分は Text_input_Proc()Com_text_input() の2つの関数で構成されています。前者がダイアログのイベントハンドラ、後者がダイアログの起動/終了処理を担当する関数です。

イベントハンドラとは、第2夜、第3夜でスケルトンの解説をした時にも出てきた 「メッセージをさばく」 ための関数です。イベントハンドラは、プログラムのどこからも直接呼ばれることはありません。ダイアログを開くときにシステムに登録され、それ以降、ダイアログ上でイベントが発生するたびにシステムから呼び出されます。前振りはこのくらいにして、実際にソース眺めてみましょう。

なお _Text_input という構造体をひとつ定義していますが、これはエディットBOXに入力した文字列を受け取るバッファと、入力がキャンセルされた場合に適用するデフォルト文字列を格納するために用意したものです(…べつに構造体である必然性はないんですけどね ^^;)。



struct _Text_input {
char edit_str[256]; //EditBox からの入力を受け取るバッファ
char default_str[256]; //入力がキャンセルされた場合に使用する文字列
};
_Text_input Text_input;


BOOL CALLBACK Text_input_Proc( HWND hwndDlg, UINT msg , WPARAM wparam, LPARAM lparam )
{

//メッセージコードによって処理を分ける
switch( msg ){
case WM_INITDIALOG: //イニシャル時
SetDlgItemText(hwndDlg,IDC_EDIT1,Text_input.default_str );
break;

case WM_COMMAND: //ユーザーによって何か操作が為され、イベントが発生した
//イベントコードは wparam に入っているので振り分け処理
switch( wparam ) {
case IDOK: //OK ボタンが押された

GetDlgItemText(hwndDlg,IDC_EDIT1,(LPTSTR)Text_input.edit_str, sizeof(Text_input.edit_str));

//文字列が空だった場合はデフォルト文字列をコピーしておく
if( strlen(Text_input.edit_str)==0 ){
strcpy( Text_input.edit_str , Text_input.default_str );
}
EndDialog( hwndDlg, TRUE ); //ダイアログを閉じる
return true;


case IDCANCEL: //キャンセルボタンが押された

//デフォルト文字列をコピー
strcpy( Text_input.edit_str , Text_input.default_str );

EndDialog( hwndDlg, FALSE ); //ダイアログを閉じる
return true;

}
break;
}

return FALSE; //↑で処理できずにこぼれおちてきたものはすべて「失敗」として扱う


}

int Com_text_input()
{

char name[256]; //変数の名前
int num; //変数の番号
int flag=OFF;

TEXT++;
strcpy( name,Kaiseki_TextStr() ); //Kaiseki_TextStr() はテキストから文字列を切り出す関数で
while( *TEXT==',' || *TEXT==' ' || *TEXT==0x0a )TEXT++; //カンマ、スペース、TABの読み飛ばし

num = kaiseki_10();
while( *TEXT==',' || *TEXT==' ' || *TEXT==0x0a )TEXT++; //カンマ、スペース、TABの読み飛ばし

strcpy( Text_input.default_str, Kaiseki_TextStr_DQ() ); //Kaiseki_TextStr_DQ() はテキストから文字列を切り出す関数

//ダイアログBOXを起動
DialogBox( hinst, MAKEINTRESOURCE(IDD_DIALOG1),hwnd,(DLGPROC)Text_input_Proc );

//受け取ったテキストを文字変数にコピー
if( strcmp( name,"\\STR" )==0) { _set_str( num, Text_input.edit_str ); flag=ON; };
// ↑
// 他の文字列変数を追加する場合はここに加えていきましょう♪
// ↓

if( flag==OFF ) {
char err_mess[256];
sprintf( err_mess,"無効な文字列指定子 [%s %d] が記述されています",name,num );
MessageBox( hwnd,err_mess,"Com_set_str()",MB_OK);
}

return 0;


}



さて、ざっとソースを見ていただいたところで、順次解説して参りましょう。

1)コマンド処理関数

まずはコマンド処理関数 Com_text_input() からです。この関数では、コマンド #text_input のパラメータ解釈を行った後に、DialogBox() というAPIでダイアログBOXの起動を行っています。DialogBox() の定義は以下のとおりです。

int DialogBox (
HINSTANCE hInstance, // 実行ファイルのインスタンス
LPCTSTR lpTemplate, // ダイアログBOXのテンプレート
HWND hWndParent, // 親ウィンドウのハンドル
DLGPROC lpDialogFunc // イベントハンドラの関数名
);



引数の内、ちょっと気をつけなければならないのは ダイアログBOXのテンプレート です。ダイアログBOXのID (ここではIDD_DIALOG1) をそのまま渡すのではなく、
MAKEINTRESOURCE() マクロで変換して指定します。その他の引数は特に難しくはないでしょう。実行ファイルのインスタンス、および親ウィンドウ(今回はゲーム本体のウィンドウがダイアログBOXから見た親ウィンドウになります)のハンドルについては第2夜のスケルトンの項を参照してください。またイベントハンドラの関数名を指定している部分は、実際には関数のポインタ参照に相当するのですが、ここではあまり難しく考える必要はないでしょう(^^)。

なおDialogBox() は失敗すると -1、成功するとダイアログ終了時のステータスを返しますが、今回のソースでは特にチェックしないことにします。

ダイアログを閉じた後は、入力された文字列をテキスト変数に代入して終了します。 「おいおい入力された文字ってどこから来るんだ」 という方、慌てずに引き続きイベントハンドラを解説しますので後述を参照願います♪ (^^)

なお、ちょっと手抜きをしてデータのセット部分は第38夜で組んだ _set_str( ) を流用していたりします。同じソースの上のほうに記述してありますので探してみてください(^^;)

2.イベントハンドラ

イベントハンドラ BOOL CALLBACK Text_input_Proc() はダイアログで発生するイベントの処理を行う関数です。関数の引数 HWND hwndDlg, UINT msg , WPARAM wparam, LPARAM lparam はスケルトンの解説に出てきたイベントハンドラ WinProc() とそっくりですね。これらの引数はやはり Windows のシステムから降ってくるものです。ダイアログBOXの起動とともにイベントハンドラ (まあ監視ループとでも思って頂ければ ^^;)が機し始めます。そして同時に親ウィンドウは停止し、ダイアログの処理が終わるまで「待ち」モードに入ります。

では、イベント処理の内容をもう少し詳細に見ていきましょう。ソースでは、まずシステムから振ってきたメッセージ msg のコードを調べ、処理を振り分けています。メッセージといても、ここでは WM_INITDIALOG (ダイアログが開いた)、WM_COMMAND (ユーザーがダイアログを操作した) のチェックだけですが…(^^)

◎ WM_INITDIALOG メッセージに対する処理

WM_INITDIALOG に対する処理は、ここではエディットボックスにデフォルトの文字列を表示することです。ダイアログが開いた瞬間に特定の文字列を表示することが出来れば、デフォルトの名前をまず表示して必要があれば変更する…という操作が可能です。やはりそっけなく EDIT などという文字列が表示されているより、山田太郎でも石川五右衛門でも、なにかデフォルトの名前が表示されていたほうがカッコイイですよね(^^)。エディットボックスの表示内容を変えるには、SetDlgItemText() というAPIを使用します。

SetDlgItemText(HWND hDlg, int nIDDlgItem, LPCTSTR lpString );

HWND hDlg
int nIDDlgItem
LPCTSTR lpString
ダイアログBOXのハンドル
エディットBOXのID
表示したい文字列


引数を見ると、このAPI を呼び出すにはエディットBOXのIDが必要なことが分かります。 第39夜で確認したたIDはここで使います。ダイアログBOXのハンドルはWindowsのシステムから振ってきたものをそのまま使います。

◎WM_COMMAND メッセージに対する処理

WM_COMMAND はユーザーがダイアログを操作したときに発生するイベントです。操作の内容は wparam に入ってきますのでそれをもとに処理を振り分けます。ところで勘のいい方はソースを見て気がつかれたかも知れませんが、個々のイベント名は実は第39夜で確認した各コントロールのIDに相当します。つまり、OKボタンが押されたなら IDOK が、キャンセルボタンが押されたら IDCANCEL が降ってくるという具合です。

なおエディットボックスの処理については特にイベントとしては扱いません。OKが確定した時点で結果だけをAPIの GetDlgItemText() で受け取り、文字列変数に代入します。CANCEL が確定したらエディットボックスの結果は受け取らず、文字列変数にはデフォルト文字列を書き込みます(まあ、こーゆーときのためのデフォルト文字列ですし ^^;)。そして、OKでもCANCELでも、ボタンが押されたら最後には EndDialog() を実行してダイアログBOXを閉じます。

UINT GetDlgItemText (
HWND hDlg, // ダイアログBOXのハンドル
int nIDDlgItem, // コントロールのID
LPTSTR lpString, // 文字列を受け取るバッファのアドレス
int nMaxCount // バッファの最大長
);



EndDialog() が実行されると、イベントハンドラでの監視ループは停止し、ダイアログが閉じて親ウィンドウの活動が再開します。これで名前入力ダイアログの操作は完了です。以外とシンプルだと思いませんか?(^^)

イベントハンドラの中で降ってくるメッセージが各コントロールのIDになっているということが分かれば、ダイアログを拡張したり別途追加したり…ということは比較的容易なのではないかと思います。要領をつかむには、実際にコンパイル→実行しながら少しずつ改造などして遊んでみると良いでしょう。

では、今回はこのへんで…♪