挫折不可能!初級ゲームプログラミング完全マニュアル  RSSを登録する

「ゲームが作りたいけどプログラミングなんて全然知らない」「文系だし数学や物理が苦手だけどゲームが作れるだろうか?」そんなアナタのためのメールマガジンです。PCの電源を入れてからどうすればいいのか?から自分のゲームが完成するまでを応援します!

最新号をメルマガでお届けします    
登録 解除

規約に同意して

登録した方には、まぐまぐの公式メルマガ(無料)をお届けします。
2008/07/26

初級ゲームプログラミング完全マニュアル [vol.0056 2008/07/26]

┏┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳┓
┣┛                                                              ┗┫
┃       挫折不可能!  初級ゲームプログラミング完全マニュアル       ┃
┃                                                                  ┃
┃                       第 56 号  2008/07/26                       ┃
┣┓                                                              ┏┫
┗┻━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┻┛
                            - Ads space -
┌┬───────────────────────────────┬┐
││ はじめに                                                     ││
└┴───────────────────────────────┴┘

 みなさま、こんにちは!

 個人ゲーム制作アドバイザーの Byerkut です!

 天気が不安定なこの時期は夕立や雷が頻発してしまいますが、

 パソコンを使っているときに停電などで電源が落ちてしまうと

 Windows の内部状態が壊れてしまうことがあります。

 そうなったときに復旧できるよう、日頃からバックアップを忘れずに

 心がけておくと安心ですよね。

 さて、今回は敵キャラの登場スケジュールです。

┌┬───────────────────────────────┬┐
││本日のラインナップ                                            ││
└┴───────────────────────────────┴┘

  ・今日のメインテーマ
      【私待つわ、順番が来るまで待つわ】

  ・あとがき

┌┬───────────────────────────────┬┐
││ みんなの備忘録                                               ││
└┴───────────────────────────────┴┘

  ■Visual C++ 2008 Express Edition をインストールする手順
    http://www.game-create.com/archives/235

  ■Visual C++ 2008 Express Edition でプロジェクトを新規作成する手順
    http://www.game-create.com/archives/270

  ■VC++ 2005 EE でプロジェクトにソースファイルを登録する手順
    http://blog.mag2.com/m/log/0000240151/108824854.html

  ■画像ファイルを LoadImage() 関数で読み込めるようにする手順
    http://www.game-create.com/archives/308

  ■ゲーム用ウィンドウのテンプレート
    http://www.game-create.com/menu/downloads

  ■ビットマップ学習用クラス - Study::Bitmap
    http://www.game-create.com/menu/downloads

