ゲーム制作講座1-8

8.敵が弾を撃つようにしよう


 前回は敵が弾にあたった時にリアクションをするようにしました。
 今回は敵(四角君)もボール君に攻撃してくるようにしたいと思います。

 敵が弾を撃つと言うことは、敵の弾のデータが新たに必要になります。
必要なデータは弾の位置、そして弾が現在飛んでいるかどうかを保持する
フラグ変数
です。実はこれは、ボール君が撃ち出す弾に必要なデータと同じです。
 ただボール君と違うのは、弾が撃ち出されるタイミングです。
 ボール君が弾を撃ち出す時はプレーヤーがショットボタンであるスペース
キーを押した時でした。ですが四角君はプログラムが動かしているので
スペースキーが押された時、などのトリガーがありません。弾を撃ち出す
タイミングもプログラムで処理してあげなければならないのです。

 そこでどんな動作をさせるか決めてみます。
 とりあえず今回は簡単なところで60フレームに1回弾を撃ち出すことに
しましょう。
 60フレームに一回弾を撃ち出すということは、逆を言えば60フレームに
一回以外は弾を撃ち出してはいけない
ということです。つまり、何らかの
方法で60に一回のためのカウントをしなくてはなりません。これにはカウントする
ために新たに変数を設けてやることで解決できます。
 新たに追加するこのカウント用の変数にまずを代入し、毎フレームづつ
足します。そして丁度60になったときに弾を一発撃ち出します。打ち出す処理
を終えたあとは、を代入し、再び毎フレームづつ足して60になるまで
待ちます、これを繰り返すことで60フレームに回弾を撃ち出すという処理を
実現できるのです。

 int ETamaCounter という変数が話題のカウント用変数として、以下に例を示します。

// 最初に0を代入 ETamaCounter = 0 ; // ゲームループ while(1) { ... ..... ....... ..... // 四角君の処理 { .... ..... .... ...... ... // 60フレームを計測するカウンターに1を足す ETamaCounter ++ ; // カウンターが60になっていたら弾を撃つ処理を行う if( ETamaCounter == 60 ) { // 弾を打ち出す処理 ..... ... ..... .... // カウンターに0を代入して、最初からカウントしなおす ETamaCounter = 0 ; } } ... .... .... ..... }

 次に弾を打ち出す処理です。  弾を打ち出す処理というのは、具体的には『今弾が飛んでいるか』という
情報を保持する変数に『飛んでいる』を表す値を代入し、飛び始めの
座標を設定してやることで完了します。
 ただし、発射の処理を行う前に既に状態が『飛んでいる』になっている場合は
何もしません。(『飛んでいる』状態でも発射処理を行うとその時画面上に飛んでいる
弾が突如消えうせることになってしまいます。)

 int ETamaX , ETamaY を弾の座標、int ETamaFlag を飛んでいるかどうかを
保持する変数(代入されている値がの場合は『飛んでいないの場合は
飛んでいる』とします)だとして以下に例を示します。

// 弾が既に飛んでいるかどうかを調べ、飛んでいない場合のみ発射の処理を行う if( ETamaFlag == 0 ) { // 弾の発射位置を設定する ETamaX = SikakuX + SikakuW / 2 - ETamaW / 2 ; ETamaY = SikakuY + SikakuH / 2 - ETamaH / 2 ; // 弾の状態を保持する変数に『飛んでいる』を示す1を代入する ETamaFlag = 1 ; } (int ETamaW:敵の弾の横幅 int ETamaH:敵の弾の縦幅)

 そして、弾を飛ばす処理では、まず弾の状態が『飛んでいる』を示す
代入されているかを調べ、『飛んでいる』状態の時のみ、弾の座標を少し
下に移動させ、画面に描画します。そして、もし画面下端からはみ出て画面
上から弾がなくなってしまった場合は弾を『飛んでいない』状態にします。

// 『飛んでいる』状態でない場合は弾の移動処理は行わない if( ETamaFlag == 1 ) { // 少し下にずらす ETamaY += 8 ; // もし弾が画面下端からはみ出てしまった場合は弾の状態を『飛んでいない』 // を表す0にする if( ETamaY > 480 ) ETamaFlag = 0 ; // 画面に描画する( ETamaGraph : 敵の弾のグラフィックのハンドル ) DrawGraph( ETamaX , ETamaY , ETamaGraph , FALSE ) ; }
 こうして見ていくと、追加するデータは以下のものになります。

int ETamaX , ETamaY : 敵の弾の位置
int ETamaFlag : 敵の弾が飛んでいるかを保持する変数(0:飛んでいない 1:飛んでいる)
int ETamaGraph : 敵の弾のグラフィック
int ETamaW , ETamaH : 敵の弾の幅と高さ
int ETamaCounter : 敵が弾を撃つタイミングを計測するためのカウンター


 これを参考に、敵が弾を飛ばすように修正したプログラムが以下のものに
