第2回 DxLibに移してゆくぅ
第2回 ゲームのルールをDxLibへ移そう
前回は、画面付きゲームで使用するInitialize()、Update()、Draw()とゲームループを確認しました。
今回は、コンソール版HIT&BLOWの処理をDxLib版へ移します。
この回は、第1回終了時点の
main.cppから作業を始めます。
1.今回やること
コンソール版の処理は、次の2種類に分けられます。
ゲームのルールを処理する部分
と
コンソールで入力・表示する部分
ゲームのルールを処理する関数は、そのままコピーして使います。
MakeProblem()
CheckAnswerError()
CountHit()
CountBlow()
一方、次の処理はコンソール専用なので、そのまま使用しません。
main()
scanf()
printf()
入力待ちをするwhile
ShowInputError()
DxLib版では、入力と表示の考え方が次のように変わります。
コンソール版
scanf()で入力を待つ
printf()でその場に表示する
DxLib版
Update()で入力を確認する
Draw()で毎フレーム表示する
今回はまだマウス入力を作りません。
プログラム内へ仮入力"1234"を用意し、判定結果をDxLibの画面へ表示します。
2.今回の完成状態
実行すると、次の内容が表示されます。
HIT & BLOW
デバッグ用正解:8250
仮の入力 :1234
HIT :1
BLOW:2
正解は起動するたびに変わるため、HIT数とBLOW数も変わります。
この教材でいう4桁は、0~9を4個並べた文字列です。
先頭が0の0123も使用できます。
STEP 1 必要なヘッダーファイルを追加する
変更する場所:
#include "DxLib.h"の下
確認すること:エラーなくビルドできること
#include "DxLib.h"
// ここから追加
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
// ここまで追加
printf()とscanf()は使用しないため、<stdio.h>は追加しません。
STEP 2 ゲームで使う変数を追加する
変更する場所:画面サイズなどの定数の下
確認すること:エラーなくビルドできること
// ここから追加
// ゲームの正解
char answer[5];
// プレイヤーの入力
char input[5];
// 入力内容のエラー番号
int inputError;
// 判定結果
int hit;
int blow;
// ここまで追加
4桁の文字列の最後には終端文字'\0'が入るため、配列は5要素にします。
'1' '2' '3' '4' '\0'
STEP 3 4つの関数をコピーする
変更する場所:グローバル変数の下、
Initialize()より前
確認すること:エラーなくビルドできること
教材に入っている移植元/HIT_AND_BLOW_コンソール版.cを開きます。
このファイルから、次の4関数を関数ごとそのままコピーします。
MakeProblem()
CheckAnswerError()
CountHit()
CountBlow()
次の順番で並べてください。
MakeProblem()
CheckAnswerError()
CountHit()
CountBlow()
Initialize()
ShowInputError()はコピーしません。
この関数はprintf()でエラーメッセージを表示するため、DxLib版ではDraw()の中へ表示処理を書きます。
4関数の内容は、ページ最後の完成コードでも確認できます。
STEP 4 Initialize関数へ初期化処理を書く
変更する場所:
Initialize()の中
確認すること:エラーなくビルドできること
Initialize()はゲーム開始時に1回だけ実行されます。
ここで、乱数の初期化、問題作成、仮入力の設定、判定を行います。
void Initialize(void)
{
// ここから追加
// 実行するたびに乱数の並びを変える
srand((unsigned int)time(NULL));
// 重複しない4桁の問題を作る
MakeProblem(answer);
// マウス入力ができるまで使用する仮の入力
strcpy_s(input, sizeof(input), "1234");
// 仮入力に問題がないか確認する
inputError = CheckAnswerError(input);
// 判定結果を初期化する
hit = 0;
blow = 0;
// 正しい入力の場合だけ判定する
if (inputError == 0)
{
hit = CountHit(answer, input);
blow = CountBlow(answer, input);
}
}
コンソール版では、入力されるまでscanf()で待っていました。
DxLib版ではゲームループを止められないため、今回は仮入力を使用します。
strcpy_s(input, sizeof(input), "1234");
数字ボタンを作った後、この仮入力は削除します。
STEP 5 Draw関数へ表示処理を書く
変更する場所:現在の
Draw()を関数全体ごと置き換える
確認すること:仮入力の判定結果が表示される
第1回で作ったDraw()へ処理を追加するのではありません。
現在のDraw()を、次のDraw()へ丸ごと置き換えます。
コンソール版のprintf()の代わりに、DxLibの描画関数を使います。
DrawString()
決まった文字を表示する
DrawFormatString()
変数の値を文字へ入れて表示する
ShowInputError()の代わりに、inputErrorの値を確認して、対応するメッセージを表示します。
// この関数を丸ごと置き換える
void Draw(void)
{
unsigned int white = GetColor(255, 255, 255);
unsigned int yellow = GetColor(255, 255, 0);
unsigned int red = GetColor(255, 80, 80);
DrawString(40, 30, "HIT & BLOW", white);
DrawFormatString(40, 80, yellow, "デバッグ用正解:%s", answer);
DrawFormatString(40, 120, white, "仮の入力 :%s", input);
if (inputError == 0)
{
DrawFormatString(40, 180, white, "HIT :%d", hit);
DrawFormatString(40, 220, white, "BLOW:%d", blow);
}
else if (inputError == 1)
{
DrawString(40, 180, "4桁で入力してください。", red);
}
else if (inputError == 2)
{
DrawString(40, 180, "0~9の数字だけを入力してください。", red);
}
else if (inputError == 3)
{
DrawString(40, 180, "同じ数字を2回使わないでください。", red);
}
// ここまで追加
}
Draw()では判定をやり直しません。
Initialize()で変数へ保存した結果を読み取り、画面へ表示します。
Initialize()で判定する
↓
結果を変数へ保存する
↓
Draw()で表示する
Update()は、まだ変更しません。マウス入力は後の回で追加します。
3.完成したmain.cpp
ここまでの作業が終わったら、自分のmain.cppと次の完成コードを見比べます。
#include "DxLib.h"
// ===== 【第2回で追加・変更】C/C++標準ライブラリを追加 ここから =====
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
// ===== 【第2回で追加・変更】C/C++標準ライブラリを追加 ここまで =====
// 画面の大きさ
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;
// ===== 【第2回で追加・変更】HIT&BLOWのゲームルールと初期化を追加 ここから =====
// ゲームの正解
char answer[5];
// プレイヤーの入力
char input[5];
// 入力内容のエラー番号
int inputError;
// 判定結果
int hit;
int blow;
// 重複しない4桁の問題を作る
void MakeProblem(char outputAnswer[5])
{
char temp[] = "0123456789";
int i;
for (i = 0; i < 100; i++)
{
int index1 = rand() % 10;
int index2 = rand() % 10;
char work;
work = temp[index1];
temp[index1] = temp[index2];
temp[index2] = work;
}
for (i = 0; i < 4; i++)
{
outputAnswer[i] = temp[i];
}
outputAnswer[4] = '\0';
}
// 入力内容に問題がないか確認する
int CheckAnswerError(char inputData[])
{
int i;
int j;
// 4桁か確認する
if (strlen(inputData) != 4)
{
return 1;
}
// すべて数字か確認する
for (i = 0; i < 4; i++)
{
if (!isdigit((unsigned char)inputData[i]))
{
return 2;
}
}
// 同じ数字が含まれていないか確認する
for (i = 0; i < 4; i++)
{
for (j = i + 1; j < 4; j++)
{
if (inputData[i] == inputData[j])
{
return 3;
}
}
}
return 0;
}
// HIT数を数える
int CountHit(char correctAnswer[], char inputData[])
{
int count = 0;
int i;
for (i = 0; i < 4; i++)
{
if (correctAnswer[i] == inputData[i])
{
count++;
}
}
return count;
}
// BLOW数を数える
int CountBlow(char correctAnswer[], char inputData[])
{
int count = 0;
int i;
int j;
for (i = 0; i < 4; i++)
{
for (j = 0; j < 4; j++)
{
if (inputData[i] == correctAnswer[j] && i != j)
{
count++;
}
}
}
return count;
}
// ゲーム開始時に1回だけ実行する
void Initialize(void)
{
// 実行するたびに乱数の並びを変える
srand((unsigned int)time(NULL));
// 重複しない4桁の問題を作る
MakeProblem(answer);
// マウス入力ができるまで使用する仮の入力
strcpy_s(input, sizeof(input), "1234");
// 仮入力に問題がないか確認する
inputError = CheckAnswerError(input);
// 判定結果を初期化する
hit = 0;
blow = 0;
// 正しい入力の場合だけ判定する
if (inputError == 0)
{
hit = CountHit(answer, input);
blow = CountBlow(answer, input);
}
// ===== 【第2回で追加・変更】HIT&BLOWのゲームルールと初期化を追加 ここまで =====
}
// 入力確認とゲームの更新を行う
void Update(void)
{
// キーボードやマウスの入力処理を書く
// ゲームの更新処理を書く
}
// ゲーム画面を描画する
void Draw(void)
{
// ===== 【第2回で追加・変更】判定結果とエラーを画面へ表示 ここから =====
unsigned int white = GetColor(255, 255, 255);
unsigned int yellow = GetColor(255, 255, 0);
unsigned int red = GetColor(255, 80, 80);
DrawString(40, 30, "HIT & BLOW", white);
DrawFormatString(40, 80, yellow, "デバッグ用正解:%s", answer);
DrawFormatString(40, 120, white, "仮の入力 :%s", input);
if (inputError == 0)
{
DrawFormatString(40, 180, white, "HIT :%d", hit);
DrawFormatString(40, 220, white, "BLOW:%d", blow);
}
else if (inputError == 1)
{
DrawString(40, 180, "4桁で入力してください。", red);
}
else if (inputError == 2)
{
DrawString(40, 180, "0~9の数字だけを入力してください。", red);
}
else if (inputError == 3)
{
DrawString(40, 180, "同じ数字を2回使わないでください。", red);
}
// ===== 【第2回で追加・変更】判定結果とエラーを画面へ表示 ここまで =====
}
// 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.動作確認
Visual Studio上部を次の設定にします。
| 項目 | 設定 |
|---|---|
| 構成 | Debug |
| プラットフォーム | x64 |
次のキーで実行します。
Ctrl + F5
次の内容を確認してください。
HIT & BLOWと表示される- デバッグ用の正解が4桁で表示される
- 仮の入力として
1234が表示される - HIT数とBLOW数が表示される
- ESCキーで終了できる
- 起動し直すと正解が変化する
日本語が文字化けした場合
デバッグ用正解やエラーメッセージなどの日本語が文字化けした場合だけ、WinMain()の中へ次の1行を追加して確認してください。
ChangeWindowMode(TRUE);より前へ追加します。
// 日本語が文字化けした場合だけ追加する
SetUseCharCodeFormat(DX_CHARCODEFORMAT_UTF8);
ChangeWindowMode(TRUE);
日本語が最初から正しく表示されている場合は、この設定を追加する必要はありません。
追加しても文字化けが直らない場合は、作成したmain.cppの文字コードがUTF-8ではない可能性があります。その場合はこの1行をいったん削除し、ファイルの文字コードを確認してください。
入力エラーを確認する
仮入力を書き換えると、エラー表示も確認できます。
strcpy_s(input, sizeof(input), "123");
4桁で入力してください。
strcpy_s(input, sizeof(input), "12A4");
0~9の数字だけを入力してください。
strcpy_s(input, sizeof(input), "1123");
同じ数字を2回使わないでください。
確認が終わったら、仮入力を1234へ戻します。
strcpy_s(input, sizeof(input), "1234");
5.今回のまとめ
今回は、コンソール版から次の4関数をそのままコピーしました。
MakeProblem()
CheckAnswerError()
CountHit()
CountBlow()
コンソール専用の入力と表示は、DxLib用に作り直しました。
scanf()・入力待ちのwhile
↓
後のUpdate()でマウス入力を作る
printf()・ShowInputError()
↓
Draw()で画面へ表示する
現在は仮入力を判定しています。
次回は画像を読み込み、数字ボタンと4桁の入力欄を画面へ並べます。

No Comments