┏┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳┓
┃┃                                                              ┃┃
┃┃                      今日のメインテーマ                      ┃┃
┃┃                                                              ┃┃
┃┃              【私待つわ、順番が来るまで待つわ】              ┃┃
┃┃                                                              ┃┃
┗┻━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┻┛

 前回までのメルマガで、地球防衛ゲームに敵キャラクターを出現させる

 ことができるようになりました。敵キャラは時間差を置いて

 編隊を組んで登場してくるようになっていますが、今週はこの仕組みに

 ついて解説していきます。



 まずは仕様を確認したいと思います。

 前回のサンプルコードをコンパイルして実行してみてください。

 前回のサンプルコードは次の場所からダウンロードできます。

    http://www.game-create.com/archives/442

 実行していただけるとわかることは、まず「うごくうんこ」が5体

 登場して、少し時間をおいて「増田ジゴロン」が5体、また少し時間を

 おいて「はっぱ隊」が5体、という流れになっています。



 これをプログラムで実現するためにはどうしたら良いでしょうか?



 まずはプログラミング言語の基本である「分岐」を使って考えてみます。

 たとえば次のようなコードです。

  ------------------------------------------------------------------
    if (/* 少し時間が経ったら */) {
      /* 「うごくうんこ」を5体登場させる */
    }

    if (/* また、少し時間が経ったら */) {
      /* 「増田ジゴロン」を5体登場させる */
    }

    if (/* また、少し時間が経ったら */) {
      /* 「はっぱ隊」を5体登場させる */
    }
  ------------------------------------------------------------------

 良いですね。わかりやすいです。こうやって書けると楽ですよね。

 しかし、実際にはそうはなりません。なぜかというと、このコードは

 1秒間に 60 回呼び出されるからです。ゲームはアニメーションのように

 なめらかに動いているように見せるため、1秒間に 60 回程度の間隔で

 画面を更新するのが基本です。この場合、このコードは1秒間に 60 回も

 呼び出される想定になっていません。1フレーム目にすべての敵が

 登場してしまいます。

 これではさすがにゲームの難易度が上がってしまいます。



 では、どうしたら良いのでしょうか?



 小さい頃、父親とお風呂に入って「肩までつかって 100 まで数えるんだ」

 と言われたことがあるかと思いますが、あの境地です。

 数を数えるのです。

 どんな数を数えるのかというと、「ゲームが始まってからの時間です」

 ゲームが始まってからの時間を数えておき、たとえば 100 まで数えたら

 「うごくうんこ」を、 200 まで数えたら「増田ジゴロン」を登場させる、

 といった感じのコードに置き換えるのです。

  ------------------------------------------------------------------
    // ゲーム時間の数値をプラス1する
    gameRunningTime = gameRunningTime + 1;

    if (gameRunningTime == 100) {
      /* 「うごくうんこ」を5体登場させる */
    }

    if (gameRunningTime == 200) {
      /* 「増田ジゴロン」を5体登場させる */
    }

    if (gameRunningTime == 300) {
      /* 「はっぱ隊」を5体登場させる */
    }
  ------------------------------------------------------------------

 これは非常に現実味のあるコードです。

 1秒間に 60 回呼び出されても問題ありません。

 なぜかというと、敵キャラが登場するタイミングが

 gameRunningTime が 100 の時と 200 の時と 300 の時の

 それぞれ一度ずつしかないためです。それ以外の場合は

 gameRunningTime がプラス1されるだけですので、

 ゲームの進行を邪魔しません。



 さて、これで大半は問題ないのですが、普通のプログラマは

 このように if 文を使って敵キャラの登場をスケジューリングすることを

 嫌います。なぜかというと、敵キャラが増えるとプログラムの

 ソースコードが長くなってしまうからです。少し慣れてから

 読み直していただけるとわかるかと思いますが、

 プログラミングをする上ではデータと処理を分離したいという

 美学があります。今回の場合、敵キャラの登場スケジュールはデータで、

 一連の if 文は処理です。これは分離しておきたいのです。

 ソースコードを分割したのと同じように「後々のため」です。



 では、どうしたら良いのでしょうか?



 今回は次のようにしました。

 enemies_controller.cpp を開いてください。

 注目すべきは冒頭部分にある enemiesTimeline という配列です。

  ------------------------------------------------------------------
  enemies_controller.cpp
  ------------------------------------------------------------------
    static EnemiesTimeline enemiesTimeline[] = {
      100, Enemy001_Initialize, Enemy001_Update, Enemy001_Draw,
      110, Enemy001_Initialize, Enemy001_Update, Enemy001_Draw,
      120, Enemy001_Initialize, Enemy001_Update, Enemy001_Draw,
      130, Enemy001_Initialize, Enemy001_Update, Enemy001_Draw,
      140, Enemy001_Initialize, Enemy001_Update, Enemy001_Draw,
      150, Enemy001_Initialize, Enemy001_Update, Enemy001_Draw,
      200, Enemy002_Initialize, Enemy002_Update, Enemy002_Draw,
      210, Enemy002_Initialize, Enemy002_Update, Enemy002_Draw,
      220, Enemy002_Initialize, Enemy002_Update, Enemy002_Draw,
      230, Enemy002_Initialize, Enemy002_Update, Enemy002_Draw,
      240, Enemy002_Initialize, Enemy002_Update, Enemy002_Draw,
      250, Enemy002_Initialize, Enemy002_Update, Enemy002_Draw,
      300, Enemy003_Initialize, Enemy003_Update, Enemy003_Draw,
      310, Enemy003_Initialize, Enemy003_Update, Enemy003_Draw,
      320, Enemy003_Initialize, Enemy003_Update, Enemy003_Draw,
      330, Enemy003_Initialize, Enemy003_Update, Enemy003_Draw,
      340, Enemy003_Initialize, Enemy003_Update, Enemy003_Draw,
      350, Enemy003_Initialize, Enemy003_Update, Enemy003_Draw,
    };
  ------------------------------------------------------------------

 「配列ってこんなんだっけ?」と思われるかもしれませんが、

 これは「配列の宣言」と「初期化」を同時にやる例です。

 余裕があったら覚えておいてください。

 このように、「敵キャラを登場させるために必要なデータ」を

 一カ所にまとめておくと、後になって手を入れるのが楽になります。

 詳しく解説しますので、次をご覧ください。この配列の各値の意味です。

  ------------------------------------------------------------------
  enemies_controller.cpp
  ------------------------------------------------------------------
    static EnemiesTimeline enemiesTimeline[] = {
      ┌敵キャラを登場させる時間(フレーム数)
      |   ┌登場時間になった敵キャラを初期化する関数
      |   |                   ┌敵キャラを更新する処理
      |   |                   |               ┌描画する処理
      ↓   ↓                   ↓               ↓
      100, Enemy001_Initialize, Enemy001_Update, Enemy001_Draw,
      110, Enemy001_Initialize, Enemy001_Update, Enemy001_Draw,
      120, Enemy001_Initialize, Enemy001_Update, Enemy001_Draw,
      130, Enemy001_Initialize, Enemy001_Update, Enemy001_Draw,
      140, Enemy001_Initialize, Enemy001_Update, Enemy001_Draw,
      150, Enemy001_Initialize, Enemy001_Update, Enemy001_Draw,
      200, Enemy002_Initialize, Enemy002_Update, Enemy002_Draw,
      210, Enemy002_Initialize, Enemy002_Update, Enemy002_Draw,
      220, Enemy002_Initialize, Enemy002_Update, Enemy002_Draw,
      230, Enemy002_Initialize, Enemy002_Update, Enemy002_Draw,
      240, Enemy002_Initialize, Enemy002_Update, Enemy002_Draw,
      250, Enemy002_Initialize, Enemy002_Update, Enemy002_Draw,
      300, Enemy003_Initialize, Enemy003_Update, Enemy003_Draw,
      310, Enemy003_Initialize, Enemy003_Update, Enemy003_Draw,
      320, Enemy003_Initialize, Enemy003_Update, Enemy003_Draw,
      330, Enemy003_Initialize, Enemy003_Update, Enemy003_Draw,
      340, Enemy003_Initialize, Enemy003_Update, Enemy003_Draw,
      350, Enemy003_Initialize, Enemy003_Update, Enemy003_Draw,
    };
  ------------------------------------------------------------------

 たとえば一番上のデータは、「ゲームが始まってから 100 フレームが

 経過したら Enemy001_Initialize() という関数を使って敵キャラクター

 を初期化し、その敵キャラを Enemy001_Update() を使って動かして、

 Enemy001_Draw() を使って描画する」という意味になっています。



 では、ここから「新しい敵キャラを追加したい」という場合は

 どうすれば良いでしょうか?



 答えは今回のメルマガ+前回のメルマガです。

 新しい敵キャラを作るためには、まず、新しい敵キャラを初期化したり

 動かしたりする関数を作ります。

 関数ができたら enemiesTimeline 配列にデータを追加すれば完了です。

 たとえば Enemy004 という敵を増やしたい場合は、

 まず、次の関数を作ります。

  ------------------------------------------------------------------
  Enemy004_Initialize()
  Enemy004_Update()
  Enemy004_Draw()
  ------------------------------------------------------------------

 これらの関数の詳細は前回解説しましたので参照ください。

    http://archive.mag2.com/0000240151/20080719133136000.html

 これらの関数ができたら enemiesTimeline 配列にデータを追加します。

  ------------------------------------------------------------------
  enemies_controller.cpp
  ------------------------------------------------------------------
    static EnemiesTimeline enemiesTimeline[] = {
      100, Enemy001_Initialize, Enemy001_Update, Enemy001_Draw,
      110, Enemy001_Initialize, Enemy001_Update, Enemy001_Draw,
      120, Enemy001_Initialize, Enemy001_Update, Enemy001_Draw,
      130, Enemy001_Initialize, Enemy001_Update, Enemy001_Draw,
      140, Enemy001_Initialize, Enemy001_Update, Enemy001_Draw,
      150, Enemy001_Initialize, Enemy001_Update, Enemy001_Draw,
      200, Enemy002_Initialize, Enemy002_Update, Enemy002_Draw,
      210, Enemy002_Initialize, Enemy002_Update, Enemy002_Draw,
      220, Enemy002_Initialize, Enemy002_Update, Enemy002_Draw,
      230, Enemy002_Initialize, Enemy002_Update, Enemy002_Draw,
      240, Enemy002_Initialize, Enemy002_Update, Enemy002_Draw,
      250, Enemy002_Initialize, Enemy002_Update, Enemy002_Draw,
      300, Enemy003_Initialize, Enemy003_Update, Enemy003_Draw,
      310, Enemy003_Initialize, Enemy003_Update, Enemy003_Draw,
      320, Enemy003_Initialize, Enemy003_Update, Enemy003_Draw,
      330, Enemy003_Initialize, Enemy003_Update, Enemy003_Draw,
      340, Enemy003_Initialize, Enemy003_Update, Enemy003_Draw,
      350, Enemy003_Initialize, Enemy003_Update, Enemy003_Draw,

      // ↓追加!!
      400, Enemy004_Initialize, Enemy004_Update, Enemy004_Draw,
    };
  ------------------------------------------------------------------

 これで見事 400 フレーム目に Enemy004 という新しい敵キャラが

 登場します。



 ちなみに、この enemiesTimeline という配列を参照して、

 実際に敵キャラを登場させている部分が enemies_controller.cpp の

 EnemiesControllerUpdate() なのですが、

 この中身は今回のメルマガの最初に登場した if 文での内容と

 ほとんど同じになっていますので、興味のある方はご覧ください。

  ------------------------------------------------------------------
  enemies_controller.cpp
  ------------------------------------------------------------------
    /*
      frame という変数にゲームが始まってからの時間がカウントされている。

      敵キャラの登場時間が来たら、敵を管理する配列(enemies)の
      空き(Dead)を探して、そこに敵キャラの情報をセット(Alive)している。
      その敵キャラが画面外へ出たり、倒されたりすると、
      再び空き(Dead)状態となり、次に登場する敵の情報が入る。
     */
    // タイムスケジュールに従って敵キャラを生成する
    if (current < (sizeof(enemiesTimeline) / sizeof(EnemiesTimeline))) {
      while (enemiesTimeline[current].frame == frame) {
        for (i = 0; i < ENEMIES_CONTROLLER_MAX; i++) {
          if (enemies[i].status == Dead) {
            enemies[i].graphic = enemiesGraphics;
            enemies[i].status  = Alive;
            enemies[i].updater = enemiesTimeline[current].updater;
            enemies[i].drawer  = enemiesTimeline[current].drawer;
            enemiesTimeline[current].initializer(enemies[i]);
            break;
          }
        }
        current++;
      }
    }
  ------------------------------------------------------------------



 さて、これで自由自在に敵キャラを追加できるようになりました。

 これで「サンプルゲームの難易度がヌルい!」という場合に

 自分で難易度を調節できるようになりますね。

 地球防衛ゲームも「背景」「プレイヤー」「敵キャラ」ができましたので

 次回は「プレイヤーの攻撃と敵の衝突」を実装して、

 よりゲームらしくしていきましょう。



 今回も最後まで読んでいただきましてありがとうございます!

 それでは!

                                                            Byerkut.

