Visual Basic ゲーム講座 |
この記事が対象とする製品・バージョン (バージョンの確認方法)
Visual Basic 2010 | ◎ | 対象です。 | |
Visual Basic 2008 | ○ | 対象ですが一部画面が異なる場合があります。 | |
Visual Basic 2005 | ○ | 対象ですが一部画面が異なる場合があります。 | |
Visual Basic.NET 2003 | × | 対象外です。 | |
Visual Basic.NET (2002) | × | 対象外です。 | |
Visual Basic 6.0 | × | 対象外です。 |
概要 ・直進する敵キャラクターを出現させる。 ・敵キャラクターをプログラムするには画像と座標、生存状態の3つが最低限必要。 ・敵キャラクターを2体以上同時に出現させる方法 ・今回はあたり判定まではつけない。 |
今回は敵キャラクターを登場させます。主人公と敵が登場してあたったら撃破などの動作がプログラムできれば一応ゲームっぽくなってきます。
説明の順番としては、今回はは非効率な方法でのプログラムを紹介します。非効率ではありますが考え方は簡単です。いきなり効率的な方法を紹介すると何をやっているのか理解できない人がいるのではないかと心配してこのような回りくどい説明をします。効率的な方法は次回紹介します。
プログラムに自信がある方は今回は斜め読みして次回をじっくり読むこともできます。自身がない方は非効率な方法だからと言って斜め読みしないで、1ステップ1ステップ説明している内容を読みながら実際にやってみることをお勧めします。
次々とコードが登場しますが、プログラム全体の中でこのコードをどこに書くか、ファイルの配置はどうするかなどちゃんとしないと説明しているような動きにはなりません。
説明では必要なことは漏らさないようにしていますので第1回からよく読んでいればちゃんと動作するプログラムを1ステップずつ試すことができます。書いてあることをやるだけですが慣れない人にはそれなりに難しいです。ぜひ説明通りに逐一動かしながら慣れていくようにしてください。
さて、敵キャラクターを登場させるために、敵キャラクターの画像を準備しましょう。前回の主人公の画像を用意したのと同じ要領で良いです。次の表にまとめておきます。
画像ファイルの形式 | bmp | できれば24ビット |
---|---|---|
透明色 | RGB(255, 0, 255) | どぎつい紫→■ |
大きさ | 64x64 | 好きな大きさで良いですがとりあえず64x64としておきます |
ファイル名 | Zako1.bmp |
今回私は次のような画像を用意しました。
■画像1:敵キャラクター
この画像をダウンロードして使用しようと思っている人は画像の形式をbmpに変換するのとファイル名をZako1.bmpにするのを忘れないでください。
次に、作成した画像をプロジェクトに取り込みます。第1回からやっている人は前回作ったプロジェクトを開いてください。途中からの人は第1回で作成したプロジェクトをここからダウンロードできます。
ダウンロード VBGameD01_1.zip 13KB
プロジェクトを開くとソリューションエクスプローラーは次のようになっています。
■画像2:初期状態のソリューションエクスプローラー
ここに作成した画像をプロジェクトに取り込むには、ソリューションエクスプローラーでVBGameD01を右クリックして[追加] -[既存の項目の追加]を選択し、今作成したZako1.bmpを選択します。
前回も説明しましたが、このとき初期状態ではプログラムファイルのみが表示され、画像ファイルが表示されない設定になっているので、右下のフィルターを「すべてのファイル」などに変更して作成したZako1.bmpが表示されるようにしてください。
■画像3:初期状態のファイル選択ダイアログ
Zako1.bmpを追加するとソリューションエクスプローラーは次のようになります。
■画像4:フィルターを変更すると画像も選択できるようになります。
これで終わりではなく、プログラムでこの画像ファイルをスマートに読み込めるようにするために出力ディレクトリにコピーする設定を行うのを忘れないでください。
そのためにはZako1.bmpが選択されている状態でプロパティウィンドウで「出力ディレクトリにコピー」の項目で「新しい場合はコピーする」を選択します。
■画像5:新しい場合はコピーする
詳細は前回の説明を参照してください。
これで準備が整ったのでいよいよプログラムにはいります。
とりあえず敵はまっすぐにだけ移動できることにします。キーボードから操作できない点以外は主人公と同じですので、前回の手法を使えば難しいことはありません。
まずは、敵の座標を管理するクラスレベルの変数zako1Xとzako1Yを用意し、Timer1_TickイベントでZako1Yをどんどんたしていきましょう。画像の読み込みは最初の一回だけInitメソッド内で行って変数ZakoImageにとっておくこととにして実際に描画するForm1_Paintイベントでは読み込み済みの画像を表示することにします。これも主人公と同じです。
つまり、以下の4つをこの段階でやります。
例
■リスト1 参考:初級講座第8回 もっと変数 のリスト1 |
変数の宣言は次の通りです。この変数の宣言はSubやFunctionの中に入れないようにしてクラスレベルの変数にしてください。
Dim ZakoImage
As Bitmap Dim zako1X As Integer Dim zako1Y As Integer = -64 |
■リスト2
zako1Yの初期値は-64にしていますが、これは敵が画面の上にいきなりパッと出現するのではなく、先っぽから徐々に出現するように画面の表示領域よりさらに64ドット上を初期位置とするからです。この値が64なのは敵キャラクターの画像の縦のサイズは64だからです。これも具体的な値をプログラム内に埋め込むことになってよくないのですが、今回はこれで良しとしておきます。
Initメソッド内で画像を読み込む方法は次の通り。
ZakoImage = Image.FromFile(Application.StartupPath &
"\Zako1.bmp") |
■リスト3
あとはTimer1_Tickに次のコードを追加すれば完成です。このコードはInvalidateよりも上に追加してください。
zako1Y += 2 mainGraphics.DrawImage(ZakoImage, zako1X, zako1Y) |
■リスト4
zako1Yの1フレームあたりの増加数は主人公よりもスピードが速い2にしてみました。
これで実行すればとりあえずまっすく進む敵が表示されるようになります。
■画像6:敵が表示される
想像以上にダサいですね。エラーになったりうまく動かない人は上記のコードを書く場所が適切でないと思います。いろいろ考えて適切な場所に書き直してください。このページの一番下から完成版がダウンロードできますが自分で苦労した方が良いと思います。
さて、画面が小さすぎるのでフォームの大きさを変更しましょう。
フォームのサイズを640 x 480にしてください。
それから、主人公も敵もフォームの最初左上にいるので、これだといきなり主人公は敵が重なってしまいます。主人公の初期位置はフォームの中央の一番下にしましょう。
主人公の初期位置を設定するためにInitメソッドに次のプログラムを追加してください。
x = (Me.ClientSize.Width - 64) \ 2 y = Me.ClientSize.Height - 64 |
■リスト5
このプログラムにでてくる記号 \ は割り算を意味します。 / と違って整数部分だけ計算するので高速です。
念のために書いておくと、xとyは主人公の画像の左上の座標を表しているので、xとyが画面中央の下側に来るような式にしてしまうと、主人公は画面からはみ出してしまって全然表示されません。なので主人公の画像の大きさが64x64であることも考えて座標を計算する必要があります。
■画像7:座標計算。黄色い四角が64x64の主人公
式はいろいろな書き方があると思いますので自分にあった式でOKです。
この例では、主人公の画像の大きさが64x64であることを前提に、数字で直接 64 という値をプログラムに埋め込んでしまっていますが、これは良くありません。これだと主人公の画像の大きさを変えるたびにプログラムに埋め込んだ数字を変更する必要がありますし、この調子で今後いろいろな座標計算に 64 と数字やそれをもとにした計算結果を埋め込んでしまったら主人公の画像の大きさを変えるだけでかなり苦労するようになってしまいます。
ではどうするのかという話は、次回以降するとして今回は直接数値を埋め込んだままで先に進みます。
敵の出現位置はランダムになるようにプログラムを変更しましょう。ただし、ランダムにするのは横位置だけで、縦位置はかならず-64、つまり画面の一番上より画像の高さ分上であるようにします。
そのために2個所にコードを追加します。
1つめはランダムな値を作り出すRandomクラスを生成するコードです。これは.NEt Frameworkで用意されているRandomクラスを使うだけなので次の1行で済みます。
Dim Random As New Random |
■リスト6
この変数Randomはクラスレベルの変数にしてください。Randomクラスを乱数が必要になるたびにNewで生成するようなプログラムを書いてしまう人が良くいるようですが、Randomクラスが生成する乱数はRandomクラスが生成された時間に関係があるため、ゲームのように一瞬の処理で大量の乱数を必要とする場合に、同じような値が生成され乱数にならなくなってしまうことがあります。これを避けるためにRandomクラスは一度生成したものをあちこちで使いまわすようにします。
次にTimer1_Tickに次のコードを追加してください。(下の2行は先ほど追加したものなので、もう一度追加する必要はありません。)
If zako1Y = -64
Then zako1X = Random.Next(0, Me.ClientSize.Width - 64 + 1) End If zako1Y += 2 mainGraphics.DrawImage(ZakoImage, zako1X, zako1Y) |
■リスト7
これで初回だけzako1Xの値が乱数で決定します。初回かどうか判断するためにzako1Yが-64であるかを調べるようにしています。また、ランダムに決定するzako1Xの範囲は0〜フォームの幅-64です。0〜フォームの幅にしてしまうと、フォームの幅に近い値が生成された場合、敵の画像が右側部分が欠けるか、全然見えなくなってしまいます。
この理屈は主人公の座標を決定するときと同じで、zako1Xが新たしているのは敵の画像の左上の座標だからです。
ここでもプログラムでも64という数値を埋め込んでしまっており良くない状態になっています。
ともかく、これで実行すると直進する敵と、自由に動き回れる主人公が出現します。
敵は画面の一番下までいくと見えなくなってしまいますが、プログラム上は消滅したわけではなくzako1Yの値はどんどん増えていきます。
このことを確認するためにzako1Yの値を画面に表示するようにプログラムを追加しましょう。Paintイベントプロシージャの中でMe.Invalidateより上に以下のコードを追加してください。
'デバッグ用にzako1Yの値を左上に表示する。 mainGraphics.DrawString(zako1Y.ToString, Me.Font, Brushes.LightGreen, New Point(0, 0)) |
■リスト8
これで実行すると左上に表示される数字がどんどん大きくなっていくのがわかります。敵が画面の外に出ても数字は大きくなり続けます。
だいたいプログラム上で敵を消滅するようなコードを一切書いていないので、勝手に計算や描画がされなくなることはありません。このままだとやがてzako1Yはコンピューターが計算できる数字の限界まで増えてエラーになってしまいますし、なにより今後いろいろな敵がたくさん出てくるようになった場合、過去にでてきた敵の分までいつまでも計算を続けてしまうということになり無駄にメモリやCPUを消費してしまいます。
ここでは素直に敵が消滅するプログラムを追加しましょう。
今はまだプログラムがシンプルなので、敵が生存しているということは座標が計算され、画像が描画されるということ、敵が消滅しているということは座標が計算されず、描画が実行されないということです。
だから、敵が生存しているかいないかを示すクラスレベルの変数IsZako1Aliveを追加して、Timer1_Tick内のコードに条件を追加します。
敵のY座標がフォームの高さより高くなった場合が敵が画面の外に出た場合ということになるのでこのとき敵は消滅したとみなし、IsZako1AliveをFalseにしましょう。
変数の宣言は次の通りです。
Dim IsZako1Alive As Boolean = True |
■リスト9
Timer1_Tick内のコードは次のようになります。
If IsZako1Alive
Then If zako1Y = -64 Then zako1X = Random.Next(0, Me.ClientSize.Width - 64 + 1) End If zako1Y += 2 mainGraphics.DrawImage(ZakoImage, zako1X, zako1Y) '敵のY座標がフォームの高さより多くなったとき(=敵が画面の外に出たとき) '敵は消滅する。 If zako1Y > Me.ClientSize.Height Then IsZako1Alive = False End If End If |
■リスト10
これで実行すると、敵が画面の外に出ると数字の増加が止まることがわかります。
今の状態だと最初に出てきた敵が消滅すると2度と敵がでてこないので、今度は消滅した敵が一定の確率で再出現するようにしてみましょう。
敵の再出現のタイミングはゲームの設計にも関係してきて重要です。たとえば、敵がいつ、どの位置に出現するかを事前に決めておく場合もあれば、ランダムに次々に敵がでてくるようにする場合もあると思います。
事前に決めておく方法については機会があれば別の回に説明することとして、今回はランダムに敵がでてくるようにします。具体的には1フレームごとに敵が0.1%の確率で出現するようにしましょう。
0.1%というと大分低い値のように思えるかもしれませんが、現時点でのTimerの設定だと1秒間に100フレームの処理が行われるので、1フレームで0.1%ということは、1秒で10%ということになります。
それから、まだ1度に1匹の敵しか出現されられないので、敵がいないときのみこの再出現の判定を行うようにします。
敵がいるかいないかはIsZako1AliveがTrueかFalseかでわかるようになっていますのでプログラムは次のようにTimer1_Tickイベントに追加してください。
If IsZako1Alive
Then この部分のプログラムは省略します。 Else If Random.Next(0, 1000) < 10 Then zako1Y = -64 IsZako1Alive = True End If End If |
■リスト11
0.1%の確率を判断するためにRandomを使って、0〜999の数をランダムに発生させます。それが10より小さければ敵の再発生というわけです。
再発生時の敵の座標はY座標は初期位置と同じ-64にしています。X座標についてはリスト★に書いてあるX座標をランダムに変更するロジックがあるので、ここでX座標まで指定する必要はありません。
これで実行すると、何度も何度も敵がでてくるようになります。同時に出現するのが1体だけというのが大分さびしいです。
では、今度は複数の敵が同時に出現できるようにプログラムに機能を追加しましょう。
複数の敵を表すには、複数の座標が必要になります。2体の敵を同時に出現させるのであれば、2組の座標が必要になりますし、3体なら3組の座標を必要になります。
今のプログラムでは敵の座標はzako1Xとzako1Yで表していますから、これに加えてzako2X, zako2Yなどの変数が必要になるわけです。もちろん変数を追加するだけではなく座標をランダムに決定したり、座標を少しずつ増やして敵が移動しているように見せるコードなども敵の数に応じて書く必要があります。
これは明らかに非効率で良くない方法です。だいいちプログラムを作る段階で最大何匹の敵が同時に出現するかわかっていなければなりません。
通常はこのような問題を回避するために同じような意味の変数がたくさん必要になることが予測される場合、その変数をコレクションとして宣言します。(配列でもよいですが、コレクションが使えるVBを使っていてこのシーンでわざわざ配列を使用するメリットは皆無です。)
コレクションにしておけば敵が何体になろうともプログラムの変更は必要ありませんし、コードの量も敵の数に応じて増やす必要はなくなります。しかし、この手法も発想がちょっと古いです。
コレクションにしても今後敵の数ではなく、種類を増やそうとした場合敵の種類に応じた分岐が多くなりプログラムが複雑になってしまいます。つまり、今は直進できる敵しかいませんが、いろいろな動きをする敵をどんどん増やしていく場合を想定するわけです。
敵の種類が増えるたびに、フレームごとの更新処理は、もし敵AならY座標に2をたす、もし敵Bならちょっと横に移動する、もし敵Cならすごいスピードで主人公にせまってくる…などとIfやSelect Caseなどで分岐して書いていくことになります。
オブジェクト指向はこの問題の多くを解決できます。次回はこのオブジェクト指向を全面的にとりあげてゲームのプログラムをより洗練したものにしていきます。
さて、実はここで私がみなさんに伝えたいメッセージは、効率的なやり方をしましょうということだけではなく、目的の達成のためには手段を選ばないようにしましょうということです。
変数で管理する方法しか思いつかなければ敵の数だけ一生懸命変数を書けばよいし、コレクションで管理する方法しか思いつかなければコレクションで管理して敵の種類が増えるたびにIfやSelect Caseを追加していけばいいのです。一番残念なのは、たとえば変数で管理する方法しか思いつかなくてプログラムをあきらめてしまうという人です。
ゲームを作っていくと他にもわからないことや、もっとましなやり方があるはずと思える場合がたくさん出てくることと思います。すべてを最適な方法で書ける人は多分いません。最適なプログラムでなくてもがんばって完成させれば必ずや次のステップアップにつながります。
では、今回は変数を増やす方法で2体目の敵を出現させてみます。
非効率な方法ではありますが、発想としては自然であり、他のもっと効率的な方法はこの発想を進化させていったものですので、いきなり効率的な方法を書くのではなく、まずは非効率であるけれどもわかりやすい発想でプログラムするというのは意味のあることだと思います。
この方法はプログラムを書くのはちょっと面倒ですが、やることは既にあるコードを真似して書いていくだけです。
まずは座標と生存状態を保持するクラスレベルの変数を追加します。
Dim zako2X
As Integer Dim zako2Y As Integer = -64 Dim IsZako2Alive As Boolean = False |
■リスト12
IsZako2AliveをTrueにして、最初いきなり2匹が同時に出現するようにしてもいいのですが、私は時間差で出現するようにFalseにしてみました。
あとは、この3つの変数を使った座標変更、消滅、再出現のコードを1体目の敵と同じようにTimer1_Tickに追加するだけです。この追加するコードは変数が変わっている以外は1体目の敵とまったく同じです。
'▼2体目の敵 If IsZako2Alive Then If zako2Y = -64 Then zako2X = Random.Next(0, Me.ClientSize.Width - 64 + 1) End If zako2Y += 2 mainGraphics.DrawImage(ZakoImage, zako2X, zako2Y) '敵のY座標がフォームの高さより多くなったとき(=敵が画面の外に出たとき) '敵は消滅する。 If zako2Y > Me.ClientSize.Height Then IsZako2Alive = False End If Else If Random.Next(0, 1000) < 10 Then zako2Y = -64 IsZako2Alive = True End If End If |
■リスト13
さぁ、これだけで2体目の敵が登場するようになります。この要領で3体、4体とどんどん同時に出現する敵の数を増やしていくことができます。
ここまでの完成版をダウンロードできるようにはしておきます。
ダウンロード VBGameD01_2.zip 14KB
次回があるとしたら、より効率的に複数の敵を管理する手法をキャラクターのクラス化という観点で説明します。
続きを書くかどうかはまったくわかりませんが書くとしたら次のようなものを想定しています。
第3回 キャラクターのクラス化
第4回 あたり判定・敵との戦い
第5回 弾
第6回 音楽と効果音
第7回 いろいろな敵
第8回 運動