Skip to main content

第11回 効果音とBGMをつけよう

第11回 効果音とBGMをつけよう

第10回までで、ゲームとしての動きは完成しました。

今回は、

歩く
荷物を押す
ゴールへ押し込む
クリアする

といった出来事に合わせて効果音を鳴らし、ゲーム中はBGMを流します。

図11-01 ゲームの出来事に合わせて音を鳴らす図11-01 ゲームの出来事に合わせて音を鳴らす

今回の流れ

サウンドファイルを読み込む
        ↓
MoveResultに合わせて効果音を選ぶ
        ↓
移動時に効果音を鳴らす
        ↓
クリア時にクリア音を鳴らす
        ↓
ゲーム中はBGMをループ再生する

1.移動結果を音にも使おう

第10回では、MoveResultを荷物のアニメーション判定に使いました。

同じ値を、今度は効果音の選択にも使えます。

MOVE_WALK
    → 歩く音

MOVE_PUSH
    → 荷物を押す音

MOVE_PUSH_GOAL
    → ゴールへ入る音

MOVE_NONE
    → 音を鳴らさない

移動処理を作り直す必要はありません。

すでにある移動結果を、音にも再利用します。


2.サウンドハンドルをまとめよう

第8回では、画像ハンドルをGameImagesへまとめました。

音も同じように、GameSoundsという構造体へまとめます。

今回使う音は5種類です。

メンバ 音
move 歩く音
push 荷物を押す音
goal ゴールへ押し込む音
clear ステージクリア音
bgm ゲーム中のBGM

GameImagesの下へ追加してください。

struct GameSounds
{
    int move;
    int push;
    int goal;
    int clear;
    int bgm;
};

今回追加する関数

LoadGameSounds()は、ゲームで使う音を読み込む関数です。

DeleteGameSounds()は、読み込んだ音を解放する関数です。

PlayMoveSound()は、MoveResultに合わせて移動時の効果音を選んで鳴らす関数です。

既存のプロトタイプ宣言へ、次の3つを追加します。

void LoadGameSounds(GameSounds* sounds);
void DeleteGameSounds(GameSounds* sounds);
void PlayMoveSound(MoveResult result, GameSounds* sounds);

UpdateGame()でも音を使うため、最後の引数へGameSounds* soundsを追加します。

void UpdateGame(Map* map, int* currentStage, int* clearFlag, GameSounds* sounds);

では、1つずつ実装してみましょう。


BGM・SEを探してみよう

自分でBGMや効果音を探したいときは、フリー素材サイトを利用できます。

サイト 主な素材 使うときのポイント
ニコニ・コモンズ BGM・SEなど 素材ごとに利用条件が違うので、ダウンロードする素材の条件を確認しよう
DOVA-SYNDROME BGM・SE ゲームにも使いやすい。作曲者が個別の条件を設定している場合があるので確認しよう
効果音ラボ SE ボタン音、足音、環境音など種類が多い
魔王魂 BGM・SE ゲーム向けの曲も多い。公開作品で使うときはクレジットなど現在の利用ルールを確認しよう
OtoLogic BGM・SE 無料利用ではクレジット表記が必要

「フリー素材」でも著作権がなくなるわけではありません。
ダウンロードする前に、そのサイトや素材の利用条件を確認してから使いましょう。

この教材ではsoundsフォルダのファイル名を、

move.wav
push.wav
goal.wav
clear.wav
bgm.wav

として説明します。

今回使ったものは、以下からダウンロードできます。

第11回 サンプルBGM・SE.zip

別の音へ差し替える場合も、同じファイル名にしておけばプログラムを変更せずに試せます。


3.音を読み込もう

DxLibでは、

LoadSoundMem("ファイル名")

で音を読み込み、サウンドハンドルを受け取れます。

今回はプロジェクト直下のsoundsフォルダへ音を置きます。

sounds/
├─ move.wav
├─ push.wav
├─ goal.wav
├─ clear.wav
└─ bgm.wav

先ほどプロトタイプ宣言したLoadGameSounds()を追加してください。

