iPad専用アクションゲーム「スライムは投げる」

最近のエントリー

サンプルコードのダウンロード

では、まずサンプルコードをダウンロードし、解凍してください。
私はVisual C++6.0でコンパイルしているので、をお持ちの方はVisual C++でプロジェクトファイルを開いてください(「game_06.dsw」をダブルクリックすれば開けます)。
圧縮ファイルに含まれる「game_06.exe」をダブルクリックし、実行してみてください。
どうでしょう?画面が切り替わり、フルスクリーン化すると思います。
そして、カーソルキーを押すと、キャラクターが動くと思います。
というわけで今回は、慣性の法則を使用したキャラ移動について解説したいと思います。

慣性の法則とは?

慣性の法則とは、私も詳しくは知りませんが、「物体は同じ運動を続けようとする」とかそんな感じの法則だった気がします。
宇宙空間のような摩擦の少ない場所で、一度動き出すと何かにぶつかったりするまで動き続けるというやつです。
スケートで一度動き出したらなかなか止まらない現象も同じですね。
詳しく知りたいなら物理の先生とかに聞いてください。

過去のゲームでも慣性を使用したゲームなんて腐るほどありますよね?
月面着陸ゲームだとか。
Bio100%さんの蟹味噌もそうですね。
使い道はいろいろあるので覚えておいても損は無いでしょう。

実際にプログラムを組む

ではサンプルコードを見ていきます。

