Skip to main content

第4回 入力用GUIを作る!

第4回 数字ボタンを押して入力しよう

前回は、画像を読み込み、数字ボタンと4桁の入力欄を画面へ並べました。

ただし、前回の数字ボタンは画像を表示しているだけです。

数字ボタンを表示する
        ↓
クリックしても何も起こらない

今回は、マウスで数字ボタンをクリックし、押した数字をinputへ追加できるようにします。

この回は、第3回終了時点のmain.cppから作業を始めます。


このページの進め方

このページでは、読む部分とソースを書き換える部分を分けています。

説明
    仕組みを確認する

STEP ○ ここでソースを書き換える!
    main.cppを実際に変更する

動作確認
    ビルドして実行する

先に完成させたい場合は、STEPを順番に進めます。
仕組みを詳しく確認したい場合は、各STEPの前後にある説明も読みます。

ソースを書き換える場所は、コード内のここから追加ここを変更この部分を削除などのコメントで示します。


1.今回の完成状態

今回のプログラムでは、次の操作ができるようになります。

  • 0~9の数字ボタンをクリックする
  • クリックした数字が入力欄へ左から追加される
  • 最大4桁まで入力できる
  • 同じ数字も入力できる
  • 「入力を消す」ボタンで4桁をすべて消せる
  • 1回のクリックで1桁だけ入力される
最初

[*][*][*][*]

3をクリック

[3][*][*][*]

8をクリック

[3][8][*][*]

判定ボタンは、まだ動きません。

HIT・BLOWの判定は次回作ります。

説明1 マウス入力をUpdateで扱う

画面へ画像を表示する処理はDraw()へ書きました。

マウスの状態を調べ、ゲームの変数を書き換える処理はUpdate()へ書きます。

Update()
    マウスの位置を調べる
    左クリックを調べる
    押された数字をinputへ追加する

Draw()
    inputの内容を画像で表示する

マウス入力をDraw()へ書かないようにします。

Draw()は表示、Update()は入力とゲームの更新を担当します。


マウス座標

マウスカーソルがある場所は、X座標とY座標で表します。

画面左上が(0, 0)です。

X座標
    右へ進むほど大きくなる

Y座標
    下へ進むほど大きくなる


GetMousePointで座標を取得する

マウス座標はGetMousePoint()で取得します。

GetMousePoint(&mouseX, &mouseY);

&を付けたmouseXmouseYへ、X座標とY座標を書き込んでもらいます。
アドレスやポインタの詳しい仕組みは、ここでは扱いません。


GetMouseInputでボタン状態を取得する

マウスボタンの状態はGetMouseInput()で取得します。

mouseInput = GetMouseInput();

左ボタンが押されているかは、次の条件で調べます。

(mouseInput & MOUSE_INPUT_LEFT) != 0

ここでの&はビット演算子で、&mouseX&とは別の意味です。ビット演算の詳しい仕組みは、ここでは扱いません。


押している間ではなく、押した瞬間を調べる

Update()は何度も繰り返されるため、押している状態だけを見ると1回のクリックで同じ数字が複数回入ることがあります。

そこで、前回は押しておらず、今回は押しているときだけを「クリックした瞬間」とします。

leftClicked = (mouseInput & MOUSE_INPUT_LEFT) != 0 && (previousMouseInput & MOUSE_INPUT_LEFT) == 0;

Update()の最後で、今回の状態を次回のために保存します。

previousMouseInput = mouseInput;

STEP 1 マウス入力と入力桁数の変数を追加する

変更する場所:グローバル変数を宣言している場所
確認すること:エラーなくビルドできること

マウス座標、前回のマウス状態、現在の入力桁数を保存する変数を追加します。

まず、char input[5];のすぐ下へinputCountを追加します。

// ここに追加
// 現在入力されている桁数
int inputCount;

次に、int blow;のすぐ下へマウス用の変数を追加します。

// ここから追加
// マウスカーソルの座標
int mouseX;
int mouseY;

// 1回前のUpdateで取得したマウス状態
int previousMouseInput;
// ここまで追加

