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.