//----------[ メイン関数 ]----------------------------------------------------------------------
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdParam, int nCmdShow){
    
	<略>

	//    cx:座標     cy:座標 mv:加速度   
	float cx=320.0f-16,cy=240.0f-16,mv=0.1f;
	float cmx=0.0f,cmy=0.0f;//移動量
	
	int angle=0;

	//サイン、コサインテーブル作成
	float fsin[360],fcos[360];

	int i;
	for(i=0;i<360;i++){
		fsin[i]=(float)sin(i*3.1415926535/180);
		fcos[i]=(float)cos(i*3.1415926535/180);
	}

	//メインループ
	while(1){
		WaitSet();					//現在の時間(単位:ミリ秒)取得
		ClearScreen(lpBack);		//バックバッファ初期化
		DdTextOut(lpBack,0,0,"↑、↓、→、←で移動 pauseキーで終了",255);
		
		angle=-1;	//とりあえず角度を-1にしておく

		//[38]↑	[40]↓	[37]←	[39]→
		if(keyg[38] && keyg[39])	//右上
			angle=45;
		else
		if(keyg[37] && keyg[38])	//左上
			angle=135;
		else
		if(keyg[37] && keyg[40])	//左下
			angle=225;
		else
		if(keyg[40] && keyg[39])	//右下
			angle=315;
		else
		if(keyg[38])	//↑
			angle=90;
		else
		if(keyg[40])	//↓
			angle=270;
		else
		if(keyg[37])	//←
			angle=180;
		else
		if(keyg[39])	//→
			angle=0;

		//angleの値が変わっていたら移動量を変更する
		if(angle!=-1){
			cmx+=fcos[angle]*mv;
			cmy-=fsin[angle]*mv;
		}

		cx+=cmx;
		cy+=cmy;

		
		//跳ね返る
		if(cx<0){		//左
			cx=0.0f;
			cmx=-cmx;
		}
		if(cx>640-32){	//右
			cx=float(640-32);
			cmx=-cmx;
		}
		if(cy<0){		//上
			cy=0.0f;
			cmy=-cmy;
		}
		if(cy>480-32){	//下
			cy=float(480-32);
			cmy=-cmy;
		}

		//キャラクタ表示
		BltClip(lpBack,(int)cx,(int)cy,32,32,lpWork,0,0,1);
		Flip();						//フィリップ
		Wait(1000/60);				//メッセージループへ
	}

では、順番に解説して行きたいと思います。

	float cmx=0.0f,cmy=0.0f;		//移動量(慣性
	<略>
	cx+=cmx;
	cy+=cmy;

移動量(cmx,cmy)は毎回キャラクタの座標を移動させる値です。
例えば、cmxを5.0に設定しておけばキャラは右へ移動し続けます。
で、キー入力により、この移動量を変えることによって、慣性の法則のようなものが実現できるというわけです。

今まではキー入力でそのままキャラの座標を変更していましたが、慣性を使用する場合はキー入力で移動量を変化させ、移動量によって毎回キャラクターを移動させます。

それから、ついでなのでキャラクターを画面の端で跳ね返らせる事にします(そうしないと直ぐ画面外に行ってしまう)。

		//跳ね返る
		if(cx<0){		//左
			cx=0.0f;
			cmx=-cmx;
		}
		if(cx>640-32){	//右
			cx=float(640-32);
			cmx=-cmx;
		}
		if(cy<0){		//上
			cy=0.0f;
			cmy=-cmy;
		}
		if(cy>480-32){	//下
			cy=float(480-32);
			cmy=-cmy;
		}

例えば、左端の壁を越えてしまった(cxが0より小さくなった)場合は、まずcxを0にします。
これをやらないとキャラクターが埋まってしまったり、帰ってこなくなる場合があるからです(^^;
で、移動量(cmx)の符号を反転させてやります。

サンプルコードのダウンロード

では、まずサンプルコードをダウンロードし、解凍してください。
私はVisual C++6.0でコンパイルしているので、お持ちの方はVisual C++でプロジェクトファイルを開いてください(「game_05.dsw」をダブルクリックすれば開けます)。
圧縮ファイルに含まれる「game_05.exe」をダブルクリックし、実行してみてください。
どうでしょう?画面が切り替わり、フルスクリーン化すると思います。
そして、カーソルキーでキャラクタが8方向へ移動すると思います。

というわけで、今回は4つのキー入力で8方向へキャラクタを移動させる方法を説明したいと思います。

やってはいけないのかも?

「ゲーム制作基礎 第2回」では4つのキー入力でキャラクタを4方向に動作させましたが、8方向というとよくこんな感じでやってしまいます(第2回のキー入力部分のelseを外しただけ)。

int cx=320-16,cy=240-16,mv=3;

	略

	//キャラクター移動
	if(keyg[38])	//↑
		cy-=mv;
	if(keyg[40])	//↓
		cy+=mv;
	if(keyg[37])	//←
		cx-=mv;
	if(keyg[39])	//→
		cx+=mv;
		
	//キャラクタ表示
	BltClip(lpBack,cx,cy,32,32,lpWork,0,0,1);

たしかに、これでも8方向の移動は可能ですが、例えば右上に移動する場合はどうでしょう?
mvが3として、右に3、上に3移動しますよね?直線距離で考えると4.24264068・・・というように3よりも移動距離が大きい事になります。
ですから、このプログラムでは斜めに移動する場合、移動量がおかしいという事になります。
(あえてこれにするという考えも有りだと思いますが。)

キャラクターの8方向移動

というわけで、8方向、同じ距離で進ませるには、三角関数を使用すると、比較的簡単に出来ます。
三角関数については前回みっちりやったので大丈夫ですね?

int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdParam, int nCmdShow){

	<略>

	//    cx:座標 cy:座標 mv:移動量   
	float cx=320.0f-16,cy=240.0f-16,mv=3.0f;
	
	int angle=0;

	//サイン、コサインテーブル作成
	float fsin[360],fcos[360];

	int i;
	for(i=0;i<360;i++){
		fsin[i]=(float)sin(i*3.1415926535/180);
		fcos[i]=(float)cos(i*3.1415926535/180);
	}

	//メインループ
	while(1){
		WaitSet();					//現在の時間(単位:ミリ秒)取得
		ClearScreen(lpBack);		//バックバッファ初期化
		DdTextOut(lpBack,0,0,"↑、↓、→、←で移動 pauseキーで終了",255);
		
		angle=-1;	//とりあえず角度を-1にしておく

		//[38]↑	[40]↓	[37]←	[39]→
		if(keyg[38] && keyg[39])	//右上
			angle=45;
		else
		if(keyg[37] && keyg[38])	//左上
			angle=135;
		else
		if(keyg[37] && keyg[40])	//左下
			angle=225;
		else
		if(keyg[40] && keyg[39])	//右下
			angle=315;
		else
		if(keyg[38])	//↑
			angle=90;
		else
		if(keyg[40])	//↓
			angle=270;
		else
		if(keyg[37])	//←
			angle=180;
		else
		if(keyg[39])	//→
			angle=0;

		//angleの値が変わっていたらキャラクタの座標を変更する
		if(angle!=-1){
			cx+=fcos[angle]*mv;
			cy-=fsin[angle]*mv;
		}

		//キャラクタ表示
		BltClip(lpBack,(int)cx,(int)cy,32,32,lpWork,0,0,1);
		Flip();						//フィリップ
		Wait(1000/60);				//メッセージループへ
	}

まず1つ角度を表す変数を用意しておき(angle)、それをまず-1に設定しておきます。
そしてキー判定によって、そのキーが示す角度へangleを変更します。
もしangleが最初に設定した-1とは違う値だったら、キーが押されていた事になり、そのangleが示す方向へ移動させてやれば良いというわけです。
ただ、気を付けないといけないのが、キー判定の順番です。
斜め移動のキー判定を先にやってからでないと、いけません。
実際にいろいろいじってみれば何故かが解ると思います。

サンプルコードのダウンロード

では、まずサンプルコードをダウンロードし、解凍してください。
私はVisual C++6.0でコンパイルしているので、お持ちの方はVisual C++でプロジェクトファイルを開いてください(「game_04.dsw」をダブルクリックすれば開けます)。
圧縮ファイルに含まれる「game_04.exe」をダブルクリックし、実行してみてください。
どうでしょう?画面が切り替わり、フルスクリーン化すると思います。
そして、カーソルの左右で角度を変更でき、上でその角度の方向へキャラクタが移動すると思います。

三角関数のお勉強 ステップ1


さて、三角関数とはなんぞや?という人のためにササっと解説してしまいます。
(高校2までに習うとは思いますが・・・)

左の図形を見てください。こういった直角三角形がある場合、

① sinΘ=b/c

② cosΘ=a/c

③ tanΘ=b/a

といった定義が存在します。ちなみに、①から順番に、サイン・コサイン・タンジェントと読みます。

ここでよくある(?)問題

A君は、とある木から10m離れた所で、地面に突っ伏し、そこから
丁度45度の角度でその木の一番高い所を見ていました。
さて、この木の高さは何mでしょう。

まぁ、この場合↑の図形で言う所のbが高さ、aが10m(木とA君の距離)ですね。
で、bを求めたくて、解っているのがaと45度という角度なので③の「tanΘ=b/a」を使って解いてやればいいですね?

tan45°=b/10

b=tan45°×10

b=1×10

答え 10m

となります。
まぁ「tan45°」のように
解りやすい角度なら頭の中でも計算できますが「tan2°」だと、直ぐ答えれる人はなかなかいないでしょう(たぶん。
幸い、C言語にはあらかじめ、sin,cos,tanの関数が用意されているので、値を簡単に出せます。

三角関数のお勉強 ステップ2


さて、基本が解ったところで(?)左のような半径1の円上にある点Pの座標(a,b)を表わすにはどうすればいいでしょう(かなり強引)。

こいつらも三角関数を使用してやります。

まず、点pのX座標がa、原点から点pまでの長さが1(半径1より)という事で

cosΘ=a/1

a=cosΘ

となります。

同様に、点pのY座標がb、原点から点pまでの長さが1という事で

sinΘ=b/1

b=sinΘ

となります。よって、半径1の円上にある点Pの座標はP(cosΘ、sinΘ)と表わす事が出来るわけです。

三角関数のお勉強 ステップ3

さて、ここで問題(またかよー)。点P(cx,cy)がΘ°の方向へ1進んだ時の座標はどう表されるでしょう?

もうお分かりですね?半径1の円上にある点Pの座標はP(cosΘ、sinΘ)という事から、

P( cx+cosΘ , cy+sinΘ )

となりますね。また、進みたい距離が1ではなく t だとしたら

P( cx+cosΘ×t , cy+sinΘ×t )

という事になります。

C言語で三角関数を使用する方法

ここまでで、三角関数の基本はバッチリ身に付いたと思います(そんなことないってか?)。
C言語で三角関数を使用するには、まず「math.h」をインクルードしてやります(まぁ当たり前ですね)。

それでは「三角関数のお勉強ステップ3」をそのままC言語で表してみましょうか。

キャラクターの座標が cx , cy の時、 angle 度の方向へ t 進みたい場合、

cx+=cos(angle)*t;
cy+=sin(angle)*t;

と、なるはずですが、パソコンのディスプレイ上の y 座標は、下に行く程増加していく、つまり数学上の座標とはy軸の向きが反対になります。
ですから、

cx+=cos(angle)*t;
cy-=sin(angle)*t;

でOK・・・となるはずですが、まだです(爆

C言語の cos , sin 等の引数はラジアンという単位で指定しなければなりません。
まぁラジアンについては私もよく知らないので数学の先生に聞いてください。
とにかく180°はラジアンでπと表わされるので、
1°はπ/180ですよね?
よって

cx+=cos(angle*3.1415926535/180)*t;
cy-=sin(angle*3.1415926535/180)*t;

これで、一応動作します。

ちょっとした高速化のお話

さて、C言語で毎回 cos() , sin() とかを呼び出してごちゃごちゃ計算させるのは処理の無駄であり、
ゲームの速度を落とす原因となりかねません。
ですから、ちょっと工夫して高速化させてやります。

と言っても、ただあらかじめテーブル作成しておくだけなんですけどね。

	//サイン、コサインテーブル作成
	float fsin[360],fcos[360];

	int i;
	for(i=0;i<360;i++){
		fsin[i]=(float)sin(i*3.1415926535/180);
		fcos[i]=(float)cos(i*3.1415926535/180);
	}	

こいつらを作成してやれば

cx+=cos(angle*3.1415926535/180)*t;
cy-=sin(angle*3.1415926535/180)*t;

cx+=fcos[angle]*t;
cy-=fsin[angle]*t;

で済みます。
ただ、角度angleは0~359の範囲でないといけないし、
0.5といった少数で指定する事も出来なくなります。
ですが、シューティングゲームのように、速度重視且つ、 sin , cos で精度にあまりとらわれなくても良いような場合はテーブルでやった方が良いですね。

サンプルコードの解説

では、サンプルコードを見てみましょう。

int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdParam, int nCmdShow){

	<略>

	//    cx:座標 cy:座標 mv:移動量   
	float cx=320.0f-16,cy=240.0f-16,mv=3.0f;
	
	// angle:向いている角度
	int angle=0;

	//サイン、コサインテーブル作成
	float fsin[360],fcos[360];

	int i;
	for(i=0;i<360;i++){
		fsin[i]=(float)sin(i*3.1415926535/180);
		fcos[i]=(float)cos(i*3.1415926535/180);
	}

	//メインループ
	while(1){
		WaitSet();					//現在の時間(単位:ミリ秒)取得
		ClearScreen(lpBack);		//バックバッファ初期化
		DdTextOut(lpBack,0,0,"←、→で旋回 ↑で進む pauseキーで終了",255);
		sprintf(tmp,"現在向いている角度 %d",angle);
		DdTextOut(lpBack,0,16,tmp,255);

		//旋回
		if(keyg[37]){	//←
			angle+=5;
			//cos,sinは配列を使用しているので角度が0~359になるように調整する必要がある
			if(angle>=360)
				angle-=360;
			//angle%=360;と書ける
		}
		if(keyg[39]){	//→
			angle-=5;
			
			if(angle<0)
				angle+=360;
			//angle=(angle+360)%360;と書ける(たぶん
		}

		//向いている角度へ進む
		if(keyg[38]){	//↑
			cx+=fcos[angle]*mv;
			cy-=fsin[angle]*mv;
		}
		
		//キャラクタ表示
		BltClip(lpBack,(int)cx,(int)cy,32,32,lpWork,0,0,1);
		Flip();						//フィリップ
		Wait(1000/60);				//メッセージループへ
	}	

キャラクタの座標(cx,cy)はfloat型で宣言します
(「int a=0; a+=0.5」とやってもaの値は0のままというのは当たり前ですからね)。
んー、まぁ後は大体上の方で説明したので解ります。

サンプルコードのダウンロード

では、まずサンプルコードをダウンロードし、解凍してください。
私はVisual C++6.0でコンパイルしているので、お持ちの方はVisual C++でプロジェクトファイルを開いてください(「game_03.dsw」をダブルクリックすれば開けます)。
圧縮ファイルに含まれる「game_03.exe」をダブルクリックし、実行してみてください。
どうでしょう?画面が切り替わり、スライムが2個表示されると思います。
というわけで今回は、キー入力により複数のキャラを動かす処理ついて解説したいと思います。

複数キャラの扱い

今回は、こいつら(slime.bmp 64×32)を使用して複数のキャラクタを表示、移動させてみたいと思います。

というわけでサンプルコードを見てみます。

//----------[ メイン関数 ]----------------------------------------------------------------------
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdParam, int nCmdShow){

	<略>

	//座標
	int cx[2]={160-16,480-16},cy[2]={240-16,240-16};
	
	//キーテーブル
	unsigned char key_up[2]={38,104},key_down[2]={40,98},
		key_left[2]={37,100},key_right[2]={39,102};
	
	//その他の変数
	int i,mv=3;

	//メインループ
	while(1){
		WaitSet();			//現在の時間(単位:ミリ秒)取得
		ClearScreen(lpBack);		//バックバッファ初期化
		DdTextOut(lpBack,0,0,"カーソルキー、テンキー(4,6,8,2)で移動 pauseキーで終了",255);

		for(i=0;i<2;i++){
			//キャラクター移動
			if(keyg[key_up[i]])	//↑
				cy[i]-=mv;
			else
			if(keyg[key_down[i]])	//↓
				cy[i]+=mv;
			else
			if(keyg[key_left[i]])	//←
				cx[i]-=mv;
			else
			if(keyg[key_right[i]])	//→
				cx[i]+=mv;
			
			//キャラクタ表示
			BltClip(lpBack,cx[i],cy[i],32,32,lpWork,32*i,0,1);
		}
		
		Flip();				//フィリップ
		Wait(1000/60);			//メッセージループへ
	}

初心者だと、つい1キャラづつ変数用意(例えば int,cx1,cx2,cx3・・・)し、処理も1キャラづつ用意しそうですが、そんな必要はありません。

座標等の変数を配列(もしくはクラス・構造体の配列)で用意する事により、ループ処理で複数のキャラを処理できるというわけです。

キーボードのお話

今回は2つのキャラクターを同時に表示し、キー入力も4方向×2キャラという事で8つのキーを扱っているのですが、AT互換機(DOS/V)でキーボードを一気にたくさん押すと、一部のキーの反応が無くなる場合があります(PC-9821シリーズでは大丈夫なようですが)。
スペースソルジャーでも、2人で同時に遊んでいると、どちらかの機体が左上に行けなくなるという現象が起きました。
これはハードウェアの仕様という事でプログラマーにはどうする事も出来ないようです。
そういった場合にはジョイパッドに逃げるしかありませんね(^^;

サンプルコードのダウンロード

では、まずサンプルコードをダウンロードし、解凍してください。
私はVisual C++6.0でコンパイルしているので、お持ちの方はVisual C++でプロジェクトファイルを開いてください(「game_02.dsw」をダブルクリックすれば開けます)。
圧縮ファイルに含まれる「game_02.exe」をダブルクリックし、実行してみてください。
どうでしょう?画面が切り替わり、フルスクリーン化すると思います。
そして、カーソルキーを押すと、スライム(?)が移動すると思います。
というわけで今回は、キー入力によるキャラクタ移動について解説したいと思います。

キャラクタの表示

では、まずキー入力の前に、キャラクターの表示について簡単に説明します。
まず、キャラクター(slime.bmp 32×32)を作業用バッファ(lpWork)に読み込んでおきます。
画面に表示するだけなら、作業用バッファからプライマリサーフェイスへBltFastとかで転送すれば、それで終わりですが、今回は、キャラクタの座標が変わったら、新しい座標へ移動するようなプログラムを組まなければなりません。
単に毎回キャラクタの座標へキャラクタを表示し続けるだけでは、元の座標に描かれていた絵がそのまま残ってしまいますよね?
そこで、多少工夫(?)し、残骸が残らないようにしてやる必要があります。

今回のサンプルでは、もっとも簡単な方法でそれらを実現しています。
ただ、バックバッファを全部消し、キャラを表示、フリップするだけ。
これなら、毎回バックバッファを全消去しているため、キャラの残骸が残らないと言うわけです。

見れば解りますね(^^;説明するまでもないと思いましたが・・・。

int cx,cy;//キャラクタのx,y座標

略
	ClearScreen(lpBack);
	BltClip(lpBack,cx,cy,32,32,lpWork,0,0,1);
	Flip();

やってはいけない?

さて、キー入力があった場合にウィンドウプロシージャが呼ばれることは前回解ったと思います。
で、実際にキー入力があった場合にキャラクタを移動させるようなプログラムを組むにはどうすればいいでしょう?
まず、初心者がよく考えそうな事は、キャラクタの座標をグローバル変数で用意し、ウィンドウプロシージャが呼ばれて、特定のキーが押されたら、そこで座標を変更するというもの。

実際に組むと、たぶんこんな感じ。
(キーコードについては前回のプログラムを実行して調べてください(爆))

int cx,cy;//キャラクタのx,y座標

略

//----------[ ウィンドウプロシージャ ]----------------------------------------------------------
HRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam){
	
	switch(iMessage){

	略

	//キー押された
	case WM_KEYDOWN:
		if(wParam==37)	//「←」キー
			cx-=5;
		else
		if(wParam==39)	//「→」キー
			cx+=5;
		・
		・
		・
		ClearScreen(lpBack);
		BltClip(lpBack,cx,cy,32,32,lpWork,0,0,1);
		Flip();

	return(TRUE);

この方法でも、キャラクタは動くには動きます。
さて、テキストで文字を打つときの事を思い浮かべてください。
‘A’キーをずっと押しっぱなしにしていると、’A’と一度表示され、しばらくしてから連続的に’A’が表示されると思います。
ウィンドウプロシージャもこのタイミングで呼ばれるため、キャラクタはカクカク動作する事になり、非常にカッコ悪いし、ゲームにも使えません。

仮想キーマップのお話

というわけで、↑の問題を解決すべく、仮想キーマップなるものを作成します。
まぁ、これは私が勝手に考えたものなので正しい方法とは言い切れません(爆。

では、プログラムを見てみましょう。


/*  グローバル変数群   */
char keyg[256];	//仮想キーマップ

略

//----------[ ウィンドウプロシージャ ]----------------------------------------------------------
HRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam){

	略

	//キー押された
    	case WM_KEYDOWN:
		//pauseキー(キーコード19)が押されたら終了
		if(wParam==19)
			Quit();
		keyg[wParam]=1;
		return(TRUE);

	//キー離された
	case WM_KEYUP:
		keyg[wParam]=0;
		return(TRUE);

では、まず256個の配列を確保します(keyg)。
キーが押され「WM_KEYDOWN」が送られてきたらkeyg[wParam]に1を代入します(wParamは、キーコードを表します)。
で、キーが離され「WM_KEYUP」が送られてきたらkeyg[wParam]に0を代入します。
これだけです(^^;これで、ウィンドウプロシージャ以外の関数内でもkeyg[キーコード]を参照する事によって
そのキーが押されている(値が1)のか、押されていないか(値が0)を判断出来ると言うわけです。

同期のお話

さて、いつでもキーの状態を取得出来るようになったので、実際にちょっと組んでみましょうか。
こんな感じですかねぇ。


//----------[ メイン関数 ]----------------------------------------------------------------------
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdParam, int nCmdShow){

	MSG msg;
	
	<略>

	int cx=320-16,cy=240-16,mv=3;
	//メインループ
	while(1){
		ClearScreen(lpBack);		//バックバッファ初期化
		DdTextOut(lpBack,0,0,"カーソルキーで移動 pauseキーで終了",255);

		//キャラクター移動
		if(keyg[38])	//↑
			cy-=mv;
		else
		if(keyg[40])	//↓
			cy+=mv;
		else
		if(keyg[37])	//←
			cx-=mv;
		else
		if(keyg[39])	//→
			cx+=mv;
		
		//キャラクタ表示
		BltClip(lpBack,cx,cy,32,32,lpWork,0,0,1);
		Flip();						//フィリップ
		
		//メッセージループ
		while(PeekMessage(&msg,NULL,0,0,PM_NOREMOVE)){
			if(!GetMessage(&msg,NULL,0,0))
				Quit();
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
	}

このプログラムには欠点があります。
それは、マシンの処理速度(描画速度)が速ければ速い程、キャラクタの移動速度も
上がってしまうという事です。つまり、処理速度の違うパソコンによってゲーム速度が変わって
しまうというのです(まぁフリップである程度抑えられますが・・・)。

というわけで、疑似タイマー処理(?)を紹介します。以前「DirectDraw基礎 第5回」でも書いたのですが、もうちょっと変更してみます(^^;

プログラムはこんな感じです(今回のサンプルコードと同じ)

/*  グローバル変数群 */
DWORD wait_time;	//ウェイト用

略

//----------[ ウェイト開始時間設定(謎 ]--------------------------------------------------------
void WaitSet(void){
	wait_time=timeGetTime();
}
//----------[ ウェイト ]------------------------------------------------------------------------
void Wait(DWORD msec){
	MSG msg;
	do{
		while(PeekMessage(&msg,NULL,0,0,PM_NOREMOVE)){
			if(!GetMessage(&msg,NULL,0,0))
				Quit();
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
	}while(timeGetTime()<wait_time+msec);
}
//----------[ メイン関数 ]----------------------------------------------------------------------
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdParam, int nCmdShow){

	
	<略>

	int cx=320-16,cy=240-16,mv=3;
	//メインループ
	while(1){
		WaitSet();			//現在の時間(単位:ミリ秒)取得
		ClearScreen(lpBack);		//バックバッファ初期化
		DdTextOut(lpBack,0,0,"カーソルキーで移動 pauseキーで終了",255);

		//キャラクター移動
		if(keyg[38])	//↑
			cy-=mv;
		else
		if(keyg[40])	//↓
			cy+=mv;
		else
		if(keyg[37])	//←
			cx-=mv;
		else
		if(keyg[39])	//→
			cx+=mv;
		
		//キャラクタ表示
		BltClip(lpBack,cx,cy,32,32,lpWork,0,0,1);
		Flip();					//フィリップ
		Wait(1000/60);				//メッセージループへ
	}

さて、まずメインループの最初でWaitSet()関数を呼び出しています。
これは自作関数なのでコードを見てもらえば解ると思いますが、グローバル変数wait_timeに、timeGetTime()関数を呼び出し、現在の時間をミリ秒(1秒=1000ミリ秒)で保存しておきます。
その後、メインループでいろいろな処理をし、最後にWait()関数が呼ばれています。
これも自作関数です。
このWait()関数の内容は主にメッセージループなのですが、現在の時間(timeGetTime())が、最初に記憶しておいたwait_time+Wait()関数の引数msecよりも小さい間はひたすたメッセージループを回しています。
つまり、WaitSet()を呼び出した時間から、Wait()の引数msecミリ秒時間が経つまでWait()関数内で止まっている事になります。

Wait()の引数を1000にすれば、秒間1回更新、500で2回更新・・・。
1000/nでn回更新となります。
ただ、この方法にも欠点があるのですが、それはWaitSet()を呼び出してから、いろいろな処理をし、Wait(DWORD msec);を呼ぶ場合、「いろいろな処理」の時間がWaitの引数「msec」より大きくなってしまうと、
Wait()関数内は、ほぼ素通り(1回はメッセージループの処理をやります)になり、いわゆる「もたつき」だとか「処理落ち」と言った現象になります。
普通、ゲームでは秒間30回更新(1ループ33msec)を目安にしたほうがいいですね。
これより小さいと、動作がカクカクに見えてしまうし、大きいと、処理落ちする可能性が大きくなります。
まぁ場合によりますので、いろいろ試してみてください(^^;

同期のお話 オマケ

さて、上では疑似タイマー処理なるものを紹介しましたが、
タイマー系の処理の種類はいろいろあります。
今回紹介したものは、毎回処理される時間を一定時間に合わせていますね?
この考え方は、よく固定フレームとか言います。
固定フレームでは、プログラムが簡単になる分、「もたつき」が起こりうる可能性が出てしまいます。
また、例えば秒間30回更新(30fps)に設定した場合、とても高性能なマシンで動かしても30FPSです。
ちょっとCPUがもったいない気もしますよね?
まぁ、60FPS設定にしておいて、処理がおいつかない場合は、描画をスキップするなんて事もよくやるようですが・・・。

ちなみに固定フレームに対するものは、変動フレームです。
これは、前回から今回のループに掛かった時間を計測しておき、
「距離=時間×速度」と言ったような式を用いてゲームを進行させる方法です。
この方法では、プログラムが複雑になったり、浮動小数点を使用する必要があったりしますが、マシンが高性能であればある程、ゲームが滑らかになります。

固定か変動かはケースバイケースなので、ゲームに合った方を使用してください。

記事検索

コミュニティ

Banner designed by よし様
ドット絵掲示板
ドット絵掲示板
自作の「ドット絵」を投稿することができます。ドッターさん同士の交流の場としてご利用ください。
TakaboSoft Wiki
TakaboSoft Wiki
ソフトウェアに関する質問・不具合報告・要望などを書き込むことができます。