inputCountは、現在の入力桁数と、次に文字を入れる配列の添え字として使います。

inputCount = 2

input[0] = '3'
input[1] = '8'
input[2] = '\0'

STEP 2 IsPointInRect関数を追加する

変更する場所Initialize()より前
確認すること:エラーなくビルドできること

数字ボタンは、画面上の長方形として考えます。

左端   left
上端   top
右端   right
下端   bottom

マウス座標が長方形の中にある条件は、次のとおりです。

left <= mouseX
mouseX < right
top <= mouseY
mouseY < bottom

この処理を関数へまとめます。

IsPointInRect()は、Initialize()より前へ追加します。

// Initialize()より前へ、ここから関数を追加
// 点が長方形の中にあるか調べる
bool IsPointInRect(int pointX, int pointY, int left, int top, int right, int bottom)
{
    return left <= pointX && pointX < right && top <= pointY && pointY < bottom;
}
// ここまで関数を追加

長方形の中ならtrue、外ならfalseを返します。


説明2 数字ボタンの位置と入力文字を求める

描画と同じ座標計算を使う

前回、数字ボタンを描画したときは、まず添え字iから列と行を求めました。

column = i % NUMBER_BUTTON_COLUMNS;

row = i / NUMBER_BUTTON_COLUMNS;

次に、その列と行からボタンの左上座標xyを求めました。

x = NUMBER_BUTTON_START_X + column * (NUMBER_BUTTON_SIZE + NUMBER_BUTTON_GAP);

y = NUMBER_BUTTON_START_Y + row * (NUMBER_BUTTON_SIZE + NUMBER_BUTTON_GAP);

ここまでが、前回の描画で使った一連の計算です。

クリック判定でも、この一連の計算をそのまま使います。

ボタン番号i
    ↓
列と行を求める
    ↓
左上座標x、yを求める

描画とクリック判定で同じ座標計算を使うことで、

画面に見えているボタンの位置
        と
クリックできる判定の位置

がずれないようにできます。


どの数字ボタンが押されたか調べる

左クリックした瞬間だけ、0~9のボタンを順番に調べます。

▲ leftClicked = true
|■ iを0から9まで繰り返す
||・iから列と行を求める
||・列と行からボタンの左上座標を求める
||▲ マウスがi番のボタン内にある
|||・i番の数字ボタンが押されたと判断する
||▼
|■
▼

iは、押された数字と同じ値になります。

0.pngの範囲をクリック
    → iは0

7.pngの範囲をクリック
    → iは7

整数を数字の文字へ変換する

ボタン番号iは整数ですが、inputへ入れるのは文字です。

input[inputCount] = (char)('0' + i);

C/C++では'0''9'が順番に並んでいるため、整数を足すと対応する数字の文字を作れます。

'0' + 3 → '3'

文字を入れたら、入力桁数を1増やします。

inputCount++;

その後、文字列の最後へ'\0'を入れます。

input[inputCount] = '\0';

処理順序は次のとおりです。

input[inputCount]へ文字を入れる
        ↓
inputCountを1増やす
        ↓
新しい末尾へ'\0'を入れる

4桁を超えて入力させない

inputは5要素ですが、数字を保存できるのは4桁までです。

input[0]~input[3]
    4桁の数字

input[4]
    '\0'

そのため、数字を追加する前にinputCountを確認します。

▲ inputCount < INPUT_DIGIT_COUNT
|・押した数字をinputへ追加する
|・inputCountを1増やす
|・文字列の最後へ'\0'を入れる
▼

4桁入力済みなら、数字ボタンを押しても何も追加しません。
実際のC++コードは、STEP 4でUpdate()へまとめて書きます。


同じ数字は判定時にエラーにする

今回は、次の入力も受け付けます。

1123
7777

数字ボタンを押す段階では、重複を禁止しません。

次回、判定ボタンを押したときにCheckAnswerError()を呼び出し、重複エラーとして表示します。

第4回
    とりあえず4桁入力できるようにする

第5回
    入力内容が正しいか確認する

