Skip to main content

第3回 ゲーム画面の作成

第3回 画像を使ってゲーム画面を作ろう

前回は、コンソール版から次の4つの関数を移し、仮入力"1234"のHIT数とBLOW数を画面へ表示しました。

MakeProblem()
CheckAnswerError()
CountHit()
CountBlow()

現在のゲームは、まだ文字だけで表示されています。

今回は、配布した画像を読み込み、次の部分を画像へ変更します。

4桁の入力欄
0~9の数字ボタン
判定ボタン

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


このページの進め方

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

説明
    仕組みを確認する

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

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

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

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


1.今回の完成状態

実行すると、4桁の入力欄、0~9の数字ボタン、判定ボタンが画像で表示されます。

4桁の入力欄には、前回プログラム内へ設定した仮入力"1234"を画像で表示します。

数字ボタンと判定ボタンは、まだクリックしても反応しません。

マウス操作は次回作ります。


説明1 画像ファイルを使う準備

画像を置く場所

このページで配布しているimages.zipをダウンロードして展開すると、imagesフォルダーが入っています。

images
├─ 0.png
├─ 00.png
├─ 1.png
├─ 11.png
├─ ...
├─ 9.png
├─ 99.png
├─ kome.png
└─ judge.png

imagesフォルダーは、Visual Studioのプロジェクトファイルがあるフォルダーへ置きます。

プロジェクトファイルとは、拡張子が.vcxprojのファイルです。

HitAndBlow
├─ HitAndBlow.vcxproj
├─ main.cpp
└─ images
   ├─ 0.png
   ├─ 00.png
   └─ ...

この教材の作り方では、通常は.vcxprojmain.cppが同じフォルダーにあります。

相対パス

画像を読み込むときは、ファイルの場所を文字列で指定します。

"images/0.png"

これは、現在の作業フォルダーから見て、

imagesフォルダーの中にある0.png

という意味です。

このように、基準となるフォルダーからの位置でファイルを指定する方法を相対パスと呼びます。

プロジェクトフォルダー
    └─ images
        └─ 0.png

フォルダー名やファイル名が間違っていると、画像を読み込めません。


今回使用する画像

今回使用する画像は、次の4種類です。

ファイル 使用目的
0.png9.png クリックする数字ボタン
00.png99.png 入力欄へ表示する数字
kome.png まだ入力されていない桁
judge.png 判定ボタン

0.png00.pngは、同じ数字でも使用目的が違います。

0.png
    数字を入力するための青いボタン

00.png
    入力済みの数字を表す緑色の画像

説明2 画像ハンドルとは

DxLibでは、LoadGraph()を使って画像を読み込みます。

int imageHandle;

imageHandle = LoadGraph("images/0.png");

LoadGraph()が返す値は、画像そのものではありません。

DxLibが読み込んだ画像を管理するための番号です。

この番号を画像ハンドルと呼びます。

画像ファイルを読み込む
        ↓
DxLibが画像を管理する
        ↓
画像を指定するための番号が返る

画像を描画するときは、ファイル名ではなく画像ハンドルを使います。 名前で全部覚えておくの大変なんで、プログラム内では番号にしておくんだね。
学生の学籍番号とか出席番号みたいなもんです。
(名前も顔も覚えてなくても、番号で授業中に当てられるもんね)

DrawGraph(40, 300, imageHandle, TRUE);

読み込みに失敗した場合

LoadGraph()は、画像を読み込めなかった場合に-1を返します。

imageHandle = LoadGraph("images/0.png");

if (imageHandle == -1)
{
    // 読み込みに失敗した
}

よくある原因は次のとおりです。

  • imagesフォルダーを置く場所が違う
  • ファイル名が違う
  • 拡張子が違う
  • フォルダー名をimageとしている
  • ZIPファイルを展開していない

今回は、どれか1枚でも読み込めなかった場合、画面へエラーメッセージを表示します。