void LoadGameSounds(GameSounds* sounds)
{
    sounds->move  = LoadSoundMem("sounds/________________");
    sounds->push  = LoadSoundMem("sounds/________________");
    sounds->goal  = LoadSoundMem("sounds/________________");
    sounds->clear = LoadSoundMem("sounds/________________");
    sounds->bgm   = LoadSoundMem("sounds/________________");
}

ヒント

ファイル名は表の通りです。

模範解答を見る
sounds->move  = LoadSoundMem("sounds/move.wav");
sounds->push  = LoadSoundMem("sounds/push.wav");
sounds->goal  = LoadSoundMem("sounds/goal.wav");
sounds->clear = LoadSoundMem("sounds/clear.wav");
sounds->bgm   = LoadSoundMem("sounds/bgm.wav");

4.読み込んだ音を解放しよう

音の解放には、

DeleteSoundMem(サウンドハンドル)

を使います。

DeleteGameSounds()を、LoadGameSounds()の下へ追加してください。

void DeleteGameSounds(GameSounds* sounds)
{
    DeleteSoundMem(________________);
    DeleteSoundMem(________________);
    DeleteSoundMem(________________);
    DeleteSoundMem(________________);
    DeleteSoundMem(________________);
}

ヒント

読み込んだ5つのハンドルを、それぞれ解放します。

模範解答を見る
DeleteSoundMem(sounds->move);
DeleteSoundMem(sounds->push);
DeleteSoundMem(sounds->goal);
DeleteSoundMem(sounds->clear);
DeleteSoundMem(sounds->bgm);

5.MoveResultに合わせて効果音を鳴らそう

効果音を1回鳴らすときは、

PlaySoundMem(サウンドハンドル, DX_PLAYTYPE_BACK, TRUE);

を使います。

DX_PLAYTYPE_BACKは、音を鳴らしたあともプログラムを止めずに処理を続ける再生方法です。

PlayMoveSound()では、受け取ったresultを見て音を選びます。

void PlayMoveSound(MoveResult result, GameSounds* sounds)
{
    if (result == MOVE_WALK)
        PlaySoundMem(________________, DX_PLAYTYPE_BACK, TRUE);

    else if (result == MOVE_PUSH)
        PlaySoundMem(________________, DX_PLAYTYPE_BACK, TRUE);

    else if (result == MOVE_PUSH_GOAL)
        PlaySoundMem(________________, DX_PLAYTYPE_BACK, TRUE);
}

ヒント

MOVE_WALK       → sounds->move
MOVE_PUSH       → sounds->push
MOVE_PUSH_GOAL  → sounds->goal
模範解答を見る
PlaySoundMem(sounds->move, DX_PLAYTYPE_BACK, TRUE);
PlaySoundMem(sounds->push, DX_PLAYTYPE_BACK, TRUE);
PlaySoundMem(sounds->goal, DX_PLAYTYPE_BACK, TRUE);

MOVE_NONEの場合は、どの条件にも入らないので音は鳴りません。


6.UpdateGameへ効果音をつなごう

第9回と同じように、MoveObject()を呼ぶ前に移動前の位置を取得してから移動します。

Point startPos = GetPlayerPos(map);
MoveResult result = MoveObject(dir, map);

その直後に、先ほど作ったPlayMoveSound()を呼びます。

MoveResult result = MoveObject(dir, map);

// resultに合った移動音を鳴らす
______________________________________________;

ヒント

移動結果      → result
サウンド一式  → sounds
模範解答を見る
PlayMoveSound(result, sounds);

クリア判定は第10回までと同じく、移動アニメーションが終わったあとに行われます。

その場所でクリアが見つかったら、

PlaySoundMem(sounds->clear, DX_PLAYTYPE_BACK, TRUE);

を呼びます。

if (CheckClear(map))
{
    // ステージをクリアしたので、クリア音を鳴らす
    ________________________________________________;

    *clearFlag = 1;
    return;
}
模範解答を見る
PlaySoundMem(sounds->clear, DX_PLAYTYPE_BACK, TRUE);

7.BGMをループ再生しよう

BGMは、ゲーム中ずっと繰り返して再生します。

DxLibでは、

PlaySoundMem(sounds.bgm, DX_PLAYTYPE_LOOP, TRUE);

