第1回 コンソール版から画面付きゲームへ
第1回 コンソール版から画面付きゲームへ
この章では、コンソール版で作ったHIT&BLOWを、DxLibを使った画面付きゲームへ作り替えていきます。
今回は、コンソール版と画面付きゲームの違いを確認し、ゲームテンプレートのDraw()へタイトルを表示します。
この回は、付録2で作ったゲームテンプレートの
main.cppから作業を始めます。
このページの進め方
このページでは、読む部分とソースを書き換える部分を分けています。
説明
仕組みを確認する
STEP ○ ここでソースを書き換える!
main.cppを実際に変更する
動作確認
ビルドして実行する
STEPを上から順番に進めてください。
詳しい仕組みは、各STEPの前後にある説明で確認できます。
ソースを書き換える場所は、コード内のここから追加、ここを変更、この部分を削除などのコメントで示します。
1.画像素材について
数字ボタンなどの画像は第3回から使用します。
画像のダウンロード、展開、配置は第3回で行います。
説明1 コンソール版とゲームループの違い
コンソール版では何をしていたか
コンソール版では、次のような流れでゲームを進めていました。
問題を作る
↓
キーボードから4桁の数字を入力する
↓
入力内容を確認する
↓
HITとBLOWを数える
↓
結果を文字で表示する
↓
正解するまで繰り返す
入力にはscanf()、表示にはprintf()を使っていました。
printf("予想した4桁の数字を入力:");
scanf("%s", input);
printf("HIT :%d\n", hit);
printf("BLOW:%d\n", blow);
scanf()を実行すると、入力されるまでプログラムはその場所で止まります。
scanfを実行する
↓
プレイヤーが入力するまで待つ
↓
入力されたら次の処理へ進む
コンソールプログラムでは、この方法で問題ありません。
画面付きゲームでは止まらずに動き続ける
画面付きゲームでは、入力を待つためにプログラムを止めません。
ゲームは、次の処理を何度も繰り返します。
入力を確認する
↓
ゲームの状態を更新する
↓
画面を描画する
↓
最初へ戻る
この繰り返しをゲームループと呼びます。
今回使用するテンプレートでは、次の関数を使います。
Initialize();
Update();
Draw();
DeltaTime();
それぞれの役割は次のとおりです。
| 関数 | 役割 |
|---|---|
Initialize() |
ゲーム開始時の準備を行う |
Update() |
入力確認とゲームの更新を行う |
Draw() |
現在のゲーム画面を描画する |
DeltaTime() |
Update()1回分の時間を秒で返す |
ゲーム開始時にInitialize()が1回だけ実行され、その後はUpdate()とDraw()が繰り返されます。
Initialize
↓
Update
↓
Draw
↓
Update
↓
Draw
↓
...
DeltaTime()は、1回の更新で進める時間を返す関数です。
このプログラムでは1秒間に60回更新するため、1.0 / 60.0秒を返します。
ゲームループでは、この値を使って「次の更新を行う時間になったか」を調べます。
while (accumulator >= DeltaTime())
{
Update();
accumulator -= DeltaTime();
}
前回の画面更新から経過した時間をaccumulatorへため、DeltaTime()が返す1/60秒以上になったらUpdate()を実行します。
・実際に経過した時間をためる
■ 1/60秒以上たまっている間
|・Updateを実行する
|・ためた時間から1/60秒を引く
■
・Drawで画面を描画する
図01-01のように、Initialize()は最初に1回だけ実行されます。
その後は、Update()でゲームの状態を更新し、Draw()で画面へ表示する処理を繰り返します。
このテンプレートで固定しているのは、Update()によるゲーム内部の更新間隔です。
Draw()の実行回数は、モニターのリフレッシュレートや処理速度によって変わることがあります。
コンソール版から変わる部分
コンソール版の入力と表示は、DxLib版では次のように変わります。
| コンソール版 | DxLib版 |
|---|---|
printf()でその場に表示する |
結果を変数へ保存し、Draw()で表示する |
scanf()で入力を待つ |
Update()でマウス入力を確認する |
正解までwhileで繰り返す |
すでにあるゲームループの中で少しずつ処理する |
一方、ゲームのルールを処理する次の関数は、そのまま再利用できます。
MakeProblem();
CheckAnswerError();
CountHit();
CountBlow();
入力方法と表示方法だけをDxLib用に作り直します。
複数のフレームにまたがって使う変数
コンソール版では、入力から判定までを一度に実行していました。
DxLib版では、入力、判定、描画が別々のタイミングで実行されます。
あるUpdateで数字を入力する
↓
後のUpdateで判定する
↓
Drawで結果を表示する
そのため、入力内容や判定結果は、次のフレームまで残しておく必要があります。
この教材では、まずグローバル変数を使って状態を保存します。
char answer[5];
char input[5];
int hit;
int blow;
関数の中で作ったローカル変数は、その関数が終わると使用できなくなります。
複数の関数や複数のフレームで使う値は、関数の外へ用意します。
この教材で扱う正解は、0~9から重複しない4個を並べた数字列です。
先頭が0の0123も正解として使用します。
STEP 1 Draw関数へタイトル表示を追加する
変更する場所:現在の
Draw()
確認すること:黒いウィンドウへHIT & BLOWと表示される
現在のDraw()を探し、下のコメントが付いた関数へ丸ごと置き換えます。
// この関数を丸ごと置き換える
void Draw(void)
{
DrawString(300, 100, "HIT & BLOW", GetColor(255, 255, 255));
}
2.動作確認
Ctrl + F5で実行します。
次の状態になれば成功です。
- 800×600の黒いウィンドウが表示される
- 画面に
HIT & BLOWと表示される - ウィンドウはすぐに閉じない
- ESCキーを押すと終了する
Draw()はゲームループから繰り返し呼び出されます。
そのため、ウィンドウを開いている間、タイトルが描画され続けます。
3.プログラム全体
第1回終了時点のmain.cppは、次のようになります。
#include "DxLib.h"
// 画面の大きさ
const int SCREEN_WIDTH = 800;
const int SCREEN_HEIGHT = 600;
// ゲーム内部を1秒間に更新する回数
const int UPDATE_FPS = 60;
// Update1回分の時間
const double FIXED_DELTA_TIME = 1.0 / UPDATE_FPS;
// 長時間処理が止まった場合に、
// 一度に処理する経過時間の上限
const double MAX_FRAME_TIME = 0.25;
// ゲーム開始時に1回だけ実行する
void Initialize(void)
{
// 初期化処理を書く
}
// 入力確認とゲームの更新を行う
void Update(void)
{
// キーボードやマウスの入力処理を書く
// ゲームの更新処理を書く
}
// ゲーム画面を描画する
void Draw(void)
{
// ===== 【第1回で追加・変更】最初の文字を画面へ表示 ここから =====
DrawString(300, 100, "HIT & BLOW", GetColor(255, 255, 255));
// ===== 【第1回で追加・変更】最初の文字を画面へ表示 ここまで =====
}
// Update1回分の時間を秒で返す
double DeltaTime(void)
{
return FIXED_DELTA_TIME;
}
int WINAPI WinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
LONGLONG previousTime;
double accumulator;
// ウィンドウモードで起動する
ChangeWindowMode(TRUE);
// 画面の大きさを設定する
SetGraphMode(SCREEN_WIDTH, SCREEN_HEIGHT, 32);
// ScreenFlipで垂直同期を待つ
SetWaitVSyncFlag(TRUE);
// DxLibを初期化する
if (DxLib_Init() == -1)
{
return -1;
}
// 描画先を裏画面にする
SetDrawScreen(DX_SCREEN_BACK);
// ゲームを初期化する
Initialize();
// 時間計測を開始する
previousTime = GetNowHiPerformanceCount();
// まだUpdateで処理していない時間
accumulator = 0.0;
// ゲームループ
while (ProcessMessage() == 0)
{
LONGLONG currentTime;
double frameTime;
// ESCキーが押されたら終了する
if (CheckHitKey(KEY_INPUT_ESCAPE) != 0)
{
break;
}
// 現在時刻を取得する
currentTime = GetNowHiPerformanceCount();
// 前回から実際に経過した時間を秒で求める
frameTime = (currentTime - previousTime) / 1000000.0;
// 次の計測に備えて現在時刻を保存する
previousTime = currentTime;
// 長時間止まった場合は、処理する時間を制限する
if (frameTime > MAX_FRAME_TIME)
{
frameTime = MAX_FRAME_TIME;
}
// 実際に経過した時間をためる
accumulator += frameTime;
// 1/60秒以上たまっている間、ゲームを更新する
while (accumulator >= DeltaTime())
{
Update();
// Updateで処理した時間を引く
accumulator -= DeltaTime();
}
// 裏画面を黒で消す
ClearDrawScreen();
// ゲーム画面を描画する
Draw();
// 裏画面を表画面へ表示する
ScreenFlip();
}
// DxLibを終了する
DxLib_End();
return 0;
}
4.今回のまとめ
コンソール版とDxLib版では、入力と表示の方法が変わります。
printf
↓
Drawで文字や画像を描画する
scanf
↓
Updateでマウスやキーボードを確認する
入力待ちをするwhile
↓
止まらずに動き続けるゲームループ
DeltaTime()は、ゲームループの中でUpdate()を1/60秒間隔で進めるために使用されています。
一方で、問題作成やHIT・BLOWの判定処理は、そのまま再利用できます。
次回は、コンソール版で作ったゲーム処理をDxLibのプロジェクトへ移します。

No Comments