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

では、まずサンプルコードをダウンロードし、解凍してください。
私はVisual C++6.0でコンパイルしているので、お持ちの方はVisual C++でプロジェクトファイルを開いてください(「game_11.dsw」をダブルクリックすれば開けます)。
圧縮ファイルに含まれる「game_11.exe」をダブルクリックし、実行してみてください。
どうでしょう?画面が切り替わり、フルスクリーン化すると思います。
そして、どこからか弾が沸き、自機(スライム)の方向へ飛んできますよね?

というわけで、今回は角度の算出とアニメーションについて説明していきたいと思います。

三角関数のお勉強 その1


第4回では、三角関数としてsin、cos、tanを説明しました。
しかし、これだけで、敵から、自機への角度を算出する事は出来ません。

そこで、今回はatan(アークタンジェント)というものを使用します。

普通のtanは

tanΘ=b/a

でしたが、アークタンジェントは

Θ=atan(b/a)

となります。要するに、タンジェントの逆算みたいな感じですね。

三角関数のお勉強 その2


というわけで、基本が解れば、あとは当てはめるだけですね。
左の図のように敵、自機が存在し、角度Θを求めたい場合、

Θ=atan((sy-my)/(sx-mx))

とすればいいですよね?

ただし、y/xが例えば1/1、-1/-1の場合、値は一緒ですよね?
という事で、atanの返値Θも半分の180度周期になってしまいます。(ちなみにsin,cosは360度周期,sin(0)=sin(360)=sin(360*n))

そこで、x,yの符号を利用して、ある条件の時はΘに180を足すと言った処理が必要になってきます。

また、xが0の場合、y/0=∞となってしまい、エラーとなってしまいます。
これも回避しなければなりません。

アークタンジェントをC言語で使用する

C言語でアークタンジェントを使用したい場合は、「math.h」をインクルードした後、atan( double v ) を呼び出せば出来ます。

ただ、上でも書いたように、返ってくる角度Θは180度周期だったり、xが0だった場合にエラーとなったりします。
で、いろいろ判定して正しい値を出そうとしがちですが、C言語にはatan2( double y, double x) という関数が用意されています。
この、yとxに値を引き渡してやると、360度周期で角度を返してくれます。しかも、xが0の時の処理もしっかりやってくれるので便利です。

ただ、atanもそうですが、atan2の返値の単位はラジアンです(0~360度=0~2π)。
atan2の場合は-π~+πまでが返ってくるので、atan2の返値をπで割れば、-1~+1までになりますよね?
そしてπは180°ですから、その値に180を掛けてやると、値は-180°~+180°になりますね?
これに、360°を足し、360で割った余りを出すと、0~359°の値が出てきます。

	double tmp;
	short angle;
	tmp=atan2(-double(sy-my),double(sx-mx));
	tmp/=3.1415926535;
	tmp*=180;
	tmp+=360;
	angle=(short)tmp%360;

こんな感じかな。

これで敵から自機への角度Θが出せますね?

サンプルコードの解説

今回のコードは、「第9回 キー入力によって弾を発射」を少し改良し、ランダムに弾を作成し、自機の方向へ飛ばすようにします。
弾の移動等、詳しい事は第9回を参照してください。

今回は、ついでに弾をアニメーションさせる事にするので、_bullet構造体にanimationという変数を
追加しておきます。

//弾用変数
struct _bullet{
	BOOL enable;	//使用:1  未使用:0
	float x,y;		//座標
	char animation;	//アニメーション
	short angle;	//進む角度
}bullet[BMAX];

では、メインループを見てみます。

//----------[ メインループ ]--------------------------------------------------------------------
void GameLoop(void){
	while(1){
		WaitSet();				//現在の時間(単位:ミリ秒)取得
		
		MovePlayer();				//自機移動

		Shoot();				//どこからともなく沸く弾作成

		MoveBullet();				//弾移動

		Show();					//画像表示
		Wait(1000/60);				//メッセージループへ
	}
}

第9回と見た目が同じですが、Shoot()とMoveBullet()、Show()の中身を変更しています。(他は全部同じ)

//----------[ 弾発射判定 ]----------------------------------------------------------------------
void Shoot(void){
	//1/100の確率で弾作成
	int x,y,angle;
	if(rand()%100==0){
		x=rand()%640;	//x,y座標はランダム
		y=rand()%480;
		angle=GetAngle(x+8,y+8,(int)player.x+16,(int)player.y+16);	//自機への角度取得
		CreateBullet((float)x,(float)y,angle);
	}
}

ここでは、1/100の確立で適当な位置を出し、自機への角度を取得して、弾を作成しています。
rand()という関数は0~RAND_MAXまでの乱数を返してくれるので、適当な値pで割った余りを出せば、0からpのランダム値が出せます(度数分布が崩れるので本当はあまりよろしくない)。

で、GetAngle(int mx,int my,int sx,int sy);関数は自作関数で、mx,myからsx,syまでの角度を算出します。
GetAngle(x+8,y+8,(int)player.x+16,(int)player.y+16);のx+8,y+8というように、ある値を足していますが、自機などの座標は、画像の左端を表わしているため、画像の中心を表わすためには、画像サイズの半分の値を足せばいいですよね?

//----------[ 角度取得 ]------------------------------------------------------------------------
short GetAngle(int mx,int my,int sx,int sy){
	return short(atan2(-double(sy-my),double(sx-mx))/3.141592*180+360)%360;
}

この自作関数は上の方で説明したので解りますね(^^;

で、弾をアニメーションさせるためには、弾移動中にanimationの値をどんどん増やしていきます。

//----------[ 弾移動 ]--------------------------------------------------------------------------
void MoveBullet(void){
	int i;
	float mv=6.0f;

	for(i=0;i<BMAX;i++){
		if(bullet[i].enable==0)	//未使用データだったら次の処理へ
			continue;

		//移動
		bullet[i].x+=fcos[bullet[i].angle]*mv;
		bullet[i].y-=fsin[bullet[i].angle]*mv;
		
		//アニメーション
		bullet[i].animation++;
		if(bullet[i].animation>=12)
			bullet[i].animation=0;
		//++bullet[i].animation%=12;  と書いても可

		//画面外に出たら未使用データにする
		if(bullet[i].x<-16 || bullet[i].x>640 || bullet[i].y<-16 || bullet[i].y>480)
			bullet[i].enable=0;
	}
}

今回使用する画像はです。弾のアニメーションは12パターンありますよね?
ですから、animationの値は0~11の間を繰り返し変化するようにプログラムしてやります。

で、後は、このanimationの値をうまく画像に照らし合わせて画面に表示してやればいいわけです。
1つの弾の画像サイズが16×16で左にスライムが32ドットあるので、弾画像のx座標は32+16×アニメーションという事になりますよね?

//----------[ 画像表示 ]------------------------------------------------------------------------
void Show(void){
	ClearScreen(lpBack);		//バックバッファ初期化
	DdTextOut(lpBack,0,0,"カーソルキーで移動 pauseキーで終了",255);

	int i;
	//弾表示
	for(i=0;i<BMAX;i++)
		if(bullet[i].enable==1)
			BltClip(lpBack,(int)bullet[i].x,(int)bullet[i].y,
				16,16,lpWork,32+16*bullet[i].animation,0,1);

	//自機転送
	BltClip(lpBack,(int)player.x,(int)player.y,32,32,lpWork,0,0,1);
	//フリップ
	Flip();
}

このアニメーションは、繰り返し行われる弾やら敵に限りますね。
自機のようなキー入力によって画像を変えたいときには使えません。

記事検索

アーカイブ