ゲーム制作講座1−5

5.ショットボタンを押しっぱなしでも連射しないようにしよう


 前回は自機が沢山弾を撃てるようになりました、が、ショットボタンを押すと弾がだらだら
連射されてしまい、いわゆる自動連射状態になってしまっています。
 別に自動連射でかまわない場合はそれでもいいのですが、それにしてもショットが繋がり
過ぎで見苦しいです。とりあえずショットボタン1回につき1発だけ発射されるように
プログラムを修正したいと思います。

 さて、それに当たってまずはなぜ弾が連続的に発射されてしまうのか考えてみましょう。
 現時点ではショットボタンが押されていて、且つ弾の最大発射量を越えていなかった場合
新たに弾が発射されます。これでは押しっぱなしで弾がどんどん発射されても無理ありません。

 これを回避するにはどうすればいいのかといいますと、ずばり『一つ前のフレームでショット
ボタンが押されていたか調べる
』につきます。

 一つ前のフレームでショットボタンが押されていたら弾を発射しなければいいのです。そう
すればショットボタンが押され始めた時のフレームでしか弾は発射されなくなります。
 前のフレームでショットボタンが押されていたかどうかは変数に保存します。押されていたら
、押されていなかったらでも代入することにしましょう。

 とりあえずそんな感じで出切たのが下記のプログラムです。

 
#include "DxLib.h"

#define SHOT 20