入力処理と入力確認を分けて作ります。


STEP 3 入力の初期化と入力消去処理を追加する

変更する場所Initialize()より前へClearInput()を追加、Initialize()
確認すること:エラーなくビルドできること

仮入力を削除する

前回までは、マウス入力の代わりに"1234"をプログラム内へ直接設定していました。

// この部分を削除
strcpy_s(input, sizeof(input), "1234");

マウスで数字を入力するようになるため、仮入力の処理は削除します。

代わりに、入力欄を空文字列で初期化します。

仮入力を削除した後は、inputCountを0、input[0]\0にして空文字列から始めます。

配列の最初に'\0'を入れると、長さ0の文字列になります。


入力を消す処理を追加する

数字を押し間違えた場合に備え、入力中の数字をすべて消す処理を作ります。

第4回では、右クリックしたときにClearInput()を呼び出します。
ClearInput()では、inputCountを0、input[0]\0へ戻します。

// ここから関数を追加
// 入力中の4桁をすべて消す
void ClearInput(void)
{
    inputCount = 0;
    input[0] = '\0';

    inputError = 0;
    hit = 0;
    blow = 0;
}
// ここまで関数を追加

Initializeを書き換える

Initialize()を、次のように変更します。

void Initialize(void)
{
    // 実行するたびに乱数の並びを変える
    srand((unsigned int)time(NULL));

    // 重複しない4桁の問題を作る
    MakeProblem(answer);

    // ここから変更
    ClearInput();

    mouseX = 0;
    mouseY = 0;
    previousMouseInput = 0;

    SetMouseDispFlag(TRUE);

    // ゲームで使用する画像を読み込む
    imageLoadError = LoadGameImages();
    // ここまで変更
}

前回までの仮入力と、起動直後の判定処理は削除します。


STEP 4 Update関数全体をマウス入力対応へ書き換える

変更する場所:現在のUpdate()
確認すること:左クリックで数字を入力でき、右クリックで入力をすべて消せる

現在のUpdate()を探し、下のコメントが付いた関数へ丸ごと置き換えます。

左クリックした瞬間は数字ボタンの入力に使います。
右クリックした瞬間はClearInput()を呼び出し、入力中の数字をすべて消します。

// この関数を丸ごと置き換える
// 入力確認とゲームの更新を行う
void Update(void)
{
    int mouseInput;
    bool leftClicked;
    bool rightClicked;

    // 現在のマウス座標を取得する
    GetMousePoint(&mouseX, &mouseY);

    // 現在のマウスボタン状態を取得する
    mouseInput = GetMouseInput();

    // 左クリックした瞬間を求める
    leftClicked = (mouseInput & MOUSE_INPUT_LEFT) != 0 && (previousMouseInput & MOUSE_INPUT_LEFT) == 0;

    // 右クリックした瞬間を求める
    rightClicked = (mouseInput & MOUSE_INPUT_RIGHT) != 0 && (previousMouseInput & MOUSE_INPUT_RIGHT) == 0;

    if (leftClicked)
    {
        int i;

        // 0~9の数字ボタンを順番に調べる
        for (i = 0; i < NUMBER_COUNT; i++)
        {
            int column;
            int row;
            int x;
            int y;

            column = i % NUMBER_BUTTON_COLUMNS;

            row = i / NUMBER_BUTTON_COLUMNS;

            x = NUMBER_BUTTON_START_X + column * (NUMBER_BUTTON_SIZE + NUMBER_BUTTON_GAP);

            y = NUMBER_BUTTON_START_Y + row * (NUMBER_BUTTON_SIZE + NUMBER_BUTTON_GAP);

            if (IsPointInRect(mouseX, mouseY, x, y, x + NUMBER_BUTTON_SIZE, y + NUMBER_BUTTON_SIZE))
            {
                // 4桁未満なら数字を追加する
                if (inputCount < INPUT_DIGIT_COUNT)
                {
                    input[inputCount] = (char)('0' + i);

                    inputCount++;

                    input[inputCount] = '\0';
                }

                break;
            }
        }

    }

    // 右クリックしたら入力中の数字をすべて消す
    if (rightClicked)
    {
        ClearInput();
    }

    // 今回の状態を次回の比較用に保存する
    previousMouseInput = mouseInput;
}

