■ 新・ゲーム開発講座




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


■第16夜:カーソルブリンク

今回は、カーソルブリンクを実装します。いままでのサンプルはテキストが垂れ流し的に表示されていくだけで、ユーザーがゆっくりと読み終わるまで「待つ」ということが出来ませんでした。やはり適度なところ(たいていの場合は行末や文末)でカーソルをブリンクさせて、ユーザーがキーまたはマウスボタンを押すまで待たせる処理が欲しいところです。そんな訳で、本日もまずはソースをDLして頂いて、それを見ながら進めて参りましょう。




こんな感じ♪

■実装の前に・・・

さて、いきなりですがここでフラグを整理しておきます。このままコマンドをどんどん追加していくと、Mainloop() で参照すべきフラグもどんどん増えていってしまいます。これをグローバル変数で大量に保持するのは管理上もソースの美的観点からも良くありませんので(笑)、構造体にまとめてしまいましょう。とりあえず、ここでは構造体の定義を _Mode_stat という名前にして、いままでのフラグはこのメンバにしてしまいます。そして、これ以降実装するコマンドでは、ここにフラグを付け足していくことに致しましょう。なお構造体の定義は main.h の中で行い、構造体の実態は main.cpp の先頭で Mode_stat という名前で宣言しています。



struct _Mode_stat {
int
int
int
flag_text; //テキスト表示:ON=進行 OFF=停止
flag_delay; //遅延処理:ON=遅延あり OFF=遅延なし
flag_cursor_blink; //カーソル点滅:ON=点滅 OFF=点滅なし

};


また上記定義内容をみればお気づきのことと思いますが、テキスト表示の進行/停止も独立したフラグで管理するようにします。みな同じベースに立って管理しようということですね。定義が済んだら、初期化も済ませておきましょう。起動時には、テキスト表示を進行するフラグのみが ON で他は OFF にしておきます。フラグの初期化はプログラム起動時に呼ばれる init_game() 内で一緒におこなうようにしておきましょう(main.cpp参照)。

蛇足ですが構造体(変数の型と同義と思って差し支えないでしょう)を定義してその型で変数を宣言すると、メンバの内容はデフォルトで0になります。本ソースでは BasicTips.h において ON=1 OFF=0 に定義していますので、将来メンバ変数が増えた場合、特に明示的に初期化しなくても初期値としてOFFが適用されます。



void init_Mode_stat()
{
Mode_stat.flag_text = ON; //テキスト表示ON
Mode_stat.flag_delay = OFF; //遅延OFF
Mode_stat.flag_cursor_blink = OFF; //カーソル表示OFF

}


■パーツ用サーフェイス

フラグの整理が出来たら、いよいよカーソル表示の下準備です。カーソルブリンクをさせるためには、なによりもまず表示するカーソルの画像が必要ですね。ここでは、25×25ピクセルのパターンを3つ並べて以下のようなBMPを作って使用することにします。


カーソルBMP


あれ、なんでカーソル3つ分の面積になるんだ?・・・とお思いの方もいるかもしれません(^^)。実は左のパターンがカーソル本体、中央がマスク、右はバッファ用エリアです。マスクとはカーソルを画面に重ねて表示するときに後ろに透けてみえる部分の定義で、図ではマスクの白==RGB(255,255,255) の部分が丁度透けてみえる部分にあたります。

さらに、右側のバッファ領域は、カーソル表示で隠れてしまう部分の背景を一時的に待避するところです。BMP上であらかじめバッファ域を用意してしまうのは手抜きっぽい方法ですが(笑)、いちいちプログラム的に待避領域分のサーフェイスを生成したり消去したり・・・というやり方に比べたら簡便で分かりやすいと思います。

さてBMPが用意できたところで、これを格納するサーフェイス(画面)が必要になります。ここでBMPの保持方法として、@リソースとして保持 A外部ファイルとして読み込む の2種類の選択が可能ですが、今回は外部BMPとして読み込む方法を採用します。将来、カーソル以外のパーツを表示したくなった場合、それをカーソルBMPに書き加えるだけで同じように扱える下地を作っておきたいためです(たとえばADV化したときのウィンドウ枠とか ^^)。

では実装してみましょう。カーソルBMPを保持するサーフェイスを生成するため、まずはグローバル変数としてDCのハンドルとBITMAPのハンドルを宣言します。名前は・・・パーツ置き場ですから Parts なんとかという名前にでもしておきましょうか(安直 ^^;)。


HDC
HBITMAP
Parts_DC; //デバイスコンテキスト(パーツ類)
hParts_Bitmap; //パーツ画面本体のハンドル