STEP 1 画像と画面配置に必要な宣言を追加する

変更する場所:画面サイズなどの定数とグローバル変数を宣言している場所
確認すること:エラーなくビルドできること

画像番号の配列

数字画像は、0~9まで10枚あります。

1枚ずつ別の変数にすると、同じような変数が10個必要になります。

int image0;
int image1;
int image2;
// ...

そこで、0~9の画像を配列へまとめます。

int numberImages[10];

入力欄へ表示する緑色の数字も配列へまとめます。

int displayNumberImages[10];

配列の添え字と数字を同じにします。

numberImages[0] → 0.png
numberImages[1] → 1.png
numberImages[2] → 2.png
       ...
numberImages[9] → 9.png

この形にしておけば、for文でまとめて処理できます。


画面配置用の定数

数字画像を描画する場所は、次回のマウス当たり判定でも使用します。

描画と当たり判定で違う数字を書いてしまうと、画像とクリックできる場所がずれます。

そこで、座標や大きさを定数としてまとめます。

画面サイズの定数の下へ追加します。

// ここから追加
// HIT&BLOWで使用する数字の数(0~9)
const int NUMBER_COUNT = 10;

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

// 入力欄の配置
// 1個目の入力欄を置く左上座標
const int INPUT_START_X = 50;
const int INPUT_START_Y = 110;

// 入力欄1個の大きさ
const int INPUT_IMAGE_SIZE = 80;

// 入力欄どうしのすき間
const int INPUT_IMAGE_GAP = 10;

// 数字ボタンの配置
// 0のボタンを置く左上座標
const int NUMBER_BUTTON_START_X = 30;
const int NUMBER_BUTTON_START_Y = 335;

// 数字ボタン1個の大きさ
const int NUMBER_BUTTON_SIZE = 70;

// 数字ボタンどうしのすき間
const int NUMBER_BUTTON_GAP = 8;

// 数字ボタンを1行に何個並べるか
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;
// ここまで追加

配置に使う座標・大きさ・間隔は、次の図で確認できます。

描画と次回のクリック判定で同じ定数を使うことで、画像と判定位置のずれを防ぎます。


画像ハンドルを保存する変数

ゲームで使用する変数の下へ、次の変数を追加します。

// ここから追加
// 0~9の数字ボタン画像
int numberImages[NUMBER_COUNT];

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

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

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

// 画像の読み込み結果
int imageLoadError;
// ここまで追加

imageLoadErrorは、次の意味で使用します。

意味
0 すべて読み込めた
1 どれかの画像を読み込めなかった

STEP 2 LoadGameImages関数を追加する

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

ファイル名を作る方法

数字画像はimages/0.pngimages/9.pngのように規則的な名前になっています。

for文でまとめて読み込むため、sprintf_s()でファイル名を作ります。

char fileName[64];
sprintf_s(fileName, sizeof(fileName), "images/%d.png", 3);

この例では、fileNameimages/3.pngが入ります。

sprintf_s()を使うため、#include <stdio.h>も追加します。

// ここから追加
#include <stdio.h>
// ここまで追加

LoadGameImages関数

画像の読み込み処理をLoadGameImages()へまとめます。

// ここから関数を追加
// ゲームで使用する画像を読み込む
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);

        // -1なら読み込み失敗
        if (numberImages[i] == -1)
        {
            return 1;
        }

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

        // 作ったファイル名の画像を読み込む
        displayNumberImages[i] = LoadGraph(fileName);

        // -1なら読み込み失敗
        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;
}
// ここまで関数を追加

読み込み処理の流れ

■ iを0から9まで繰り返す
|・images/i.pngを読み込む
|・images/ii.pngを読み込む
■

・kome.pngを読み込む
・judge.pngを読み込む

戻り値は、ほかのエラー確認と同じ考え方です。

0 → 成功
1 → 失敗

STEP 3 Initialize関数へ画像読み込みを追加する

