Skip to main content

第2回 文字でステージを表示しよう

第2回 文字でステージを表示しよう

前回確認したコスコマンのステージを、今回はDxLibのウィンドウへ表示します。

目標は次の画面です。

##########
#        #
#   .    #
#   $    #
#   @    #
#        #
##########

まだ@は動きません。まずは、数字で持っているステージを文字へ変えて描くところまで作ります。

Mapから文字へ


1.まず、プログラム全体の流れを見よう

ステージを表示するまでの大まかな流れは、次のようになります。

// ステージのデータと関数を用意する
int WINAPI WinMain(...)
{
    // ゲームで使う変数とDxLibの準備

    // ゲーム開始時に1回だけ
    ステージを読み込む;

    while (ゲーム中)
    {
        画面を消す;
        ステージを描く;
        画面を表示する;
    }

    // ゲーム終了時に1回だけ
    後片付けをする;
}
// ステージを読み込む関数
// ステージを描く関数

今回の大きな流れは、

ゲーム開始時に1回
        ↓
ゲーム中に繰り返す
        ↓
ゲーム終了時に1回

です。


2.DxLibのウィンドウを表示しよう

main.cppを開いて、まずはDxLibのウィンドウを表示します。
ほんとに、Window出すだけです。
次のコードを書いて実行してみましょう。

#include "DxLib.h"

#define SCREEN_WIDTH  720
#define SCREEN_HEIGHT 600

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    ChangeWindowMode(TRUE);
    SetGraphMode(SCREEN_WIDTH, SCREEN_HEIGHT, 32);
    SetMainWindowText("Costco Man");

    if (DxLib_Init() != 0)
        return -1;

    SetDrawScreen(DX_SCREEN_BACK);

    while (ProcessMessage() == 0)
    {
        ClearDrawScreen();

        DrawString(24, 24, "COSTCO MAN", GetColor(255, 255, 255));

        ScreenFlip();
    }

    DxLib_End();
    return 0;
}

この時点でも、

DxLibを使えるようにする
        ↓
whileの中を繰り返す
        ↓
DxLibを終了する

このソースコードをビルドして実行することで、一番単純なDxLibのWindowを出すだけってプログラムが動きます。

実行して確認

ウィンドウが開いて、左上にCOSTCO MANと出ればOKです。


3.1マスの種類に名前を付けよう

ステージの1マスには、床、壁、荷物、ゴール、コスコマンなどが入ります。

プログラムの中では、それぞれを0~7の数字で表します。

まず、ステージの最大サイズを決めます。

追加する場所:SCREEN_HEIGHTの下
ここに置く理由:このあとMap構造体の配列サイズとして使うので、Mapを定義するより前に用意します。

#define MAX_STAGE_WIDTH  10
#define MAX_STAGE_HEIGHT 10

その下へ、1マスの種類を表すenum OBJNAMEを追加します。

ここに置く理由:ステージ全体で使う名前なので、WinMain()の中ではなく、どの関数からでも使える場所へ置きます。

enum OBJNAME
{
    FLOOR = 0,
    WALL = 1,
    LUGG = 2,
    GOAL = 3,
    HUMAN = 4,
    HUMAN_ON_GOAL = 5,
    LUGG_ON_GOAL = 6,
    OUTSIDE = 7
};

enumを使うと、処理を書くときに、

if (obj == WALL)

のように意味が分かる名前で比較できます。

数字と意味、表示文字の対応は次の通りです。

値 名前 意味 文字
0 FLOOR 床
1 WALL 壁 #
2 LUGG 荷物 $
3 GOAL ゴール .
4 HUMAN コスコマン @
5 HUMAN_ON_GOAL ゴール上のコスコマン +
6 LUGG_ON_GOAL ゴール上の荷物 *
7 OUTSIDE ステージ外

この記号の並びは、Unix系のフリーソフトの倉庫番系のステージ表現で使われてきたXsokoban形式と同じ考え方です。
(ということで、普通に記号読んで数字に直すようにすればよかった。そうすれば、マップそのままネットからコピペできたのに!アドリブで作ってるとたびたびこういうことが起こります。設計って大事だね)


4.ステージ全体をMapで持とう

ステージは、横幅、高さ、2次元配列の3つをまとめて扱います。

追加する場所:enum OBJNAMEの下
ここに置く理由:このあとMap型のステージデータや変数を作るので、それより先に「Mapとは何か」を定義しておきます。

