ゲームの作り方
当然のことながら、RPGとシューティングゲームは全く作り方が異なります。ですが、基本的に共通している部分もあります。Windowsでゲームを作る以上、当然Windowsプログラムの一種です。ただ、一般のアプリケーションと違った特有のテクニックが必要になります。それを、一般的なゲームの作り方と定義して、このゲームプログラムテクニックで紹介して行きたいと思います。
ひとつ注意して欲しいのは、ここで紹介しているテクニックを使わなくてはゲームが作れないということは決してありません。ループ構造なんてなくても、フォームにコントロールをペタペタ張っていけば必ず完成するはずです。ゲームに特化したアイディアの一部として捕らえてもらって、実装はこれを参考にしていただければいいと思います。
ゲームの流れ
管理者の経験上、大半のゲームはこのような流れで処理することができます。
どこかにこんなことが書かれていました。
ゲームは一般的に「プレイヤーが動作する」→「動作を反映させる」→「画面に情報を提示する」→「プレイヤーが動作する」…の繰り返しであると。
それを、モデル化したのが上の画像です。プレイヤーが「→」キーを押します。すると、それを受け取ってキャラクタの座標をひとつ右へずらします。反映させた結果を画面に表示します。基本的な構造はこれですべて片付くはずです。
こんな話をすると、ゲームというものが非常につまらなく感じると思います。構造は非常に簡単なのですが、ゲームにはそれ以外に重要な要素があります。管理者が必要と感じた要素を挙げておきます。
画像の表示(DirectX等) |
BGM・効果音 |
情報取得(キーボードやマウス、ゲームパッド) |
データの読み込み |
これらの処理がゲームプログラムのこだわりどころです。これに加えて、各種ゲーム特有の処理が必要となります。まぁ、そこが一番面白いところですが。後は、データの集積です。キャラクタのデータ、アイテムのデータを作っておいて、それに応じて処理を分けています。
ループの製作
色々と実験
ちょっと横道にそれましたが、上の説明からゲームは同じ処理をひたすら繰り返します。VC#には「タイマー」コントロールがあるので、それを使ってループを作ってもいいでしょう。Athlon64 3200+環境下で実験したところInterval16でFPS40、Interval10でFPS64、というよく分からない精度の結果が得られました。ちなみに、どんなにIntervalを減らしても64が最高でした。Intevalはミリセカンド単位で入力するため、16でFPS60くらいが出てくれるとありがたかったのですが…ちなみにFPSとは「Frame per second」の略で、一秒間に画面を何回書き換えたのかをあらわしています。
ゲームでは画面上でキャラクタをアニメーションさせて構成するのが普通でしょう(特に、アクションゲームやシューティングは)。一秒間に大体30回の書き換えを行うと、人間の目はアニメーションしているように思ってくれるそうです。また、パソコンのディスプレイの画面書き換え頻度を考えると一秒間に60回くらいの書き換えは必要になります。すると、FPSが最高でも64しか出ないタイマーではかなり微妙なラインです。実行環境によっては60でないこともあると思います。
タイマーコントロールはTimerイベントが発生するとメソッドを呼び出して処理を行います。そのため、余分な処理が多くその分遅いのです。ゲームプログラムでは、そんな処理必要ありません。普通、ループをしようと思ったら「while」構文などを使いますよね。
private void button2_Click(object sender, System.EventArgs e) { lfps=0; oldtime = System.Environment.TickCount; for(;;) { lfps++; if (oldtime + 1000 <= System.Environment.TickCount) { this.label1.Text = lfps.ToString() + "FPS"; oldtime = System.Environment.TickCount; lfps = 0; } Application.DoEvents(); } } |
「Application.DoEvents()」と言うのは、Windowsのメッセージを処理するメソッドです。これを入れずにループしてしまうと、Formの終了ボタンを押しても何をしてもプログラムが反応しなくなってしまいます。このように「for」を使ってFPSを算出すると600,000くらいはでます。ま氈A何の負荷もないのですから当然ですが、早いほうがいいでしょう。負荷もありませんし。
今の例では、ボタンを押して測定しましたが、ゲームを起動したと同時にループに入りたい場合があります。と言うか、それが普通でしょう。そっちのほうがカッコいいですから!そこで今度は、この文章を「Form_Load」に書き込んでみましょう。
…試してみてください。多分、ウィンドウが表示されません。理論的にはできそうなのですが、どうやら「Form_Load」ではウィンドウが表示される前にループに入ってしまうため早すぎるようです。困った…
はじめからループに入る
そもそも、ゲームを作るうえでわざわざForm内のメソッドでループを回す必要はありません。ウィンドウが表示されていて、そこに描画できさえすればこと足ります。また、DirectXなどの描画エンジンを使うと、Form上のコントロールがサーフェイスの下に隠れてしまって結局は意味のないものになります。そこで、Formはただ表示しておくだけで、ループ自体は別のところで作っておく方式をとってみることにします。
C#基本事項のフォームコード解析の回で説明しましたが、Formに処理が渡されているのはMainメソッド内の文章です。そこを書き換えて、処理を渡さずに、とりあえずフォーム(ウィンドウ)表示とアプリケーション登録だけはやっておいて、後の処理はMainメソッド内のループで回す方式をとってみます。
static void Main() { Form1 frm = new Form1(); frm.Show(); lfps=0; oldtime = System.Environment.TickCount; while(frm.Created) { lfps++; if (oldtime + 1000 <= System.Environment.TickCount) { frm.label1.Text = lfps.ToString() + "FPS"; oldtime = System.Environment.TickCount; lfps = 0; } Application.DoEvents(); } } |
Mainメソッド内で「Application.Run(new Form1());」と処理を渡していたところを、通常のインスタンス生成に変更し、 「show()」メソッドを呼び出して手動でウィンドウを表示しています。フォームを表示したら、ソッコウ「while」ループに突入です。ここでも「Application.DoEvents();」は絶対に忘れないで下さい。Windowsメッセージを受け取らなくてってしまいます。ループの脱出は、「Created」プロパティを使用しています。このプロパティはフォームが生成されている間は「true」を返します。フォームが消されたら、「false」が返ってきてループを抜け、無事にプログラムが終了します。
もうひとつ変わっていることは、「this.label1」から「frm.label1」のところです。まぁ、当たり前ですが。このように、frmのメンバにも当然アクセスは可能です。また、実験してみれば分かることですが、「Application.DoEvents();」をループ内に置いているため、form上のボタンのイベントなどもしっかりと発生します。Mainで回しても、何不自由なく通常の処理を行うことができます。
このようにループを回せば、frmもひとつのインスタンスだと感覚がつかみやすいですね。また、プログラムの支配権はこちらにあり!と言った感じもします。(あくまで、管理者の感覚ですが…)Form1内のどこだか分からないところで処理が行われているよりは、こうして自分で作ってしまったほうが落ち着きますね。当初の目標であった、「起動時からループに突入!」の目標も達成できました。
まとめ
C++を使うと、実は「Applicaiton.DoEvents();」のところ(つまり、Windowsのメッセージ処理)を自分で書かなくてはいけません。これが結構面倒な作業なのです。ですが、幸いにもC#はたった1文でこれを済ましてくれます。何て便利なのでしょう〜!
以後、この「ゲームプログラムテクニック」では最後にやったMainメソッド内でのループを使ってプログラムを構成していきます。Form1内での処理の流れはC#基本事項のフォームコードの解析で説明してあるので、気になる方はそちらをご覧下さい。
では、今回はこの辺で…次回は、FPSを安定させるタイマーの製作をします。