変更する場所:現在のInitialize()の最後、閉じかっこ}の直前
確認すること:エラーなくビルドできること

前回までのInitialize()の処理は消しません。
関数の最後へ、次の1行だけ追加します。

// Initialize()の最後へ追加
imageLoadError = LoadGameImages();

画像は、ゲームの開始時に1回だけ読み込みます。

Initialize()
    ↓
LoadGameImages()
    ↓
画像ハンドルを配列へ保存する

Draw()の中でLoadGraph()を呼ばないようにします。

Draw()は何度も呼ばれるため、毎回画像を読み込む必要はありません。


説明3 配布画像をそのままの大きさで描画する

今回の配布画像は、ゲーム画面へ表示する大きさに調整してあります。

画像 大きさ
0.png9.png 70×70
00.png99.png 80×80
kome.png 80×80
judge.png 200×46

そのため、画像を拡大・縮小せず、DrawGraph()でそのまま表示します。

DrawGraph(40, 300, numberImages[0], TRUE);
引数 内容
40 左上のX座標
300 左上のY座標
numberImages[0] 表示する画像ハンドル
TRUE 透過部分を透明にする

画像の大きさは、配置計算と後のクリック判定に使用する定数と同じです。

const int INPUT_IMAGE_SIZE = 80;
const int NUMBER_BUTTON_SIZE = 70;
const int JUDGE_BUTTON_WIDTH = 200;
const int JUDGE_BUTTON_HEIGHT = 46;

STEP 4 入力欄を画像で表示する

変更する場所Draw()の入力欄を表示する場所
確認すること:4桁の入力欄が画像で表示される

画像を追加する前に、第2回までの文字表示を整理します。

第2回の仮入力表示を画像用の配置へ変更する

第2回では、入力内容を文字で表示していました。

DrawString(40, 30, "HIT & BLOW", white);
DrawFormatString(40, 80, yellow, "デバッグ用正解:%s", answer);
DrawFormatString(40, 120, white, "仮の入力   :%s", input);

この3行を、次の3行へ置き換えます。

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

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

DrawString(40, 85, "仮入力を画像で表示", white);
// ここまで変更

文字で表示していたinputは削除し、この後で画像として表示します。

画像を読み込めなかった場合の表示を追加する

デバッグ用正解を表示した直後へ、次の処理を追加します。

// ここから追加
// 画像の読み込みに失敗していた場合
if (imageLoadError != 0)
{
    DrawString(40, 110, "画像を読み込めませんでした。", red);
    DrawString(40, 140, "imagesフォルダーの場所を確認してください。", red);
    return;
}
// ここまで追加

画像を読み込めなかった場合は、エラーメッセージを表示して、その回のDraw()を終了します。

前回の仮入力は、文字列"1234"です。

input[0] → '1'
input[1] → '2'
input[2] → '3'
input[3] → '4'

入力済みの桁には緑色の数字画像を表示します。

未入力の桁にはkome.pngを表示します。

// ここから追加
int inputLength;

// inputに何文字入っているか調べる
inputLength = (int)strlen(input);