なります。
#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 SikakuDamageFlag , SikakuDamageCounter , SikakuDamageGraph ; int ShotX[SHOT] , ShotY[SHOT] , ShotFlag[SHOT] , ShotGraph ; int SikakuW , SikakuH , ShotW , ShotH ; int ShotBFlag ; int i ; int ETamaX , ETamaY , ETamaFlag ; int ETamaW , ETamaH , ETamaGraph ; int ETamaCounter ; // 画面モードの設定 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 ; // 四角君のダメージ時のグラフィックをメモリにロード SikakuDamageGraph = LoadGraph( "SikakuDam.png" ) ; // 四角君が顔を歪めているかどうかの変数に『歪めていない』を表す0を代入 SikakuDamageFlag = 0 ; // 敵の弾のグラフィックをロード ETamaGraph = LoadGraph( "EShot.png" ) ; // 敵の弾のグラフィックのサイズを得る GetGraphSize( ETamaGraph , &ETamaW , &ETamaH ) ; // 敵の弾が飛んでいるかどうかを保持する変数に『飛んでいない』を表す0を代入 ETamaFlag = 0 ; // 敵が弾を撃つタイミングを取るための計測用変数に0を代入 ETamaCounter = 0 ; // 弾のグラフィックをメモリにロード ShotGraph = LoadGraph( "Shot.png" ) ; // 弾が画面上に存在しているか保持する変数に『存在していない』を意味する0を代入しておく for( i = 0 ; i < SHOT ; i ++ ) { ShotFlag[i] = 0 ; } // ショットボタンが前のフレームで押されたかどうかを保存する変数に0(押されいない)を代入 ShotBFlag = 0 ; // 四角君の移動方向をセット SikakuMuki = 1 ; // 弾のグラフィックのサイズをえる GetGraphSize( ShotGraph , &ShotW , &ShotH ) ; // 四角君のグラフィックのサイズを得る GetGraphSize( SikakuGraph , &SikakuW , &SikakuH ) ; // 移動ルーチン 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( SikakuDamageFlag == 1 ) { // 顔を歪めている場合はダメージ時のグラフィックを描画する DrawGraph( SikakuX , SikakuY , SikakuDamageGraph , FALSE ) ; // 顔を歪めている時間を測るカウンターに1を加算する SikakuDamageCounter ++ ; // もし顔を歪め初めて 30 フレーム経過していたら顔の歪んだ状態から // 元に戻してあげる if( SikakuDamageCounter == 30 ) { // 『歪んでいない』を表す0を代入 SikakuDamageFlag = 0 ; } } else { // 歪んでいない場合は今まで通りの処理 // 四角君の座標を移動している方向に移動する 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 ) ; // 弾を撃つタイミングを計測するためのカウンターに1を足す ETamaCounter ++ ; // もしカウンター変数が60だった場合は弾を撃つ処理を行う if( ETamaCounter == 60 ) { // もし既に弾が『飛んでいない』状態だった場合のみ発射処理を行う if( ETamaFlag == 0 ) { // 弾の発射位置を設定する ETamaX = SikakuX + SikakuW / 2 - ETamaW / 2 ; ETamaY = SikakuY + SikakuH / 2 - ETamaH / 2 ; // 弾の状態を保持する変数に『飛んでいる』を示す1を代入する ETamaFlag = 1 ; } // 弾を打つタイミングを計測するための変数に0を代入 ETamaCounter = 0 ; } } } // 敵の弾の状態が『飛んでいる』場合のみ弾の移動処理を行う if( ETamaFlag == 1 ) { // 少し下にずらす ETamaY += 8 ; // もし弾が画面下端からはみ出てしまった場合は弾の状態を『飛んでいない』 // を表す0にする if( ETamaY > 480 ) ETamaFlag = 0 ; // 画面に描画する( ETamaGraph : 敵の弾のグラフィックのハンドル ) DrawGraph( ETamaX , ETamaY , ETamaGraph , FALSE ) ; } // 弾と敵の当たり判定、弾の数だけ繰り返す for( i = 0 ; i < SHOT ; i ++ ) { // 弾iが存在している場合のみ次の処理に映る if( ShotFlag[ i ] == 1 ) { // 四角君との当たり判定 if( ( ( ShotX[i] > SikakuX && ShotX[i] < SikakuX + SikakuW ) || ( SikakuX > ShotX[i] && SikakuX < ShotX[i] + ShotW ) ) && ( ( ShotY[i] > SikakuY && ShotY[i] < SikakuY + SikakuH ) || ( SikakuY > ShotY[i] && SikakuY < ShotY[i] + ShotH ) ) ) { // 接触している場合は当たった弾の存在を消す ShotFlag[ i ] = 0 ; // 四角君の顔を歪めているかどうかを保持する変数に『歪めている』を表す1を代入 SikakuDamageFlag = 1 ; // 四角君の顔を歪めている時間を測るカウンタ変数に0を代入 SikakuDamageCounter = 0 ; } } } // 裏画面の内容を表画面にコピーする ScreenFlip() ; // Windows 特有の面倒な処理をDXライブラリにやらせる // -1 が返ってきたらループを抜ける if( ProcessMessage() < 0 ) break ; // もしESCキーが押されていたらループから抜ける if( CheckHitKey( KEY_INPUT_ESCAPE ) ) break ; } DxLib_End() ; // DXライブラリ使用の終了処理 return 0 ; // ソフトの終了 }

<実行図>

 敵の弾のグラフィック

 実行してみると、ボール君に当たり判定はありませんが、四角君が周期的に弾を
撃ってくると思います。

 さて、今回は殆ど今までの応用でした。
 次は敵がボール君に向かって弾を撃つようにしたいと思います。

戻る