STEP 5 Draw関数の入力欄と操作案内を変更する

変更する場所:入力欄と判定ボタンの下
確認すること:第4回の完成画面になる

入力欄の描画をinputCountへ変更する

前回は、strlen()で仮入力の長さを求めていました。

今回は、入力処理で管理しているinputCountを使用します。

// ここから変更
// 4桁の入力欄を描画する
for (int i = 0; i < INPUT_DIGIT_COUNT; i++)
{
    int x;
    int imageHandle;

    // i番目の桁を表示するX座標
    x = INPUT_START_X + i * (INPUT_IMAGE_SIZE + INPUT_IMAGE_GAP);

    // 未入力の場合に表示する画像
    imageHandle = blankImage;

    // 入力済みで、数字が入っている場合
    if (i < inputCount)
    {
        int number;

        // 文字の数字を整数へ変換する
        number = input[i] - '0';

        // 対応する緑色の数字画像を選ぶ
        imageHandle = displayNumberImages[number];
    }

    // 入力欄の画像を80×80で表示する
    DrawGraph(x, INPUT_START_Y, imageHandle, TRUE);
}
// ここまで変更

数字ボタンからしか入力できないため、isdigit()による確認は不要になります。


判定前の表示へ変更する

第3回では、仮入力とHIT・BLOWの結果を表示していました。

第4回では数字の入力までを完成させ、HIT・BLOWの判定は次回作ります。

まず、入力欄の上にある説明を次のように変更します。

// ここを変更
DrawString(40, 85, "数字を4桁入力してください", white);

次に、第3回で作ったHIT・BLOWとエラーメッセージの表示を削除し、次の2行へ置き換えます。

// ここから変更
DrawFormatString(40, 215, white, "入力:%s", input);

DrawString(40, 245, "HITとBLOWの判定は次回作ります。", gray);
// ここまで変更

ここで作った「HITとBLOWの判定は次回作ります。」という表示は、第5回でHIT数とBLOW数の表示へ置き換えます。

第3回から残っている判定ボタンのコメントも、現在の状態に合わせて変更します。

// このコメントを変更
// 判定ボタンはまだ表示だけ
DrawGraph(JUDGE_BUTTON_X, JUDGE_BUTTON_Y, judgeImage, TRUE);

右クリックの操作案内を表示する

入力を消す操作は画面上のボタンではなく、右クリックで行います。

判定ボタンの右側へ、操作方法を文字で表示します。

// ここから追加
// 右クリックで入力を消せることを表示する
DrawString(330, 525, "右クリック:入力を消す", gray);
// ここまで追加

この文字は操作方法の案内で、クリックするボタンではありません。


右側の確認表示を整理する

第3回で追加した右側の仮表示は、マウス座標も表示できるように位置を整理します。

第3回の次の2行を、

DrawString(480, 115, "右側は第6回で", gray);
DrawString(480, 145, "解答履歴に使います。", gray);

次の位置へ変更します。

// この2行を変更
DrawString(480, 85, "右側は第6回で", gray);
DrawString(480, 115, "解答履歴に使います。", gray);

その上へ、マウス座標を表示します。

// ここに追加
// マウス座標をデバッグ表示する
DrawFormatString(500, 35, gray, "マウス:(%d, %d)", mouseX, mouseY);

マウスを動かすと、数値が変化することを確認できます。

この3つの確認表示は、第6回で解答履歴を作るときに削除します。


6.プログラム全体

ここまでの内容をすべて入れたmain.cppは、次のようになります。

#include "DxLib.h"

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.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;

// HIT&BLOWで使用する数字の数
const int NUMBER_COUNT = 10;

// 入力する桁数
const int INPUT_DIGIT_COUNT = 4;

// 入力欄の配置
const int INPUT_START_X = 50;
const int INPUT_START_Y = 110;
const int INPUT_IMAGE_SIZE = 80;
const int INPUT_IMAGE_GAP = 10;