// 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 < inputLength && isdigit((unsigned char)input[i]))
    {
        int number;

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

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

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

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

input[i]に入っているのは、整数ではなく文字です。

'3'

配列の添え字として使うには、整数の3へ変換する必要があります。

number = input[i] - '0';
'3' - '0'
    ↓
3

これで、対応する画像を選べます。

displayNumberImages[3]

HIT・BLOWとエラー表示を入力画像の下へ移動する

第2回の表示位置のままだと、80×80の入力画像とHIT・BLOW表示が重なります。

Draw()内の表示位置を、次のように変更します。

表示 第2回まで 第3回
HIT Y=180 Y=215
BLOW Y=220 Y=245
入力エラーメッセージ Y=180 Y=215

X座標は40のままです。

Draw()にあるinputErrorの判定部分を、次のコードへ丸ごと置き換えます。

// この部分を丸ごと置き換える
// 入力内容に問題がなければ判定結果を表示する
if (inputError == 0)
{
    DrawFormatString(40, 215, white, "HIT :%d", hit);
    DrawFormatString(40, 245, white, "BLOW:%d", blow);
}
else if (inputError == 1)
{
    DrawString(40, 215, "4桁で入力してください。", red);
}
else if (inputError == 2)
{
    DrawString(40, 215, "0~9の数字だけを入力してください。", red);
}
else if (inputError == 3)
{
    DrawString(40, 215, "同じ数字を2回使わないでください。", red);
}

STEP 5 数字ボタンを5個ずつ2段に並べる

変更する場所Draw()の入力欄描画の後
確認すること:数字ボタンが5×2で表示される

数字ボタンは、次のように並べます。

0 1 2 3 4
5 6 7 8 9

添え字から列と行を求めます。

列はi % NUMBER_BUTTON_COLUMNS、行はi / NUMBER_BUTTON_COLUMNSで求めます。実際の描画処理は下のコードへまとめます。

NUMBER_BUTTON_COLUMNSは5なので、結果は次のようになります。

i i % 5 i / 5
0 0 0
4 4 0
5 0 1
9 4 1

描画処理は次のようになります。

まず、数字ボタンの上へ見出しを表示します。

// ここに追加
DrawString(40, 300, "数字ボタン", white);

その直後へ、0~9の画像を並べるfor文を追加します。

// ここから追加
// 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);
}
// ここまで追加

0~9のボタンを1個ずつ描くのではなく、同じ処理をfor文で10回繰り返しています。


STEP 6 判定ボタンを表示する

変更する場所Draw()の数字ボタン描画の後
確認すること:判定ボタンが表示される

判定ボタンは、judge.pngを使用します。

// ここから追加
// 判定ボタンを表示する
DrawGraph(JUDGE_BUTTON_X, JUDGE_BUTTON_Y, judgeImage, TRUE);
// ここまで追加

judge.pngは200×46なので、そのままの大きさで表示します。

最後に、第6回で解答履歴を表示する場所が分かるよう、右側へ仮の文字を表示します。

Draw()の色を作っている場所へ、grayを追加します。

// ここに追加
unsigned int gray = GetColor(160, 160, 160);

判定ボタンを描画した後へ、次の2行を追加します。

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

この文字は、第6回で実際の解答履歴へ置き換えます。

この回では、判定ボタンをクリックしても何も起こりません。


確認 Draw関数の完成形

STEP 4~STEP 6で、Draw()が次の状態になっていることを確認します。

  • 第2回の文字による仮入力表示が削除されている
  • 画像読み込みエラーの表示が追加されている
  • 入力欄が画像で表示されている
  • HIT・BLOWとエラー表示が入力画像と重ならない位置へ移動している
  • 0~9の数字ボタンが表示されている
  • 判定ボタンが表示されている
  • 右側に第6回用の仮表示がある

ここではDraw()全体を書き直しません。
完成したmain.cppは、この後の「プログラム全体」で確認できます。


7.プログラム全体

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

#include "DxLib.h"

#include <ctype.h>
// ===== 【第3回で追加・変更】画像ファイル名を作るためのヘッダーを追加 ここから =====
#include <stdio.h>
// ===== 【第3回で追加・変更】画像ファイル名を作るためのヘッダーを追加 ここまで =====
#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;

// ===== 【第3回で追加・変更】画像と画面配置に必要な定数を追加 ここから =====
// 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;

// ===== 【第3回で追加・変更】画像と画面配置に必要な定数を追加 ここまで =====
// ゲームの正解
char answer[5];

// プレイヤーの入力
char input[5];

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

// 判定結果
int hit;
int blow;
// ===== 【第3回で追加・変更】ゲームで使う画像ハンドルを追加 ここから =====

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

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

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

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