とするとループ再生できます。

WinMain()では、画像と同じようにGameSounds変数を1つ用意します。

GameSounds sounds;

DxLibの初期化後に、

LoadGameSounds(&sounds);

を呼び、ステージを読み込んだあとにBGMを開始します。

PlaySoundMem(sounds.bgm, DX_PLAYTYPE_LOOP, TRUE);

ゲームループでは、UpdateGame()へsoundsのアドレスを渡します。

UpdateGame(&currentMap, &currentStage, &clearFlag, &sounds);

終了するときは、

StopSoundMem(sounds.bgm);
DeleteGameSounds(&sounds);

の順で、BGMを止めてから音を解放します。


8.実行して確認しよう

次を確認してください。

  • 普通に歩くとmove.wavが鳴る
  • 荷物を押すとpush.wavが鳴る
  • 荷物をゴールへ押すとgoal.wavが鳴る
  • 壁へぶつかったときは移動音が鳴らない
  • アニメーション終了後にクリアするとclear.wavが鳴る
  • ゲーム中はbgm.wavが繰り返し流れる
  • 終了時にエラーなく音が解放される

9.第11回の完成コード

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

#define SCREEN_WIDTH  720
#define SCREEN_HEIGHT 600

#define MAX_STAGE_WIDTH  10
#define MAX_STAGE_HEIGHT 10
#define STAGE_COUNT 3

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

struct Point
{
    int x;
    int y;
};

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

enum Direction
{
    DIR_UP,
    DIR_LEFT,
    DIR_DOWN,
    DIR_RIGHT,
    DIR_NONE
};

enum MoveResult
{
    MOVE_NONE,
    MOVE_WALK,
    MOVE_PUSH,
    MOVE_PUSH_GOAL
};

const Point DIR_VECTOR[] = {
    { 0, -1 },
    {-1,  0 },
    { 0, +1 },
    {+1,  0 },
    { 0,  0 }
};

#define TILE_SIZE 64
#define STAGE_X 40
#define STAGE_Y 90

struct GameImages
{
    int floor;
    int wall;
    int goal;
    int luggage;
    int human;
};

#define MOVE_TIME 200

Point moveStartPos = { 0, 0 };
Direction moveDir = DIR_NONE;
int moveStartTime = 0;
MoveResult lastMoveResult = MOVE_NONE;

struct GameSounds
{
    int move;
    int push;
    int goal;
    int clear;
    int bgm;
};

const Map STAGES[STAGE_COUNT] = {
    {
        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 }
        }
    },
    {
        10, 7,
        {
            { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
            { 1, 0, 0, 0, 1, 0, 0, 0, 0, 1 },
            { 1, 0, 2, 0, 1, 0, 3, 0, 0, 1 },
            { 1, 0, 0, 2, 0, 0, 3, 0, 0, 1 },
            { 1, 0, 0, 0, 1, 1, 0, 0, 0, 1 },
            { 1, 0, 4, 0, 0, 0, 0, 0, 0, 1 },
            { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }
        }
    },
    {
        10, 7,
        {
            { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
            { 1, 1, 1, 1, 0, 0, 1, 0, 4, 1 },
            { 1, 1, 1, 0, 0, 0, 1, 0, 0, 1 },
            { 1, 1, 1, 2, 0, 2, 0, 2, 0, 1 },
            { 1, 1, 1, 0, 2, 2, 1, 0, 0, 1 },
            { 1, 3, 3, 3, 3, 3, 0, 0, 1, 1 },
            { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }
        }
    }
};

void LoadStage(Map* map, int stageNumber);
Direction GetDirection(void);
Point GetPlayerPos(Map* map);
OBJNAME GetObjectNum(Point pos, Map* map);
void SetObjtoMap(OBJNAME obj, Point pos, Map* map);
int IsKeyPressed(int key);
MoveResult MoveObject(Direction dir, Map* map);
int CheckClear(Map* map);
void LoadGameImages(GameImages* images);
void DeleteGameImages(GameImages* images);
float GetMoveRate(void);
Point GetMoveDrawPos(Point startPos, Direction dir, float rate);
void DrawStageImage(Map* map, GameImages* images);
void LoadGameSounds(GameSounds* sounds);
void DeleteGameSounds(GameSounds* sounds);
void PlayMoveSound(MoveResult result, GameSounds* sounds);
void UpdateGame(Map* map, int* currentStage, int* clearFlag, GameSounds* sounds);
void DrawGame(Map* map, int currentStage, int clearFlag, GameImages* images);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    Map currentMap;
    GameImages images;
    GameSounds sounds;
    int currentStage = 0;
    int clearFlag = 0;

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

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

    SetDrawScreen(DX_SCREEN_BACK);

    LoadGameImages(&images);
    LoadGameSounds(&sounds);
    LoadStage(&currentMap, currentStage);
    PlaySoundMem(sounds.bgm, DX_PLAYTYPE_LOOP, TRUE);

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

        UpdateGame(&currentMap, &currentStage, &clearFlag, &sounds);
        DrawGame(&currentMap, currentStage, clearFlag, &images);

        ScreenFlip();
    }

    StopSoundMem(sounds.bgm);
    DeleteGameSounds(&sounds);
    DeleteGameImages(&images);
    DxLib_End();
    return 0;
}