// 数字ボタンの配置
const int NUMBER_BUTTON_START_X = 30;
const int NUMBER_BUTTON_START_Y = 335;
const int NUMBER_BUTTON_SIZE = 70;
const int NUMBER_BUTTON_GAP = 8;
const int NUMBER_BUTTON_COLUMNS = 5;

// 判定ボタンの配置
const int JUDGE_BUTTON_X = 120;
const int JUDGE_BUTTON_Y = 510;
const int JUDGE_BUTTON_WIDTH = 200;
const int JUDGE_BUTTON_HEIGHT = 46;


// ゲームの正解
char answer[5];

// プレイヤーの入力
char input[5];
// ===== 【第4回で追加・変更】現在の入力桁数を保存 ここから =====

// 現在入力されている桁数
int inputCount;
// ===== 【第4回で追加・変更】現在の入力桁数を保存 ここまで =====

// 入力内容のエラー番号
int inputError;

// 判定結果
int hit;
int blow;
// ===== 【第4回で追加・変更】マウス入力の状態を保存 ここから =====

// マウスカーソルの座標
int mouseX;
int mouseY;

// 1回前のUpdateで取得したマウス状態
int previousMouseInput;
// ===== 【第4回で追加・変更】マウス入力の状態を保存 ここまで =====

// 0~9の数字ボタン画像
int numberImages[NUMBER_COUNT];

// 入力欄へ表示する0~9の画像
int displayNumberImages[NUMBER_COUNT];

// 未入力の桁に表示する画像
int blankImage;

// 判定ボタン画像
int judgeImage;

// 画像の読み込み結果
int imageLoadError;


// 重複しない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;
}

// ゲームで使用する画像を読み込む
int LoadGameImages(void)
{
    char fileName[64];
    int i;

    // 0~9の画像を読み込む
    for (i = 0; i < NUMBER_COUNT; i++)
    {
        // 青い数字ボタンのファイル名を作る
        sprintf_s(fileName, sizeof(fileName), "images/%d.png", i);

        numberImages[i] = LoadGraph(fileName);

        if (numberImages[i] == -1)
        {
            return 1;
        }

        // 緑色の数字画像のファイル名を作る
        sprintf_s(fileName, sizeof(fileName), "images/%d%d.png", i, i);

        displayNumberImages[i] = LoadGraph(fileName);

        if (displayNumberImages[i] == -1)
        {
            return 1;
        }
    }

    // 未入力の桁に表示する画像を読み込む
    blankImage = LoadGraph("images/kome.png");

    if (blankImage == -1)
    {
        return 1;
    }

    // 判定ボタン画像を読み込む
    judgeImage = LoadGraph("images/judge.png");

    if (judgeImage == -1)
    {
        return 1;
    }

    return 0;
}

// ===== 【第4回で追加・変更】クリック判定と入力消去の処理を追加 ここから =====
// 点が長方形の中にあるか調べる
bool IsPointInRect(int pointX, int pointY, int left, int top, int right, int bottom)
{
    return left <= pointX && pointX < right && top <= pointY && pointY < bottom;
}

// 入力中の4桁をすべて消す
void ClearInput(void)
{
    inputCount = 0;
    input[0] = '\0';

    inputError = 0;
    hit = 0;
    blow = 0;
}

// ===== 【第4回で追加・変更】クリック判定と入力消去の処理を追加 ここまで =====
// ゲーム開始時に1回だけ実行する
void Initialize(void)
{
    // 実行するたびに乱数の並びを変える
    srand((unsigned int)time(NULL));

    // 重複しない4桁の問題を作る
    MakeProblem(answer);

    // ===== 【第4回で追加・変更】入力とマウスの初期状態を設定 ここから =====
    ClearInput();

    mouseX = 0;
    mouseY = 0;
    previousMouseInput = 0;

    SetMouseDispFlag(TRUE);
    // ===== 【第4回で追加・変更】入力とマウスの初期状態を設定 ここまで =====

    // ゲームで使用する画像を読み込む
    imageLoadError = LoadGameImages();

}

