簡単なシューティングゲームをつくります。ただし本当に鳩でも分かる内容にしたいので完成度はそんなに高くありません。この動画のようなものをつくります。

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

自機は左右にしか移動できず、ランダムに上方から下りてくる敵を撃退するだけの単純なゲームです。

まず自機と敵機を画像として描画します。そのためには画像を描画するテクニックを習得する必要があります。

画像をリソースに追加する

まずメニューのプロジェクト ⇒ WinFormsAppXのプロパティを選択します。そして[リソース]と書かれている部分をクリックします。すると「規定のリソースファイルが追加されていません」と表示されます。

このメッセージを読み進めてみると「ファイルを作成するにはここをクリック」と書かれているので、これをクリックします。

すると上のような表示になります。ここへ追加したい画像をドラッグアンドドロップしましょう。追加したい画像はこれを使います(ファイル名 sprite2.png)。

あとはこのなかから使いたい画像だけを切り取って使うことになります。

まずさきほどドラッグアンドドロップした画像のBitmapを取得するには

これでOKです。ここから必要な部分を取得するのですが、必要なのは上の段にある青い戦闘機、敵キャラとして3種類のひよこ(やさぐれひよこ)、弾丸として戦闘機の下にある丸いものを使えばよいと思います。

まず画像のなかから切り取りたい部分を決めます。

戦闘機であれば左上の座標は(57,1)、サイズは幅44、高さ48です。
黄色いひよこであれば左上の座標は(4,61)、サイズは幅24、高さ28です。
ピンク色のひよこであれば左上の座標は(4,94)、上と同じです。
一番悪そうな青いひよこであれば左上の座標は(4,125)、上と同じです。

では元になる画像(sprite2.png)から必要な部分だけ取り出すにはどうすればいいのでしょうか?

まずsourceRectangeを作成します。コンストラクタの引数は切り取りたい部分の左上の座標とサイズです。次に切り取ったものをサイズをどうするか、幅と高さを指定します。変更しない場合も指定は必要です。ここではdestWidthとdestHeightという変数でこれを指定します。

取得したいBitmapのサイズは幅destWidth、高さdestHeightになるのでその大きさでBitmapを生成、Graphicsオブジェクトを生成してDrawImageメソッドで元のBitmapから出力先になるBitmapに書き込みます。最後にGraphicsオブジェクトを破棄します。

自機のBitmapを取得するのであれば以下のようになります。なんども取得すると時間がかかるのとメモリーが圧迫されるので、以下のメソッドは最初に1回だけ実行して、取得されたBitmapはフィールド変数に保存しておきます。

同じようにすれば敵キャラのBitmapも取得できます。

敵をつくる

敵を作ったら動かさないといけません。そこで生成した敵を格納するリストを作ります。作成した敵はとりあえずこのなかにいれます。

それから敵をつくるペースは1秒に1回です。今回は本当に鳩でもわかる内容にしたいのであまり凝ったことはしません。敵は一番上に現れて、左右に移動しながら下りてくるだけです。

まずタイマーをつくります。コンストラクタ内でTickイベントを1秒間に60回発生させる設定にしてタイマーをスタートさせます。Tickイベントが発生するとイベントハンドラ Timer_Tickが実行されます。

あと、ちらつきをおさえるためにDoubleBuffered = trueにしています。それからゲームらしくするために背景色を黒にしています。

Timer_Tickが実行されたらフィールド変数 TickCountをつかってTimer_Tickが何回実行されたかを数えます。そして60回に1回、つまり1秒ごとに1回、新しい敵をつくります。敵はEnemyクラス(後述)で管理します。Enemyクラス内にあるMoveメソッドで敵を動かします。そしてInvalidateメソッドを呼び出せば移動した敵を描画することができます。

敵を左右に移動させる場合、フォームの幅がわからないとどこで方向転換させればいいのかわかりません。そこでこれから作成するEnemyからでもフォームのサイズがわかるように静的フィールド変数 FormClientSizeを定義します。これでForm1.FormClientSizeとすればフォームのサイズがわかります。Form1のインスタンスはひとつしか作成しないので、これで問題はないはずです。

OnLoadはフォームが表示されたときに、OnResizeはサイズが変更されたときに実行されます。このときにフォームのサイズをFormClientSizeに格納することで、それ以外のクラスからでも Form1.FormClientSizeとすればフォームのサイズを取得することができるのです。

Enemyクラスを作成する

Enemyクラスを作成します。敵をつくるためには上記で作成したBitmapのどれを使うのか、最初に出現する座標はどこにするのかが必要です。そこでこれらはコンストラクタで渡すことにしました。Bitmapプロパティは敵のBitmap、XプロパティとYプロパティは敵が現在いる座標です。MoveRightプロパティは敵は右に動くかどうかです。

敵を動かすためにMoveメソッドを作成しました。敵はフォームの端までくると方向を転換します。このときフォームの幅がどうなっているかがわからないと処理ができません。

それから自機に攻撃された場合は消滅することになります。この場合、リストから取り除かないといけないので死亡フラグも必要です。それから自機に撃たれることなくフォームの下まで到達した場合も上から下にしか移動できない以上、描画されることはありません。これもリストから取り除かないといけないので死亡フラグをセットすることになります。

これは敵を描画するためのメソッドです。

自機を描画するためのクラスをつくる

自機を描画するためのJikiクラスを作成します。Bitmapは最初に作成したものを使います。自機が最初に出現する場所は中央のやや下寄りの場所です。最初は常に同じ場所に出現するため、Enemyクラスのようにコンストラクタに初期座標の引数をわたしていません。

Enemyクラスと同様に移動させるためのメソッドを作成します。前後左右のキーが押されていたら移動させます。画面の外に出てしまわないように移動する前に本当に移動できるのか確認しています。

作成したJikiクラスをForm1クラスに組み込むと以下のようになります。

これで自機と敵機が描画されるようになりました。しかしキーをおしても移動しません。また敵機を攻撃することもできません。キー操作にかんしてはまだ実装していないので当然といえば当然です。これは次回おこないます。