struct Map
{
    int stage_width;
    int stage_height;
    int Dat[MAX_STAGE_HEIGHT][MAX_STAGE_WIDTH];
};

構造体を定義するときはstruct Mapと書きます。

変数を作るときは、

Map currentMap;

のようにMapを型名として使います。

Dat[y][x]には、Y行目・X列目のマスの数字が入ります。


5.Mapの数字を文字へ変える表を作ろう

画面には数字ではなく、#、$、@などの文字を表示します。

そこで、Mapの数字と表示文字を同じ番号で対応させます。

追加する場所:Map構造体の下
ここに置く理由:この配列はステージを描く関数から使う共通データなので、WinMain()の外へ置きます。

const char OBJECT_CHAR[] = {
    ' ',
    '#',
    '$',
    '.',
    '@',
    '+',
    '*',
    ' '
};

enum OBJNAMEと同じ順番です。見やすいように縦に改行していますが、改行しないで横に並べても問題ないです。

例えば、

OBJECT_CHAR[2]

なら'$'、

OBJECT_CHAR[4]

なら'@'が取り出せます。

つまり、

Mapの数字
    ↓
同じ番号でOBJECT_CHARを見る
    ↓
表示する文字

です。
(この変換ルールが、記号→数字、数字→記号でできればマップを記号で書いといて、数字にして読み込むもできるし。何なら、記号のままゲーム進めてもよかったなぁ。)


6.最初のステージを用意しよう

次に、ゲーム開始時のステージデータを用意します。

追加する場所:OBJECT_CHARの下
ここに置く理由:START_STAGEはMap型のデータなので、Mapを定義したあとに置きます。ゲーム中に書き換える変数ではないので、WinMain()の外へ置きます。