// 画像の読み込み結果
int imageLoadError;
// ===== 【第3回で追加・変更】ゲームで使う画像ハンドルを追加 ここまで =====

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

// ===== 【第3回で追加・変更】ゲーム画像を読み込む処理を追加 ここから =====
// ゲームで使用する画像を読み込む
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;
}

// ===== 【第3回で追加・変更】ゲーム画像を読み込む処理を追加 ここまで =====
// ゲーム開始時に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);
    }
// ===== 【第3回で追加・変更】Initialize()で画像を読み込む ここから =====

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

// ===== 【第3回で追加・変更】Initialize()で画像を読み込む ここまで =====
}

// 入力確認とゲームの更新を行う
void Update(void)
{
    // キーボードやマウスの入力処理を書く

    // ゲームの更新処理を書く
}

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

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

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

    // ===== 【第3回で追加・変更】画像を使ったゲーム画面へ変更 ここから =====
    unsigned int gray = GetColor(160, 160, 160);

    int inputLength;

    // ゲームタイトルを表示する
    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;
    }

    DrawString(40, 85, "仮入力を画像で表示", white);

    // inputに何文字入っているか調べる
    inputLength = (int)strlen(input);

    // 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 < inputLength && isdigit((unsigned char)input[i]))
        {
            int number;

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

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

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

    // 入力内容に問題がなければ判定結果を表示する
    if (inputError == 0)
    {
        DrawFormatString(40, 215, white, "HIT :%d", hit);

        DrawFormatString(40, 245, white, "BLOW:%d", blow);
    }
    else if (inputError == 1)
    {
        DrawString(40, 215, "4桁で入力してください。", red);
    }
    else if (inputError == 2)
    {
        DrawString(40, 215, "0~9の数字だけを入力してください。", red);
    }
    else if (inputError == 3)
    {
        DrawString(40, 215, "同じ数字を2回使わないでください。", red);
    }

    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(480, 115, "右側は第6回で", gray);

    DrawString(480, 145, "解答履歴に使います。", gray);
    // ===== 【第3回で追加・変更】画像を使ったゲーム画面へ変更 ここまで =====
}

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

8.動作確認

imagesフォルダーを配置した状態で、プログラムを実行します。

次のことを確認してください。

  • 1234が緑色の数字画像で表示される
  • 0~9の数字ボタンが5個ずつ2段に並ぶ
  • 数字ボタンが70×70で表示される
  • 判定ボタンが表示される
  • HIT数とBLOW数が表示される
  • 画像が画面の外へはみ出していない
  • ESCキーで終了できる

9.画像が表示されない場合

次のメッセージが表示された場合は、画像の場所を確認します。

画像を読み込めませんでした。
imagesフォルダーの場所を確認してください。

確認する場所は次のとおりです。

ZIPを展開したか

間違い

HitAndBlow
└─ images.zip
正しい

HitAndBlow
└─ images
   ├─ 0.png
   └─ ...

imagesフォルダーが二重になっていないか

間違い

HitAndBlow
└─ images
   └─ images
      ├─ 0.png
      └─ ...
正しい

HitAndBlow
└─ images
   ├─ 0.png
   └─ ...

ファイル名を変更していないか

0.png
00.png
kome.png
judge.png

ファイル名は変更せず、そのまま使用します。


10.今回のまとめ

今回は、画像を読み込み、HIT&BLOWのゲーム画面を作りました。

LoadGraph()
    画像を読み込み、画像ハンドルを受け取る

DrawGraph()
    読み込んだ画像をそのままの大きさで表示する

0~9の画像は配列へまとめ、for文で読み込みと描画を行いました。

配列
    似た役割の画像をまとめる

for文
    同じ形の処理を繰り返す

添え字
    数字と画像を対応させる

現在は、プログラムの中へ設定した仮入力"1234"を画像で表示しています。

次回は、数字ボタンへマウスの当たり判定を付け、クリックした数字をinputへ追加します。