// 入力確認とゲームの更新を行う
void Update(void)
{
    // ===== 【第4回で追加・変更】数字ボタンのクリック入力と右クリック消去を追加 ここから =====
    int mouseInput;
    bool leftClicked;
    bool rightClicked;

    // 現在のマウス座標を取得する
    GetMousePoint(&mouseX, &mouseY);

    // 現在のマウスボタン状態を取得する
    mouseInput = GetMouseInput();

    // 左クリックした瞬間を求める
    leftClicked = (mouseInput & MOUSE_INPUT_LEFT) != 0 && (previousMouseInput & MOUSE_INPUT_LEFT) == 0;

    // 右クリックした瞬間を求める
    rightClicked = (mouseInput & MOUSE_INPUT_RIGHT) != 0 && (previousMouseInput & MOUSE_INPUT_RIGHT) == 0;

    if (leftClicked)
    {
        int i;

        // 0~9の数字ボタンを順番に調べる
        for (i = 0; i < NUMBER_COUNT; i++)
        {
            int column;
            int row;
            int x;
            int y;

            column = i % NUMBER_BUTTON_COLUMNS;

            row = i / NUMBER_BUTTON_COLUMNS;

            x = NUMBER_BUTTON_START_X + column * (NUMBER_BUTTON_SIZE + NUMBER_BUTTON_GAP);

            y = NUMBER_BUTTON_START_Y + row * (NUMBER_BUTTON_SIZE + NUMBER_BUTTON_GAP);

            if (IsPointInRect(mouseX, mouseY, x, y, x + NUMBER_BUTTON_SIZE, y + NUMBER_BUTTON_SIZE))
            {
                // 4桁未満なら数字を追加する
                if (inputCount < INPUT_DIGIT_COUNT)
                {
                    input[inputCount] = (char)('0' + i);

                    inputCount++;

                    input[inputCount] = '\0';
                }

                break;
            }
        }

    }

    // 右クリックしたら入力中の数字をすべて消す
    if (rightClicked)
    {
        ClearInput();
    }

    // 今回の状態を次回の比較用に保存する
    previousMouseInput = mouseInput;
    // ===== 【第4回で追加・変更】数字ボタンのクリック入力と右クリック消去を追加 ここまで =====
}

// ゲーム画面を描画する
void Draw(void)
{
    unsigned int white = GetColor(255, 255, 255);

    unsigned int yellow = GetColor(255, 255, 0);

    unsigned int red = GetColor(255, 80, 80);

    unsigned int gray = GetColor(160, 160, 160);

    // ゲームタイトルを表示する
    DrawString(40, 25, "HIT & BLOW", white);

    // 完成時には削除する正解表示
    DrawFormatString(40, 55, yellow, "デバッグ用正解:%s", answer);

    // 画像の読み込みに失敗していた場合
    if (imageLoadError != 0)
    {
        DrawString(40, 110, "画像を読み込めませんでした。", red);

        DrawString(40, 140, "imagesフォルダーの場所を確認してください。", red);

        return;
    }

    // ===== 【第4回で追加・変更】入力中の数字と操作案内を表示 ここから =====
    DrawString(40, 85, "数字を4桁入力してください", white);

    // 4桁の入力欄を描画する
    for (int i = 0; i < INPUT_DIGIT_COUNT; i++)
    {
        int x;
        int imageHandle;

        // i番目の桁を表示するX座標
        x = INPUT_START_X + i * (INPUT_IMAGE_SIZE + INPUT_IMAGE_GAP);

        // 未入力の場合に表示する画像
        imageHandle = blankImage;

        // 入力済みで、数字が入っている場合
        if (i < inputCount)
        {
            int number;

            // 文字の数字を整数へ変換する
            number = input[i] - '0';

            // 対応する緑色の数字画像を選ぶ
            imageHandle = displayNumberImages[number];
        }

        // 入力欄の画像を80×80で表示する
        DrawGraph(x, INPUT_START_Y, imageHandle, TRUE);
    }

    DrawFormatString(40, 215, white, "入力:%s", input);

    DrawString(40, 245, "HITとBLOWの判定は次回作ります。", gray);

    DrawString(40, 300, "数字ボタン", white);

    // 0~9の数字ボタンを描画する
    for (int i = 0; i < NUMBER_COUNT; i++)
    {
        int column;
        int row;
        int x;
        int y;

        // 5個ずつ並べるための列と行を求める
        column = i % NUMBER_BUTTON_COLUMNS;

        row = i / NUMBER_BUTTON_COLUMNS;

        // 列と行から画面座標を求める
        x = NUMBER_BUTTON_START_X + column * (NUMBER_BUTTON_SIZE + NUMBER_BUTTON_GAP);

        y = NUMBER_BUTTON_START_Y + row * (NUMBER_BUTTON_SIZE + NUMBER_BUTTON_GAP);

        // 数字ボタンを70×70で表示する
        DrawGraph(x, y, numberImages[i], TRUE);
    }

    // 判定ボタンはまだ表示だけ
    DrawGraph(JUDGE_BUTTON_X, JUDGE_BUTTON_Y, judgeImage, TRUE);

    // 右クリックで入力を消せることを表示する
    DrawString(330, 525, "右クリック:入力を消す", gray);

    // マウス座標をデバッグ表示する
    DrawFormatString(500, 35, gray, "マウス:(%d, %d)", mouseX, mouseY);

    DrawString(480, 85, "右側は第6回で", gray);

    DrawString(480, 115, "解答履歴に使います。", gray);
    // ===== 【第4回で追加・変更】入力中の数字と操作案内を表示 ここまで =====
}