const Map START_STAGE = {
    10, 7,
    {
        { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
        { 1, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
        { 1, 0, 0, 0, 3, 0, 0, 0, 0, 1 },
        { 1, 0, 0, 0, 2, 0, 0, 0, 0, 1 },
        { 1, 0, 0, 0, 4, 0, 0, 0, 0, 1 },
        { 1, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
        { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }
    }
};

数字だけを見ると、

1 1 1 1 1 1 1 1 1 1
1 0 0 0 0 0 0 0 0 1
1 0 0 0 3 0 0 0 0 1
1 0 0 0 2 0 0 0 0 1
1 0 0 0 4 0 0 0 0 1
1 0 0 0 0 0 0 0 0 1
1 1 1 1 1 1 1 1 1 1

です。

対応表を見れば、3がゴール、2が荷物、4がコスコマンだと分かります。(わかんねぇよ。って突っ込みなしね!やっぱ記号の方が。。。笑)


7.今回使う関数を先に宣言しよう

今回、このあと使う関数は2つです。

LoadStage()
    元のステージを、ゲーム中に使うMapへコピーする

DrawStageText()
    Mapの数字を文字へ変えて画面に描く

通常、関数は、定義が書かれた順に使えるようになります。(ソースコードを読んでいて、読んでる時点で知らない関数(出てきていない関数)は使えない!)

しかし、この2つの関数の中身は、WinMain()より下に書きます。

WinMain()から先に呼び出せるように、START_STAGEの下、WinMain()の前へプロトタイプ宣言を書きます。

void LoadStage(Map* map);
void DrawStageText(Map* map, int fontHandle);

プロトタイプ宣言は、

この名前と形の関数があります

と、先にコンパイラへ知らせるためのものです。
これを書いておくと、中身はともかく関数があることだけはその時点で知ることができるようになるので、下の方に中身(定義)を並べておけば、定義する順番はさほど気にしなくてもよくなります。
また、呼び出しはそのソースコードの中のプロトタイプ宣言以下であればどこでも(大体ね)できるようになります。


8.元のステージを、遊ぶためのMapへコピーしよう

このあとコスコマンを動かすと、Mapの中身は書き換わります。

元データまで直接書き換えると、最初の配置へ戻したいときに元データがなくなっていて困ります。

そこで、

START_STAGE
  最初の配置。書き換えない
        ↓ コピー
currentMap
  ゲーム中に書き換える

と分けます。この考え方は、元のデータ(設計)と、実際使うデータ(実データ)を分けておくだけですが、ゲームを作っているといろいろなところで出てきます!

元ステージとプレイ中Map

8-1.ゲーム中に使うMapを用意する

追加する場所:WinMain()の最初
ここに置く理由:currentMapは、ゲーム開始時の読み込みから、ゲーム中の描画までWinMain()が動いている間ずっと使う変数だからです。

Map currentMap;

8-2.LoadStage()の中身を作る

追加する場所:WinMain()の下
ここに置く理由:WinMain()を先に読みやすくして、実際の処理を行う関数の中身をそのあとへまとめます。実行されるのは、LoadStage()を呼び出したときです。

void LoadStage(Map* map)
{
    // 元ステージの横幅を、ゲーム中に使うMapへコピーする
    map->stage_width = ________________________________;

    // 元ステージの高さを、ゲーム中に使うMapへコピーする
    map->stage_height = ________________________________;

    // ステージの上から下まで、行を順番にコピーする
    for (int y = 0; y < ____________________; y++)
    {
        // 1行の左から右まで、マスを順番にコピーする
        for (int x = 0; x < ____________________; x++)
        {
            // 元ステージと同じ位置の数字をcurrentMapへ入れる
            map->Dat[y][x] = ________________________________;
        }
    }
}

ヒント

mapはコピー先です。コピー元はSTART_STAGEです。ループの回数は、コピーした幅と高さを使えます。

模範解答を見る
void LoadStage(Map* map)
{
    map->stage_width = START_STAGE.stage_width;
    map->stage_height = START_STAGE.stage_height;

    for (int y = 0; y < map->stage_height; y++)
    {
        for (int x = 0; x < map->stage_width; x++)
        {
            map->Dat[y][x] = START_STAGE.Dat[y][x];
        }
    }
}

8-3.ゲーム開始時に1回だけ読み込む

LoadStage()は、初期設定なので、ゲーム開始時(ゲームオーバーの後の再スタートの時なども)に1度だけ実行すればよいです。

毎フレームSTART_STAGEをコピーすると、あとでコスコマンを動かしても、すぐ最初の配置へ戻ってしまいます。

追加する場所:SetDrawScreen(DX_SCREEN_BACK);の下、whileの前
ここに置く理由:ステージの読み込みは、ゲームを始めるときに1回だけ行いたいからです。

// ゲームを始める前に、元ステージをプレイ用Mapへコピーする
LoadStage(____________________);

ヒント

LoadStage()は、書き換えるMapのアドレスを受け取ります。

模範解答を見る
LoadStage(&currentMap);

ここでビルド

まだステージは描いていないので画面は変わりません。

エラーなくビルドできれば、Mapの準備までできています。


9.文字の幅をそろえよう

ステージは文字をマスの代わりに並べます。

文字によって横幅が違うと、同じ列にある壁や荷物がずれてしまいます。

そこで、どの文字も同じ幅で表示される等幅フォントを使います。

今回はMS ゴシックを使います。

9-1.文字の大きさと表示位置を決める

追加する場所:MAX_STAGE_HEIGHTの下
ここに置く理由:文字の大きさや表示位置は途中で変化する値ではなく、描画全体で使う固定値だからです。

#define TEXT_SIZE 32
#define TEXT_STAGE_X 40
#define TEXT_STAGE_Y 90

9-2.フォントハンドルを入れる変数を用意する

追加する場所:WinMain()のMap currentMap;の下
ここに置く理由:作ったフォントをゲーム中の描画と、終了時の削除の両方で使うからです。

int fontHandle;

9-3.ゲーム開始時にフォントを作る

追加する場所:SetDrawScreen(DX_SCREEN_BACK);の下、LoadStage()より前
ここに置く理由:フォントは毎フレーム作り直す必要がないので、whileへ入る前に1回だけ作ります。

// ステージの文字を同じ幅で描くためのフォントを作る
fontHandle = ____________________("MS ゴシック", TEXT_SIZE, 1);

ヒント

DxLibでフォントハンドルを作る関数を使います。関数名にはCreateFontが入っています。

模範解答を見る
fontHandle = CreateFontToHandle("MS ゴシック", TEXT_SIZE, 1);

ここまでで、whileへ入る前は、

フォントを作る
      ↓
ステージを読み込む
      ↓
ゲームループへ入る

となります。

この2つはどちらも「ゲーム開始時に1回だけ行う処理」です。
ここは、フォントを扱うための呪文のようなところなので、フーンって感じでいいです。「こうやればフォントを等幅のものに設定できるのね、へぇ」ですすんでください。

9-4.終了時にフォントを削除する

作ったフォントは、ゲームが終わるときに削除します。

追加する場所:whileの後、DxLib_End();の前
ここに置く理由:ゲーム中はフォントを使い続けるので、ループを抜けてから後片付けします。

// 作ったフォントハンドルを終了前に削除する
DeleteFontToHandle(____________________);

ヒント

削除するのは、先ほど作ったフォントです。

模範解答を見る
DeleteFontToHandle(fontHandle);

10.Mapの1行を文字列にしよう

1マスずつ別々に描くのではなく、横1行を1本の文字列にして描きます。

例えば、

1 0 0 0 4 0 0 0 0 1

は、

#   @    #

になります。

C言語の文字列は、最後に'\0'が必要です。

横幅が最大10文字なので、

char line[MAX_STAGE_WIDTH + 1];

とすれば、10文字と最後の'\0'を入れられます。
(だから結局、記号の表現をそのまま読めるようにすれば。。。いやなんでもない)

DrawStageText()の中身を作る

先ほど宣言したDrawStageText()の中身を作ります。

LoadStage()の関数本体の下へ追加してください。

void DrawStageText(Map* map, int fontHandle)
{
    // ステージ1行分と、文字列の終わりを入れられる大きさにする
    char line[MAX_STAGE_WIDTH + ____________________];

    // ステージの上の行から下の行まで描く
    for (int y = 0; y < ____________________; y++)
    {
        // 今の1行を、左端から右端まで文字へ変換する
        for (int x = 0; x < ____________________; x++)
        {
            // 現在のマスに入っている数字を取り出す
            int obj = ________________________________;

            // その数字と同じ番号の表示文字をlineへ入れる
            line[x] = ________________________________;
        }

        // 1行分を書き終えた位置へ、文字列の終わりを入れる
        line[____________________] = ____________________;

        DrawStringToHandle(
            TEXT_STAGE_X,

            // y行目になるよう、1行の高さずつ下へずらす
            TEXT_STAGE_Y + ________________________________,
            line,
            GetColor(255, 255, 255),
            fontHandle);
    }
}

ヒント

外側は高さ、内側は幅まで繰り返します。Mapの数字を取り出したら、OBJECT_CHARの添字として使います。文字列の最後には'\0'が必要です。

模範解答を見る
void DrawStageText(Map* map, int fontHandle)
{
    char line[MAX_STAGE_WIDTH + 1];

    for (int y = 0; y < map->stage_height; y++)
    {
        for (int x = 0; x < map->stage_width; x++)
        {
            int obj = map->Dat[y][x];
            line[x] = OBJECT_CHAR[obj];
        }

        line[map->stage_width] = '\0';

        DrawStringToHandle(
            TEXT_STAGE_X,
            TEXT_STAGE_Y + y * TEXT_SIZE,
            line,
            GetColor(255, 255, 255),
            fontHandle);
    }
}

11.ゲームループからステージを描こう

ステージは、ゲーム画面として毎フレーム表示します。
これは、どんなゲームでもほとんど一緒です。

1フレームの描画順は、

前の画面を消す
      ↓
今の内容を描く
      ↓
描いた画面を表示する

です。

今のwhileでは、

ClearDrawScreen(); //画面消去

DrawString(24, 24, "COSTCO MAN", GetColor(255, 255, 255)); //描画処理

ScreenFlip(); //新しい画面の表示

となっています。

ステージの描画は、画面を消したあと、ScreenFlip()で表示する前に行います。

追加する場所:whileの中、DrawString()の下
ここに置く理由:ステージは毎フレーム描き直す必要があり、ScreenFlip()より前に裏画面へ描いておく必要があるからです。

// プレイ用Mapを、作った等幅フォントで描く
DrawStageText(____________________, ____________________);

ヒント

第1引数はゲーム中に使っているMapのアドレス、第2引数はフォントハンドルです。
変数のアドレスを取得するのはどうするんだっけ?

模範解答を見る
DrawStageText(&currentMap, fontHandle);

実行して確認

##########
#        #
#   .    #
#   $    #
#   @    #
#        #
##########

と表示されたら成功です。

第2回の実行例

実際に実行すると、ウィンドウ上部にCOSTCO MAN、その下に文字で作ったステージが表示されます。

第2回の実行結果

この画面で確認できるのは、次の3点です。

  • Mapの外周に入れたWALLが#として並んでいる
  • GOAL、LUGG、HUMANが、それぞれ.、$、@へ変換されている
  • すべての文字が等幅フォントで描かれ、縦と横の位置がそろっている

ウィンドウの見た目や文字の大きさは、Windowsの表示倍率やフォント環境によって少し違って見えることがあります。ステージの配置が同じなら問題ありません。

数字で持っていた2次元配列が、OBJECT_CHARを通して文字のステージになりました。

ここまでの実行順をもう一度見ると、

フォントを作る
      ↓
ステージを読み込む
      ↓
┌──────── 毎フレーム ────────┐
│ 画面を消す                  │
│ タイトルとステージを描く    │
│ 画面を表示する              │
└────────────────────────────┘
      ↓
フォントを削除する
      ↓
DxLibを終了する

となります。

次回は、Mapの中から@の位置を探して、実際に動かします。


12.第2回の完成コード

自分のコードと見比べたい場合だけ開いてください。

第2回の完成コードを見る
#include "DxLib.h"

#define SCREEN_WIDTH  720
#define SCREEN_HEIGHT 600

#define MAX_STAGE_WIDTH  10
#define MAX_STAGE_HEIGHT 10

#define TEXT_SIZE 32
#define TEXT_STAGE_X 40
#define TEXT_STAGE_Y 90

enum OBJNAME
{
    FLOOR = 0,
    WALL = 1,
    LUGG = 2,
    GOAL = 3,
    HUMAN = 4,
    HUMAN_ON_GOAL = 5,
    LUGG_ON_GOAL = 6,
    OUTSIDE = 7
};

struct Map
{
    int stage_width;
    int stage_height;
    int Dat[MAX_STAGE_HEIGHT][MAX_STAGE_WIDTH];
};

const char OBJECT_CHAR[] = {
    ' ',
    '#',
    '$',
    '.',
    '@',
    '+',
    '*',
    ' '
};

const Map START_STAGE = {
    10, 7,
    {
        { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
        { 1, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
        { 1, 0, 0, 0, 3, 0, 0, 0, 0, 1 },
        { 1, 0, 0, 0, 2, 0, 0, 0, 0, 1 },
        { 1, 0, 0, 0, 4, 0, 0, 0, 0, 1 },
        { 1, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
        { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }
    }
};

void LoadStage(Map* map);
void DrawStageText(Map* map, int fontHandle);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    Map currentMap;
    int fontHandle;

    ChangeWindowMode(TRUE);
    SetGraphMode(SCREEN_WIDTH, SCREEN_HEIGHT, 32);
    SetMainWindowText("Costco Man");

    if (DxLib_Init() != 0)
        return -1;

    SetDrawScreen(DX_SCREEN_BACK);

    fontHandle = CreateFontToHandle("MS ゴシック", TEXT_SIZE, 1);
    LoadStage(&currentMap);

    while (ProcessMessage() == 0)
    {
        ClearDrawScreen();

        DrawString(24, 24, "COSTCO MAN", GetColor(255, 255, 255));
        DrawStageText(&currentMap, fontHandle);

        ScreenFlip();
    }

    DeleteFontToHandle(fontHandle);

    DxLib_End();
    return 0;
}

void LoadStage(Map* map)
{
    map->stage_width = START_STAGE.stage_width;
    map->stage_height = START_STAGE.stage_height;

    for (int y = 0; y < map->stage_height; y++)
    {
        for (int x = 0; x < map->stage_width; x++)
        {
            map->Dat[y][x] = START_STAGE.Dat[y][x];
        }
    }
}

void DrawStageText(Map* map, int fontHandle)
{
    char line[MAX_STAGE_WIDTH + 1];

    for (int y = 0; y < map->stage_height; y++)
    {
        for (int x = 0; x < map->stage_width; x++)
        {
            int obj = map->Dat[y][x];
            line[x] = OBJECT_CHAR[obj];
        }

        line[map->stage_width] = '\0';

        DrawStringToHandle(
            TEXT_STAGE_X,
            TEXT_STAGE_Y + y * TEXT_SIZE,
            line,
            GetColor(255, 255, 255),
            fontHandle);
    }
}
第2回の変更点入り完成コードを見る

このコードは、上の説明なし完成コードと同じ処理です。今回追加・変更した場所を中心に、役割や理由もコメントで確認できます。

  • ステージを数値のMapとして用意し、表示用の文字へ変換する流れ
  • 元のステージとゲーム中のMapを分け、開始時に1回だけコピーする理由
  • 初期化・ゲームループ・終了処理の実行タイミング
#include "DxLib.h"

#define SCREEN_WIDTH  720
#define SCREEN_HEIGHT 600

#define MAX_STAGE_WIDTH  10
#define MAX_STAGE_HEIGHT 10

#define TEXT_SIZE 32
#define TEXT_STAGE_X 40
#define TEXT_STAGE_Y 90

// 【解説・今回追加】1マスの種類を番号で表す。Mapにはこの番号を保存する。
// 【解説・役割】表示文字ではなく種類を保存するので、あとから判定にも同じMapを使える。
enum OBJNAME
{
    FLOOR = 0,
    WALL = 1,
    LUGG = 2,
    GOAL = 3,
    HUMAN = 4,
    HUMAN_ON_GOAL = 5,
    LUGG_ON_GOAL = 6,
    OUTSIDE = 7
};

// 【解説・今回追加】ステージの幅・高さと、各マスの状態を1つにまとめる。
struct Map
{
    int stage_width;
    int stage_height;
    int Dat[MAX_STAGE_HEIGHT][MAX_STAGE_WIDTH];
};

// 【解説・流れ】OBJNAMEの番号を添字にして、画面へ出す1文字へ変換する表。
const char OBJECT_CHAR[] = {
    ' ',
    '#',
    '$',
    '.',
    '@',
    '+',
    '*',
    ' '
};

// 【解説・役割】書き換えない元ステージ。ゲーム中は、この内容をcurrentMapへコピーして使う。
const Map START_STAGE = {
    10, 7,
    {
        { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
        { 1, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
        { 1, 0, 0, 0, 3, 0, 0, 0, 0, 1 },
        { 1, 0, 0, 0, 2, 0, 0, 0, 0, 1 },
        { 1, 0, 0, 0, 4, 0, 0, 0, 0, 1 },
        { 1, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
        { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }
    }
};

// 【解説・今回追加】WinMainより下にある関数を先に呼べるよう、名前と引数を宣言する。
// 【解説・役割】LoadStageは読み込み、DrawStageTextは文字描画を担当する。
void LoadStage(Map* map);
void DrawStageText(Map* map, int fontHandle);

// 【解説・流れ】ここから、初期化→繰り返し処理→終了処理の順に実行される。
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    Map currentMap;
    int fontHandle;

    ChangeWindowMode(TRUE);
    SetGraphMode(SCREEN_WIDTH, SCREEN_HEIGHT, 32);
    SetMainWindowText("Costco Man");

    if (DxLib_Init() != 0)
        return -1;

    SetDrawScreen(DX_SCREEN_BACK);

    // 【解説・理由】フォントは毎フレーム作らず、ゲームループへ入る前に1回だけ作る。
    fontHandle = CreateFontToHandle("MS ゴシック", TEXT_SIZE, 1);
    // 【解説・理由】元ステージを直接変更しないよう、開始時にゲーム用Mapへコピーする。
    LoadStage(&currentMap);

    // 【解説・流れ】入力受付・更新・描画を繰り返すゲームループ。
    while (ProcessMessage() == 0)
    {
        ClearDrawScreen();

        DrawString(24, 24, "COSTCO MAN", GetColor(255, 255, 255));
        // 【解説・描画】現在のMapを毎フレーム描き直す。
        DrawStageText(&currentMap, fontHandle);

        ScreenFlip();
    }

    // 【解説・終了処理】ループを抜けたあと、作成したフォントを解放する。
    DeleteFontToHandle(fontHandle);

    DxLib_End();
    return 0;
}

// 【解説・役割】幅・高さをコピーしたあと、二重forで全マスをコピーする。
void LoadStage(Map* map)
{
    map->stage_width = START_STAGE.stage_width;
    map->stage_height = START_STAGE.stage_height;

    for (int y = 0; y < map->stage_height; y++)
    {
        for (int x = 0; x < map->stage_width; x++)
        {
            map->Dat[y][x] = START_STAGE.Dat[y][x];
        }
    }
}

// 【解説・役割】Mapの1行を文字列へ変換し、上から順に描く。
void DrawStageText(Map* map, int fontHandle)
{
    char line[MAX_STAGE_WIDTH + 1];

    for (int y = 0; y < map->stage_height; y++)
    {
        for (int x = 0; x < map->stage_width; x++)
        {
            int obj = map->Dat[y][x];
            line[x] = OBJECT_CHAR[obj];
        }

        // 【解説・理由】文字列の終端を入れ、DrawStringToHandleが行の終わりを判断できるようにする。
        line[map->stage_width] = '\0';

        DrawStringToHandle(
            TEXT_STAGE_X,
            TEXT_STAGE_Y + y * TEXT_SIZE,
            line,
            GetColor(255, 255, 255),
            fontHandle);
    }
}