次のページ 前のページ 目次へ

12. ペンタッチ処理とゲームのメインループ

画面ができれば次はペンタッチ処理の番です。 このタイプのゲームの場合、 人の操作が入るまでは何もせず待っているだけで、 タッチされてはじめてゲーム処理が進みます。 ここではペンタッチ処理と絡めつつ、 ゲームのメイン処理の実現方法について説明しましょう。

12.1 ペン関連のイベント

第3章で触れましたが、 moreソフトへはユーザー操作やシステム事象がイベントという形で通知されます。 これはペン操作も例外ではなく、次のような3つのイベントとしてまわってきます。
	PenDownID	画面がタッチされた時に発生
	PenStillDownID	ペンが押されたままの時に繰り返し発生
	PenUpID		ペンが離された時に発生
ボタン等のコントロールはこれらのペンイベントを監視しており、 自分宛ての PenDown で処理を開始し、 PenStillDownの間はペンが上にあるか否かでその表示状態を変えます。 そして PenUp が来た時点で押されたか否かを決定し、 ユーザーのイベント関数へ処理をまわすといった流れになります。 これがグラフィックツールであれば PenDown で点を描き始め、 PenStillDown のあいだ点を描き続けていき PenUp で終わるという感じでしょう。

ところで、これらイベントメッセージの流れは下記のような順番になっています。
	フォームメソッド関数の FormEventFilter()
			↓
		各コントロール
			↓
	フォームメソッド関数の FormOtherEvent()
従って、各コントロールに流れる前に処理する必要があれば FormEventFilter() に記述、 通常の処理は各コントロールのイベント関数に記述、 誰にも処理されなかった場合の追加処理は FormOtherEvent() に記述して利用するのです。

なお、FormEventFilter() 等には、 すべてのイベントが流れてきますから、 対応するイベントかどうか、それが本当に自分宛てのものなのかどうか、 しっかり確認することが大切です。

12.2 四川省で必要なイベント処理

Add in 四川省の場合は、 メインループの中でペンがタッチされるのを待ち、 その座標を得てどの牌が選択されたのか、 取る事ができるのか?などの判断を行っていました。 Add in と more の違いはあっても、 この基本的な考え方と流れには変わりがありません。

違いとしては、 Add in が自前でメインループを作ってペンの状態を調べ続けたのに対し、 moreはイベントとして勝手にまわってくることです。 このおかげで、moreではメインループをわざわざ書く必要がなく、 どこかでイベントを待ち構えていれば済むのです。

ペンイベントを待つ場所としては、先に挙げた FormEventFilter(), 各コントロールのイベント関数, FormOtherEvent() の3つがあるのですが、 今回は GameAreaUC という名前でユーザーコントロールを作りましたから、 これのコールバック関数内で記述するのが良いでしょう。

対応させるイベントは、 もとの四川省がペンタッチした時しか見ていないため、 こちらも PenDown だけという事で十分です。 従って、 [新規ゲーム]や[やり直し]といったものはボタン押下のイベントで処理し、 ゲーム本体は PenDown イベントの処理として作っていきます。

12.3 タッチされた牌を知るには

Add in では、TOUCH X, TOUCH Y でペンタッチされた座標を得て、 それを牌の縦横のサイズで割ってどこが押されたのか判定しました。 more では、PenDown イベントメッセージに座標値が含まれてくるので、 そこから値を得て同様に算出できます。