初期化(main.cpp)については、以前実装したバックサーフェイスと同じ要領です。サーフェイスを作成したら、すかさずBMPを読み込んでおくようにしておきましょう。なお、今回はカーソルBMPに合せて75×25ピクセル分のサーフェイスにしてありますが、将来もっと大きなBMPを読ませたい場合は、その面積に応じて CreateCompatibleBitmap() の引数を変更して下さい。


void init_Parts_Surface()
{

HDC work_hdc; //作業用のDC

//■ バック画面の初期化
work_hdc=GetDC(hwnd); //主(表)画面のDCの内容を取得
Parts_DC=CreateCompatibleDC(work_hdc); //同じ設定でバック画面用のDCを生成
hParts_Bitmap=CreateCompatibleBitmap(work_hdc,75,25);//主(表)画面と同じ属性で画面生成
SelectObject(Parts_DC,hParts_Bitmap); //DCと画面本体を関連付ける

Load_Bmp( Parts_DC,"Parts.bmp"); //パーツ用BMPを読み込んでおく

ReleaseDC(hwnd,work_hdc); //作業用DCを開放


}

//----------------------------------
// 初期化メイン
//----------------------------------

void init_game()
{

init_Back_Surface(); //Back Surface作成
init_Parts_Surface(); //Parts Surface作成

Init_Text_engine(); //テキストエンジン初期化

init_Mode_stat(); //Mode_statにまとめたフラグ類の初期化


}


さて、これでプログラムが起動するのと同時にパーツ用サーフェイスにカーソル画像がロードされるという状況が出来上がりました。ひととおり準備ができたところで、次はいよいよコマンド本体のインプリメントです。

■撃つ

まずは、カーソルブリンクの仕様を確認しておきましょう。

■書式 : #wait または #w

テキスト表示を停止してカーソルをブリンクさせ、マウス左ボタン、スペース、リターンキーのいずれかが押されたらカーソルを消去してテキスト表示を再開する。

#w という短縮表記を許容したのは、単にスクリプト記述の効率をアップしたいというだけの理由です(笑)。ではさっそくコマンド解釈部 (TextEnine.cpp) に追加しましょう。



void Command_call ()
{

char com_name[256];
nt i=0;
int flag=OFF;

TEXT++; //#を飛ばす

//コマンド文字列の切り出し
while( *TEXT>0x20 ) {
com_name[i] = *TEXT;
TEXT++;
++;

}
TEXT--;
com_name[i]=0; //これはNULL文字

//コマンド名の評価 → 処理関数呼び出し
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( flag == OFF ) {

char str[256];

sprintf( str,"警告:無効なコマンド [#%s] が記述されています",com_name);
MessageBox(NULL,str,"Command_call()",MB_OK);


}


}


Com_cursor_blink() というのが、コマンド実行部のうち「撃つ」部分に相当する関数です(Text_Com_01.cpp参照)。一種の遅延処理に相当しますのでタイマー変数として cursor_timer というものを用意しました。

処理はまずフラグ操作からです。テキスト表示フラグ Mode_stat.flag_text を OFF にして、カーソルブリンク用のフラグ Mode_stat.flag_cursor_blink を ON にしています。Mainloop() ではこのフラグを参照して、テキスト表示を止めた状態でカーソルブリンクのタスク処理部を連続コールするようになります。

次に、これから行うカーソル表示で上書きされてしまう部分の画像をバッファ領域に待避し、タイマーを撃ちます。最後に1発目のカーソル表示を行って「撃つ」部分の処理はおしまいです。


DWORD cursor_timer; //タイマー用変数
DWORD cursor_wait = 200; //何ミリ秒待つのか記憶しておく変数

int Com_cursor_blink()
{

//テキスト表示を停止する
Mode_stat.flag_text = OFF;

//カーソルブリンク用フラグを立てる
Mode_stat.flag_cursor_blink = ON;

//カーソルによって隠れる背景領域を待避しておく
BitBlt(Parts_DC,50,0,25,25,Back_DC,TEXT_X,TEXT_Y,SRCCOPY);

//タイマーショット
HLS_timer_start( &cursor_timer );

//1発目のカーソル表示
BitBlt(Back_DC,TEXT_X,TEXT_Y,25,25,Parts_DC,25,0,SRCAND ); //AND
BitBlt(Back_DC,TEXT_X,TEXT_Y,25,25,Parts_DC, 0,0,SRCPAINT); //OR

return 0;


}

※テキスト表示位置はグローバル変数 TEXT_X、TEXT_Y で保持されています。カーソル表示位置はこの文字座標に準拠しています。



ところで、カーソル表示で2回 BitBlt() を呼んでいますが、これは最初の方がマスク処理、後の方がパターン本体の転送に相当します。これは「くりぬき表示」を行う場合の古典的手法で、論理演算の ANDOR を用いたものです。BitBlt() の転送オプションでは AND が SRCAND、OR が SRCPAINT のシンボル名で定義されています。ここでは詳細は省きますが「マスクを AND して本体を OR するとくりぬき表示が出来る」と覚えておくと便利でしょう。(DirectX ではマスクの代わりにカラーキーを適用できます)