// 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;
}

7.動作確認

次の操作を確認してください。

数字入力

  • 数字ボタンを1回クリックすると、1桁だけ入力される
  • 押した数字が左から順番に表示される
  • 4桁まで入力できる
  • 5個目の数字は追加されない
  • 0も入力できる
  • 同じ数字も入力できる

クリック

  • 左ボタンを少し長く押しても、同じ数字が連続入力されない
  • ボタンとボタンの隙間を押しても入力されない
  • マウス座標の表示がカーソルに合わせて変化する

入力消去

  • 「入力を消す」を押すと4桁がすべて消える
  • 1桁だけ入力した状態でも消せる
  • 何も入力していない状態で押しても問題ない

8.うまく動かない場合

1回のクリックで同じ数字が何個も入る

前回のマウス状態を確認しているか調べます。

leftClicked = (mouseInput & MOUSE_INPUT_LEFT) != 0 && (previousMouseInput & MOUSE_INPUT_LEFT) == 0;

Update()の最後で、今回の状態を保存しているかも確認します。

previousMouseInput = mouseInput;

画像とクリック位置がずれている

描画とクリック判定で、同じ定数と座標計算を使っているか確認します。

NUMBER_BUTTON_START_X
NUMBER_BUTTON_START_Y
NUMBER_BUTTON_SIZE
NUMBER_BUTTON_GAP
NUMBER_BUTTON_COLUMNS

4桁目を入力すると表示がおかしくなる

文字を追加したあと、文字列の最後へ'\0'を入れているか確認します。

input[inputCount] = '\0';

9.今回のまとめ

今回は、マウスで数字ボタンを操作できるようにしました。

GetMousePoint()
    マウス座標を取得する

GetMouseInput()
    マウスボタンの状態を取得する

IsPointInRect()
    マウスが長方形の中にあるか調べる

1回のクリックで1桁だけ入力するため、前回と今回のマウス状態を比較しました。

前回は押されていない
        かつ
今回は押されている
        ↓
クリックした瞬間

押した数字は、整数から文字へ変換してinputへ追加しました。

整数の3
    ↓
文字の'3'
    ↓
inputへ追加

現在は4桁を入力できますが、入力内容の確認とHIT・BLOWの判定はまだ行いません。

次回は、判定ボタンをクリックできるようにし、CheckAnswerError()CountHit()CountBlow()を呼び出します。