以下、GameAreaUCのコールバック関数の switch文内に記述する物を 順々に書いていきましょう。 (LEFTMGN, TOPMGN は左と上の隙間分、PAI_X_SIZEは牌の横方向のサイズ、 PAI_Y_MINIは重なり分を除いた牌の縦方向のサイズを#defineで定義した物)

[ペンタッチ座標の取得]
※PenDownメッセージの中に座標値がQP単位で入っている
POINT	point;

point.x = Message->PenDown.PositionX;
point.y = Message->PenDown.PositionY;
[自分宛てのイベントかを確認]
※UserRectで渡された領域内か外かで判断
if ( point.x < UserRect->left )
	break;
if ( point.y < UserRect->top )
	break;
if ( (UserRect->left + UserRect->width) <= point.x )
	break;
if ( (UserRect->top + UserRect->height) <= point.y )
	break;
[牌の範囲外も除外]
※牌を表示していない部分だった場合を除く
if ( (point.x < LEFTMGN) || (point.y < TOPMGN) )
	break;
[タッチ座標から牌の座標を計算(X:1〜18, Y:1〜8)]
※LEFTMGN, TOPMGN を引いた上で牌の大きさで割る
g_ewk->T1 = (point.x - LEFTMGN) / (PAI_X_SIZE) + 1;
g_ewk->U1 = (point.y - TOPMGN ) / (PAI_Y_MINI) + 1;
こうして、ペンタッチされた生の座標値から、 画面用配列 G[][] の添字位置を得る事ができました。 Add in 四川省で言うと、メインループ部分、ペンタッチ待ち、 配列の添字値の算出まで(510行まで)が終わった事になります。

12.4 ゲーム本来の処理の組み込み

画面表示や入出力というものは、 どうしてもOSやその環境に依存して書き直す部分が多くなります。 今回の場合も、牌の画像データからその描画処理を新規に起こし、 メインループすらも、展開して初期処理、 ペンタッチ処理といった形に分けて作り直す必要がありました。

しかしゲーム本来の処理部分・・・例えば、 牌が取れるかどうかの判定処理といった物は、 言語の違いによる微妙な違いを除けばほとんど一緒です。 考えてみれば当然で、 同じ物を作るのにアルゴリズムに大きな違いが出るわけはないんです。

四川省であれば、この後に牌の選択処理や判定処理、 そして取り除ける場合の処理などが続きますが、 今までのようにまるきり作り直すというより、 そのまま持ってきて修正するといった感じで済むんですね。

・・・となると、これはほとんど説明するまでもないでしょう。 1つ目の牌がタッチされた時のサブルーチン TC1()、 2つ目の時のサブルーチン TC2()、 牌を取り除くサブルーチン TC3()、 取れるかどうか判定する処理 HANTEI()、 取れる牌があるか検索する処理 SEARCH() を、 もとの Add in ソースに習って組み込んでいきます。

ちょっと行数が多いので別ファイルにしますが、 もとのソースと比べてみて、 そんなに差が無いという事を感じ取ってください (※牌消去時の接続ラインの描画や、時間待ち等は more 用に書き起こしてます)。

12.5 とりあえず遊べるmore四川省

さて、ここまでで画面に牌が表示され、 タッチした牌の選択と、ルールに従った除去までが動くようになりました。 まだ他の機能に切り替えた時の状態保存・復帰はなく、 オートパワーオフしたらお終い、 ヘルプもハイスコア登録も無し、 タイムも計測されない・・・とないないだらけのソフトですが、 [新規ゲーム] と [やり直し] ボタンくらい動けばそこそこ遊べそうです。

それじゃ、とりあえずフォームエディタで、 [新規] と [やり直し] のボタンに次の処理を書き込んで、 途中経過版の四川省を作ってみましょう。
[GameNewBtn の ButtonSelectID イベント関数]

void GameNewBtnBtnSelect(APLFORMPTR AplForm, MESSAGE* Message, const MESSAGE* BtnMsg)
{
	/* 新規に並べ直してゲーム開始 */
	MESSAGE		lm;

	SHUFFLE();

	g_ewk->SCH	= 0;
	g_ewk->MD	= 0;
	Z		= 144;
	g_ewk->P1	= N;
	g_ewk->HF	= 0;

	CGSET();

	lm.CommonMsg.MessageID = RedispID;
	ControlCall( GameAreaUC, AplForm->Control, &lm );
}


[GameRepBtn の ButtonSelectID イベント関数]

void GameRepBtnBtnSelect(APLFORMPTR AplForm, MESSAGE* Message, const MESSAGE* BtnMsg)
{
	/* 今の並びのままでゲームを再開 */
	MESSAGE		lm;

	g_ewk->SCH	= 0;
	g_ewk->MD	= 0;
	Z		= 144;
	g_ewk->P1	= N;
	g_ewk->HF	= 0;

	CGSET();

	lm.CommonMsg.MessageID = RedispID;
	ControlCall( GameAreaUC, AplForm->Control, &lm );
}
もとのAdd inでは、 *RENEW と *RENEW2 が新規とやり直しに相当する部分となっており、 GOTO文でそこに飛んだあと再びメインループに入っていたのですが、 more化するにあたっては、その時に行っている変数の初期化と、 ゲームエリアの再描画を抜き出してボタンのイベント関数に割り当てています。

ところで、もとのソースでは最後に ALLDRAW() で画面を描画させていたのに対し、 上では ControlCall() を使って RedispID メッセージを GameAreaUC に送っている事に注目してください。 今回は背景をクリアして描画し直したかったので、 コントロール全体を描画し直すようにしたわけなんですね。

さて、こうして作った”四川省 途中経過版”がコレ(↓)です。
そこそこ遊べるので試してみてください(ザウルスで直接ダウンロード可能)。


次のページ 前のページ 目次へ