■回す

次に Mainloop() からぐるぐる呼ばれ続けるタスク処理部分です。リストを見れば分かると思いますが、遅延処理の変形版になっています。関数の最初でタイマーチェックを行い、指定時間に達していなければ何もしないでリターンしています。単純な遅延と異なるのは、指定時間に達した後にまた次のタイマーショットを放っていることですね。要は、カーソル表示の ON・OFF を切り替えてはタイマー監視を繰り返しているのです。



int Com_cursor_blink_task ()
{

static int flag=OFF;

//指定時間経過したか? → していなければ Mainloop に戻る
if( HLS_timer_check( cursor_timer, cursor_wait )==false )return 0;

HLS_timer_start( &cursor_timer ); //次回に向けてのタイマーショット

switch ( flag ) {
case ON: //カーソル表示
BitBlt(Back_DC,TEXT_X,TEXT_Y,25,25,Parts_DC,25,0,SRCAND ); //AND
BitBlt(Back_DC,TEXT_X,TEXT_Y,25,25,Parts_DC, 0,0,SRCPAINT); //OR
flag = OFF; //次回はOFF
break;

case OFF: //カーソル非表示
BitBlt(Back_DC,TEXT_X,TEXT_Y,25,25,Parts_DC,50,0,SRCCOPY); //背景書き戻し
flag = ON; //次回はON
break;

}

return 0;


}


■締める

さて、カーソルブリンクの終了条件ですが・・・マウスの左クリック、またはリターンキー/スペースキーが押された場合でした。これは、イベントハンドラ WndProc() の中でチェックします。おお、いよいよイベントハンドラに手を出すときが来ましたね(^^)。とりあえず下のリストを見て下さい。

キーの押下げもマウスボタンクリックも、それに相当するメッセージコードが定義されています。まずはキー入力ですが、メッセージコードは WM_KEYDOWN、押されたキーの内容は wprm に入ってWindowsのシステムから降ってきます。キーコードは VK_なんたら というシンボル名で定義されています。スペースは VK_SPACEリターンは VK_RETURN、ついでに ESCは VK_ESCAPE になります。ここでは VK_SPASEVK_RETURN が押されたの場合のみ、カーソルブリンク終了関数 Com_cursor_blink_end() を呼ぶようにすれば良いのですね。(ついでと言ってはナニですが、ESCキーでプログラムが終了するようにもしてみました ^^;)

またマウスの左ボタン押下げは、WM_LBUTTONDOWN です。ここでもまったく同様にカーソルブリンクの終了関数を呼んでいます。



LRESULT WndProc(HWND hwnd,UINT msg,WPARAM wprm,LPARAM lprm)
{

//↓この例では、Window右上の消去ボタンを押して終了させることしか出来ません(爆)
//後に、解説しながらいろいろなイベント処理を追加していきます。

switch ( msg ) {
case WM_CREATE: //Windowが生成された
break;
case WM_DESTROY: //Windowの消去操作がされた
PostQuitMessage(0);
break;
case WM_KEYDOWN: //キーが押された
switch(wprm){
case VK_ESCAPE: //ESC
PostQuitMessage(0); //終了
break;
case VK_SPACE: //SPACE
case VK_RETURN: //RETURN
//カーソルブリンク中ならブリンク解除
if( Mode_stat.flag_cursor_blink == ON ) {
Com_cursor_blink_end();
}
break;
}
break;
case WM_LBUTTONDOWN: //マウス左ボタンが押された
//カーソルブリンク中ならブリンク解除
if( Mode_stat.flag_cursor_blink == ON ) {
Com_cursor_blink_end();
}
break;
default:
//その他のイベントはWindowsのシステムにお任せ(楽ちん、楽ちん)
return DefWindowProc(hwnd,msg,wprm,lprm);
}

return 0;

}
ではカーソルブリンクの終了はどのようにするのでしょうか。フラグを初期状態に戻してカーソルの背景部分を描き戻せば完了です。簡単ですね♪(Text_Com_01.cpp参照)
int Com_cursor_blink_end()
{

//テキスト表示再開
Mode_stat.flag_text = ON;

//カーソルブリンク用フラグを解除
Mode_stat.flag_cursor_blink = OFF;

//カーソルの背景部分を描き戻して終了する
BitBlt(Back_DC,TEXT_X,TEXT_Y,25,25,Parts_DC,50,0,SRCCOPY); //背景書き戻し

return 0;

}
さあ、これでずいぶんノベルらしくなってきましたね♪ まだまだ、へっぽこ的に頑張りましょう(^0^)