void LoadStage(Map* map, int stageNumber)
{
    map->stage_width = STAGES[stageNumber].stage_width;
    map->stage_height = STAGES[stageNumber].stage_height;

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

Direction GetDirection(void)
{
    // 4方向のキーが押された瞬間を調べる
    int up = IsKeyPressed(KEY_INPUT_W);
    int left = IsKeyPressed(KEY_INPUT_A);
    int down = IsKeyPressed(KEY_INPUT_S);
    int right = IsKeyPressed(KEY_INPUT_D);

    if (up)
    {
        return DIR_UP;
    }
    if (left)
    {
        return DIR_LEFT;
    }
    if (down)
    {
        return DIR_DOWN;
    }
    if (right)
    {
        return DIR_RIGHT;
    }

    return DIR_NONE;
}

Point GetPlayerPos(Map* map)
{
    Point result = { -1, -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];

            if (obj == HUMAN || obj == HUMAN_ON_GOAL)
            {
                result.x = x;
                result.y = y;
                return result;
            }
        }
    }

    return result;
}

OBJNAME GetObjectNum(Point pos, Map* map)
{
    if (pos.x < 0 || pos.x >= map->stage_width || pos.y < 0 || pos.y >= map->stage_height)
        return OUTSIDE;

    return (OBJNAME)map->Dat[pos.y][pos.x];
}

void SetObjtoMap(OBJNAME obj, Point pos, Map* map)
{
    map->Dat[pos.y][pos.x] = obj;
}

int IsKeyPressed(int key)
{
    static int previous[256] = { 0 };
    int current = CheckHitKey(key);
    int pressed = 0;
    if (current != 0 && previous[key] == 0)
    {
        pressed = 1;
    }

    previous[key] = current;
    return pressed;
}

MoveResult MoveObject(Direction dir, Map* map)
{
    Point playerPos = GetPlayerPos(map);

    Point nextPos = {
        playerPos.x + DIR_VECTOR[dir].x,
        playerPos.y + DIR_VECTOR[dir].y
    };

    OBJNAME current = GetObjectNum(playerPos, map);
    OBJNAME next = GetObjectNum(nextPos, map);

    if (next == FLOOR || next == GOAL)
    {
        if (current == HUMAN_ON_GOAL)
            SetObjtoMap(GOAL, playerPos, map);
        else
            SetObjtoMap(FLOOR, playerPos, map);

        if (next == GOAL)
            SetObjtoMap(HUMAN_ON_GOAL, nextPos, map);
        else
            SetObjtoMap(HUMAN, nextPos, map);

        return MOVE_WALK;
    }

    if (next == LUGG || next == LUGG_ON_GOAL)
    {
        Point nextNextPos = {
            nextPos.x + DIR_VECTOR[dir].x,
            nextPos.y + DIR_VECTOR[dir].y
        };

        OBJNAME nextNext = GetObjectNum(nextNextPos, map);

        if (nextNext != FLOOR && nextNext != GOAL)
            return MOVE_NONE;

        if (current == HUMAN_ON_GOAL)
            SetObjtoMap(GOAL, playerPos, map);
        else
            SetObjtoMap(FLOOR, playerPos, map);

        if (next == LUGG_ON_GOAL)
            SetObjtoMap(HUMAN_ON_GOAL, nextPos, map);
        else
            SetObjtoMap(HUMAN, nextPos, map);

        if (nextNext == GOAL)
        {
            SetObjtoMap(LUGG_ON_GOAL, nextNextPos, map);
            return MOVE_PUSH_GOAL;
        }

        SetObjtoMap(LUGG, nextNextPos, map);
        return MOVE_PUSH;
    }

    return MOVE_NONE;
}