┌┬───────────────────────────────┬┐
││ 重要記事のダイジェスト                                       ││
└┴───────────────────────────────┴┘

  ■C++ は計算と記憶しかできない
  ■外部の機能(関数)を借り受けるには #include を使う
  ■#include で借り受けた機能を使うには関数を呼び出す
    http://archive.mag2.com/0000240151/20080203171350000.html

  ■Windows プログラムには必ず WinMain() 関数から開始する
    http://archive.mag2.com/0000240151/20080209215231000.html

  ■プログラムは関数の集まり
  ■プログラムは関数を増やしてソフトウェアを作る
  ■関数には次の要素がある(名前・機能・引数・戻り値)
    http://archive.mag2.com/0000240151/20080216090000000.html
  ■関数を作には次の要素を決める(名前・機能・引数・戻り値)
    http://archive.mag2.com/0000240151/20080608200909000.html

  ■プログラムはソースコードを分散させて大きなソフトを作る
    http://archive.mag2.com/0000240151/20080525121425000.html
    http://archive.mag2.com/0000240151/20080531212858000.html

  ■ゲームを作るためには「入力」「出力」「条件分岐」が必要
    http://archive.mag2.com/0000240151/20080301221623000.html

  ■画像ファイルの絵を画面に表示する際にはメモリに読み込む
   http://archive.mag2.com/0000240151/20080406202024000.html

  ■LoadImage() 関数はコピペで使い回せる
  ■LoadImage() 関数の第2引数は
    メモリに読み込みたい画像ファイルの名前を渡す
  ■LoadImage() 関数で読み込むファイルは
    プロジェクトディレクトリにおいてある必要がある
    http://archive.mag2.com/0000240151/20080413174135000.html