// WinMain関数
int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,
					 LPSTR lpCmdLine, int nCmdShow )
{
	int BallX , BallY , BallGraph ;
	int SikakuX , SikakuY , SikakuMuki , SikakuGraph ;
	int ShotX[SHOT] , ShotY[SHOT] , ShotFlag[SHOT] , ShotGraph ;
	int ShotBFlag ;
	int i ;

	// 画面モードの設定
	SetGraphMode( 640 , 480 , 16 ) ;

	// DXライブラリ初期化処理
	if( DxLib_Init() == -1 ) return -1;

	// グラフィックの描画先を裏画面にセット
	SetDrawScreen( DX_SCREEN_BACK ) ;

	// ボール君のグラフィックをメモリにロード&表示座標をセット
	BallGraph = LoadGraph( "Ball.png" ) ;
	BallX = 288 ; BallY = 400 ;

	// 四角君のグラフィックをメモリにロード&表示座標をセット
	SikakuGraph = LoadGraph( "Sikaku.png" ) ;
	SikakuX = 0 ; SikakuY = 50 ;

	// 弾のグラフィックをメモリにロード
	ShotGraph = LoadGraph( "Shot.png" ) ;

	// 弾が画面上に存在しているか保持する変数に『存在していない』を意味する0を代入しておく
	for( i = 0 ; i < SHOT ; i ++ )
	{
		ShotFlag[i] = 0 ;
	}

	// ショットボタンが前のフレームで押されたかどうかを保存する変数に0(押されいない)を代入
	ShotBFlag = 0 ;

	// 四角君の移動方向をセット
	SikakuMuki = 1 ;

	// 移動ルーチン
	while( 1 )
	{
		// 画面を初期化(真っ黒にする)
		ClearDrawScreen() ;

		// ボール君の操作ルーチン
		{
			// ↑キーを押していたらボール君を上に移動させる
			if( CheckHitKey( KEY_INPUT_UP ) == 1 ) BallY -= 3 ;

			// ↓キーを押していたらボール君を下に移動させる
			if( CheckHitKey( KEY_INPUT_DOWN ) == 1 ) BallY += 3 ;

			// ←キーを押していたらボール君を左に移動させる
			if( CheckHitKey( KEY_INPUT_LEFT ) == 1 ) BallX -= 3 ;

			// →キーを押していたらボール君を右に移動させる
			if( CheckHitKey( KEY_INPUT_RIGHT ) == 1 ) BallX += 3 ;

			// スペースキーを押した場合は処理を分岐
			if( CheckHitKey( KEY_INPUT_SPACE ) )
			{
				// 前フレームでショットボタンを押したかが保存されている変数が0だったら弾を発射
				if( ShotBFlag == 0 )
				{
					// 画面上にでていない弾があるか、弾の数だけ繰り返して調べる
					for( i = 0 ; i < SHOT ; i ++ )
					{
						// 弾iが画面上にでていない場合はその弾を画面に出す
						if( ShotFlag[i] == 0 )
						{
							int Bw, Bh, Sw, Sh ;

							// ボール君と弾の画像のサイズを得る
							GetGraphSize( BallGraph , &Bw , &Bh ) ;
							GetGraphSize( ShotGraph , &Sw , &Sh ) ;

							// 弾iの位置をセット、位置はボール君の中心にする
							ShotX[i] = ( Bw - Sw ) / 2 + BallX ;
							ShotY[i] = ( Bh - Sh ) / 2 + BallY ;

							// 弾iは現時点を持って存在するので、存在状態を保持する変数に1を代入する
							ShotFlag[i] = 1 ;

							// 一つ弾を出したので弾を出すループから抜けます
							break ;
						}
					}
				}

				// 前フレームでショットボタンを押されていたかを保存する変数に1(おされていた)を代入
				ShotBFlag = 1 ;
			}
			else
			{
				// ショットボタンが押されていなかった場合は
				// 前フレームでショットボタンが押されていたかを保存する変数に0(おされていない)を代入
				ShotBFlag = 0 ;
			}

			// ボール君が画面左端からはみ出そうになっていたら画面内の座標に戻してあげる
			if( BallX < 0 ) BallX = 0 ;

			// ボール君が画面右端からはみ出そうになっていたら画面内の座標に戻してあげる
			if( BallX > 640 - 64 ) BallX = 640 - 64  ;

			// ボール君が画面上端からはみ出そうになっていたら画面内の座標に戻してあげる
			if( BallY < 0 ) BallY = 0 ;

			// ボール君が画面下端からはみ出そうになっていたら画面内の座標に戻してあげる
			if( BallY > 480 - 64 ) BallY = 480 - 64 ;

			// ボール君を描画
			DrawGraph( BallX , BallY , BallGraph , FALSE ) ;
		}

		// 弾の数だけ弾を動かす処理を繰り返す
		for( i = 0 ; i < SHOT ; i ++ )
		{
			// 自機の弾iの移動ルーチン( 存在状態を保持している変数の内容が1(存在する)の場合のみ行う )
			if( ShotFlag[ i ] == 1 )
			{
				// 弾iを16ドット上に移動させる
				ShotY[ i ] -= 16 ;

				// 画面外に出てしまった場合は存在状態を保持している変数に0(存在しない)を代入する
				if( ShotY[ i ] < -80 )
				{
					ShotFlag[ i ] = 0 ;
				}

				// 画面に弾iを描画する
				DrawGraph( ShotX[ i ] , ShotY[ i ] , ShotGraph , FALSE ) ;
			}
		}

		// 四角君の移動ルーチン
		{
			// 四角君の座標を移動している方向に移動する
			if( SikakuMuki == 1 ) SikakuX += 3 ;
			if( SikakuMuki == 0 ) SikakuX -= 3 ;

			// 四角君が画面右端からでそうになっていたら画面内の座標に戻してあげ、移動する方向も反転する
			if( SikakuX > 576 )
			{
				SikakuX = 576 ;
				SikakuMuki = 0 ;
			}

			// 四角君が画面左端からでそうになっていたら画面内の座標に戻してあげ、移動する方向も反転する
			if( SikakuX < 0 )
			{
				SikakuX = 0 ;
				SikakuMuki = 1 ;
			}

			// 四角君を描画
			DrawGraph( SikakuX , SikakuY , SikakuGraph , FALSE ) ;
		}

		// 裏画面の内容を表画面にコピーする
		ScreenFlip() ;


		// Windows 特有の面倒な処理をDXライブラリにやらせる
		// -1 が返ってきたらループを抜ける
		if( ProcessMessage() < 0 ) break ;

		// もしESCキーが押されていたらループから抜ける
		if( CheckHitKey( KEY_INPUT_ESCAPE ) ) break ;
	}

	DxLib_End() ;				// DXライブラリ使用の終了処理

	return 0 ;					// ソフトの終了
}

<実行図>

 実行してみるとショットボタン1回につき1発しか弾が発射されなくなったと思います。
 プログラムの変更は、1フレーム前にショットボタンが押されたかを保存するための
変数として ShotBFlag が追加されています。この変数は、毎フレーム実際弾が発射されたか
どうかを問わずに
常にショットボタン(スペースキー)が押されていた場合は1を、押されて
いなかった場合は0
を代入しています。
 あとはショットボタンが押されたときに ShotBFlag 変数の内容が0だった場合のみ
弾の発射処理を行えばいい
のです。

 さて、今回は短いですがだらだら弾が連射しなくなるようになりました。
 次は敵に弾が当たるようにしたいと思います。

戻る