Skip to main content

第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桁の入力欄を画面へ並べます。