┌┬─────┬─────────────────────────┬┐
││ あとがき │  発行者のつぶやきです                            ││
└┴─────┴─────────────────────────┴┘

 モンスターハンターですが、やっとドドブランゴを倒すことができました。

 普段は大剣を使っているのですが、攻略サイトを参考に双剣で攻めました。

 ふたを開けてみれば乱舞で圧勝。あの苦労は何だったのでしょうか…

┌┬───────────────────────────────┬┐
├┘                                                              └┤
│  購読の解除はこちら↓                                            │
│  http://www.game-create.com/contents/gp_beginners_ml             │
├┐                                                              ┌┤
└┴───────────────────────────────┴┘
                            - Ads space -
┌┬───────────────────────────────┬┐
├┘                                                              └┤
│ 「挫折不可能!初級ゲームプログラミング完全マニュアル」は、       │
│ 「いちばんやさしいゲームの作り方」が運営しています。             │
│                                                                  │
│  サイト     : http://www.game-create.com/                        │
│  発行元     : http://www.game-create.com/menu/about              │
│  発行者     : http://www.game-create.com/menu/profile            │
│  問い合わせ : http://www.game-create.com/menu/contact            │
│  利用規約   : http://www.game-create.com/informations/agreement  │
├┐                                                              ┌┤
└┴───────────────────────────────┴┘
                 POWERED BY LIBERTIASTER GAME STYLE.
最新号をメルマガでお届け
登録 解除

規約に同意して

登録した方には、まぐまぐの公式メルマガ(無料)をお届けします。

最近の記事

上へ戻る