int CheckClear(Map* map)
{
    for (int y = 0; y < map->stage_height; y++)
    {
        for (int x = 0; x < map->stage_width; x++)
        {
            if (map->Dat[y][x] == LUGG)
                return 0;
        }
    }

    return 1;
}


void LoadGameImages(GameImages* images)
{
    images->floor = LoadGraph("images/floor.png");
    images->wall = LoadGraph("images/wall.png");
    images->goal = LoadGraph("images/goal.png");
    images->luggage = LoadGraph("images/luggage.png");
    images->human = LoadGraph("images/human.png");
}

void DeleteGameImages(GameImages* images)
{
    DeleteGraph(images->floor);
    DeleteGraph(images->wall);
    DeleteGraph(images->goal);
    DeleteGraph(images->luggage);
    DeleteGraph(images->human);
}

float GetMoveRate(void)
{
    if (moveDir == DIR_NONE)
        return 1.0f;

    int elapsed = GetNowCount() - moveStartTime;
    float rate = (float)elapsed / MOVE_TIME;

    if (rate < 0.0f)
        rate = 0.0f;

    if (rate > 1.0f)
        rate = 1.0f;

    return rate;
}

Point GetMoveDrawPos(Point startPos, Direction dir, float rate)
{
    Point moveVector = DIR_VECTOR[dir];

    Point drawPos = {
        STAGE_X + startPos.x * TILE_SIZE + (int)(moveVector.x * TILE_SIZE * rate),
        STAGE_Y + startPos.y * TILE_SIZE + (int)(moveVector.y * TILE_SIZE * rate)
    };

    return drawPos;
}

void DrawStageImage(Map* map, GameImages* images)
{
    float rate = GetMoveRate();
    Point moveVector = DIR_VECTOR[moveDir];

    // 荷物を押したとき、荷物はプレイヤーの1マス先から動く
    Point luggageStartPos = {
        moveStartPos.x + moveVector.x,
        moveStartPos.y + moveVector.y
    };

    // Map上では荷物はすでに1マス移動している
    Point luggageEndPos = {
        luggageStartPos.x + moveVector.x,
        luggageStartPos.y + moveVector.y
    };

    for (int y = 0; y < map->stage_height; y++)
    {
        for (int x = 0; x < map->stage_width; x++)
        {
            int obj = map->Dat[y][x];
            int drawX = STAGE_X + x * TILE_SIZE;
            int drawY = STAGE_Y + y * TILE_SIZE;

            if (obj == WALL)
                DrawGraph(drawX, drawY, images->wall, TRUE);
            else if (obj == GOAL || obj == HUMAN_ON_GOAL || obj == LUGG_ON_GOAL)
                DrawGraph(drawX, drawY, images->goal, TRUE);
            else
                DrawGraph(drawX, drawY, images->floor, TRUE);
        }
    }

    for (int y = 0; y < map->stage_height; y++)
    {
        for (int x = 0; x < map->stage_width; x++)
        {
            int obj = map->Dat[y][x];
            int drawX = STAGE_X + x * TILE_SIZE;
            int drawY = STAGE_Y + y * TILE_SIZE;

            if (obj == HUMAN || obj == HUMAN_ON_GOAL)
            {
                if (rate < 1.0f)
                {
                    Point drawPos = GetMoveDrawPos(moveStartPos, moveDir, rate);
                    drawX = drawPos.x;
                    drawY = drawPos.y;
                }

                DrawGraph(drawX, drawY, images->human, TRUE);
            }
            else if (obj == LUGG || obj == LUGG_ON_GOAL)
            {
                // 荷物を押した移動の途中で、今回動いた荷物だけ描画位置を変える
                if (rate < 1.0f &&
                    (lastMoveResult == MOVE_PUSH ||
                     lastMoveResult == MOVE_PUSH_GOAL) &&
                    x == luggageEndPos.x &&
                    y == luggageEndPos.y)
                {
                    Point drawPos = GetMoveDrawPos(luggageStartPos, moveDir, rate);
                    drawX = drawPos.x;
                    drawY = drawPos.y;
                }

                DrawGraph(drawX, drawY, images->luggage, TRUE);
            }
        }
    }
}






