ソースコードはGitHubで公開しています。⇒ https://github.com/mi3w2a1/SimpleShooter

前回の簡単なシューティングゲーム 本当に鳩でも分かるC#講座では自機と敵の描画をしました。しかしこのままでは自機を移動させることができないし、弾丸を発射することもできません。そこで今回は自機の移動と弾丸の発射、当たり判定を考えます。

自機を移動させる

まず移動ですが、これはJikiクラスのMoveLeftやMoveRightをtrueにすることで自動的に移動できるようになります。

キーが押されたらOnKeyDownメソッドが離されたらOnKeyUpメソッドが呼び出されます。そのときに適切なフィールド変数をtrueやfalseにするだけです。

方向キーで移動しますが、弾丸を発射をする動作も実装することにします。Shotという自作メソッドをつくります。

弾丸を発射させる

ではShotメソッドはどうすればいいのでしょうか?

その前に描画すべき弾丸をつくる必要があります。リソースとして読み込んだPNGファイルの一部からBitmapを生成してBurretBitmapに格納します。

これでBitmapの取得は完了です。次に弾丸を移動させたり描画するためのBurretクラスを考えます。

弾丸の移動と描画のためのBurretクラス

コンストラクタでは使用するBitmap、初期のXY座標、移動量を指定します。移動量として0.5のような値を設定しても問題なく動作するようにX、Y、VX、VYプロパティはdouble型にします。描画するときにint型にキャストします。

弾丸は自機も敵も同じものを使います。本当に鳩でもわかる内容にしたいので、もうちょっと完成度が高いものを目指すのであれば、3Dっぽい縦シューティングゲームをつくる(タイトルの付け方がよろしくない!)を参照してください。

Moveメソッドで弾丸を移動させたあと画面の外に出ている場合は、描画する必要はないのでIsDeadプロパティをtrueにして描画対象から外しています。

Shotメソッドで弾丸を発射

弾丸が表示できるようになったらShotメソッドで弾丸を発射できるようにします。Burretのリストをつくってこのなかにオブジェクトを格納します。弾丸の出現位置はY座標は自機と同じでいいのですが、X座標は自機の中心にならないと不自然です。自機のX座標とJikiBitmapとBurretBitmapの幅から算出しています。それから1発だけではシューティングゲームらしくないので3発を扇形に発射させます。

それから敵も当然弾丸を発射してきます。なので敵の弾丸を格納するリストもつくっておきます。

Timer_Tickメソッドにおける処理

Timer_Tickメソッドが呼び出されたときにいくつかやることがあります。

自機と自機から発射された弾丸、敵と敵から発射された弾丸の移動、新たに敵をつくるか、自機、敵、弾丸のそれぞれの当たり判定をしなければなりません。

Timer_Tickメソッドのなかがややこしくなるので以下のようにわけました。

まずは自機と敵の弾丸を移動させる処理を示します。

次に新たに敵をつくるメソッドを示します。

1秒間に4回の割合で乱数を発生させて新しく敵を生成するかを決めます。4分の3の確率で新しく敵をつくります。敵を生成する場合は3種類あるのでどれにするか、これも乱数で決めます。そして出現位置はY座標は一番上ですが、X座標はバラバラにしたほうがいいので、これも乱数で決めます。敵は左右に移動しながら下に移動しますが、最初に移動する方向が右か左かも乱数で決めています。

敵に弾丸を発射させる

敵に弾丸を発射させる処理を示します。弾丸を発射するのは敵一体につき、0.5秒に1回、3分の1の確率です。発射する場合、方向は自機のいる方向にむけて発射します。発射方向を求めるためにTanの逆関数アークタンジェントを使用しています。高校の数学ではTanはほとんど出てきません。いつもsinとcosだけです。だからといって油断していると突然出てくるのがTanです。

Y座標の差をX座標の差で割ったものをTanの逆関数に渡せば発射角度を求めることができます。あとはsinとcosで発射速度を求めてコンストラクタに渡します。

当たり判定

次に当たり判定を考えます。

当たり判定ですが、自機、敵、弾丸が重なっている場合は当たっていると判断します。ではそれらが重なっているかどうかはどうすればわかるでしょうか?

まずふたつの矩形(長方形)が重なっている場合について考えます。ただこの場合、ふたつの矩形が重なっていない場合を考えたほうがよさそうです。

矩形1の右側よりも矩形2の左が右にある場合はあきらかに両者は重なっていません。
同様に、矩形1の下側よりも矩形2の上が下にある場合も両者は重なっていないと断言できます。
矩形2の右側よりも矩形2の左が右にある場合、矩形2の下側よりも矩形1の上が下にある場合も両者は重なっていません。

それ以外であれば両者は重なっていると判断できます。そのためふたつの矩形が重なっているかどうかは以下の方法で判定できます。

では敵と自機が発射した弾丸が命中しているかどうかはどうでしょうか? 敵と弾丸の左上の座標はXプロパティとYプロパティをみればわかります。あとはそれぞれのBitmapの幅と高さがわかればこれを囲む矩形を取得できるので、上記のメソッドで判定できます。

同様に自機と敵の弾丸、自機と敵が接触したかどうかは以下の方法で判定できます。

そこで当たり判定をするHitJudgeメソッドは以下のように書くことができます。

IsHitメソッドが最初にtrueを返す要素を探します。ない場合はnullが返されます。null以外が返されたときは当たっているものが存在するということなので、それをリストのなかから取り除きます。ただしforeach文のなかで取り除くと例外が発生するので、その場合はIsDeadプロパティをtrueにして、ループを出たらIsDeadプロパティがtrueのものを取り除きます。

命中した弾丸だけでなく画面外に飛んでいった弾丸もリストのなかから取り除かないといけないので、ここではそのための処理もしています。

移動処理と当たり判定をしてリストから取り除かれなかったものを描画します。自機や敵の上に弾丸が描画されてはおかしいので弾丸を先に描画しています。Jiki.Dead == trueのときは自機は描画されません(現状、敵の弾丸や敵そのものと接触すると消えてしまう)。

これで弾丸が当たったら消滅するようになりました。ただちょっとさみしいです。爆発の描画もあればいいのですが・・・。次回に続きます。