void LoadGameSounds(GameSounds* sounds)
{
    sounds->move = LoadSoundMem("sounds/move.wav");
    sounds->push = LoadSoundMem("sounds/push.wav");
    sounds->goal = LoadSoundMem("sounds/goal.wav");
    sounds->clear = LoadSoundMem("sounds/clear.wav");
    sounds->bgm = LoadSoundMem("sounds/bgm.wav");
}

void DeleteGameSounds(GameSounds* sounds)
{
    DeleteSoundMem(sounds->move);
    DeleteSoundMem(sounds->push);
    DeleteSoundMem(sounds->goal);
    DeleteSoundMem(sounds->clear);
    DeleteSoundMem(sounds->bgm);
}

void PlayMoveSound(MoveResult result, GameSounds* sounds)
{
    if (result == MOVE_WALK)
        PlaySoundMem(sounds->move, DX_PLAYTYPE_BACK, TRUE);
    else if (result == MOVE_PUSH)
        PlaySoundMem(sounds->push, DX_PLAYTYPE_BACK, TRUE);
    else if (result == MOVE_PUSH_GOAL)
        PlaySoundMem(sounds->goal, DX_PLAYTYPE_BACK, TRUE);
}

void UpdateGame(Map* map, int* currentStage, int* clearFlag, GameSounds* sounds)
{
    int enterPressed = IsKeyPressed(KEY_INPUT_RETURN);
    int retryPressed = IsKeyPressed(KEY_INPUT_R);

    if (GetMoveRate() < 1.0f)
        return;

    if (*clearFlag)
    {
        if (enterPressed)
        {
            (*currentStage)++;

            if (*currentStage >= STAGE_COUNT)
                *currentStage = 0;

            LoadStage(map, *currentStage);
            moveDir = DIR_NONE;
            lastMoveResult = MOVE_NONE;
            *clearFlag = 0;
        }

        return;
    }

    if (CheckClear(map))
    {
        PlaySoundMem(sounds->clear, DX_PLAYTYPE_BACK, TRUE);
        *clearFlag = 1;
        return;
    }

    if (retryPressed)
    {
        LoadStage(map, *currentStage);
        moveDir = DIR_NONE;
        lastMoveResult = MOVE_NONE;
        return;
    }

    Direction dir = GetDirection();

    if (dir != DIR_NONE)
    {
        // 移動する前のプレイヤー位置を先に覚えておく
        Point startPos = GetPlayerPos(map);

        MoveResult result = MoveObject(dir, map);
        PlayMoveSound(result, sounds);

        if (result != MOVE_NONE)
        {
            moveStartPos = startPos;
            moveDir = dir;
            moveStartTime = GetNowCount();
            lastMoveResult = result;
        }
    }
}

void DrawGame(Map* map, int currentStage, int clearFlag, GameImages* images)
{
    DrawFormatString(24, 20, GetColor(255, 255, 255), "STAGE %d / %d", currentStage + 1, STAGE_COUNT);
    DrawString(300, 20, "WASD : MOVE    R : RETRY", GetColor(200, 200, 200));
    DrawStageImage(map, images);

    if (clearFlag)
    {
        DrawString(250, 540, "CLEAR!", GetColor(255, 220, 80));
        DrawString(355, 540, "ENTER : NEXT", GetColor(255, 255, 255));
    }
}