Skip to content
第13回 新作プロジェクトとゲームの土台

4 / 4 背景とプレイヤーを表示する

このステップの目的

Gameが入力を確認し、背景とプレイヤーを繰り返し描画します。

この回のゲームループ

第13回は静止表示を作ります。Game.hにはInitialize、MainLoopと描画用関数を宣言します。プレイヤーの移動を行うUpdateは第14回で追加します。

Game.hcpp
#pragma once

#include "Platformer/ImageManager.h"
#include "Platformer/Input.h"
#include "Platformer/Player.h"

namespace dxgame::platformer
{
    // 入力、プレイヤー、描画順を調整する
    class Game
    {
    public:
        // 必須画像を読み込む
        bool Initialize();
        // ウィンドウ終了まで更新と描画を繰り返す
        void MainLoop();

    private:
        void Draw() const;
        void DrawBackground() const;
        void DrawHud() const;

        Input input_;
        ImageManager images_;
        Player player_;
    };
}

入力の取得、画面の消去、描画、画面反転、待機をこの順に呼びます。待機処理は前の作品と同じ目的で使用するため、そのまま残します。

Game.cppcpp
void Game::MainLoop()
{
    // 小数分の待ち時間を次フレームへ持ち越す
    double nextFrame = GetNowCount();

    while (ProcessMessage() == 0)
    {
        input_.Update();
        if (input_.IsHeld(Key::Escape))
            break;

        ClearDrawScreen();
        Draw();
        ScreenFlip();

        nextFrame += dxgame::config::FrameMilliseconds;
        const int waitMilliseconds =
            static_cast<int>(nextFrame - GetNowCount());

        if (waitMilliseconds > 0)
            WaitTimer(waitMilliseconds);
        else
            nextFrame = GetNowCount();
    }
}

背景の前へプレイヤーを描く

Initializeで画像を読み込み、Player::Resetへ画面中央を渡します。Drawでは背景、プレイヤー、操作案内の順に描きます。各描画関数をGame.cppの名前空間内へ定義してください。

Game.cppcpp
bool Game::Initialize()
{
    if (!images_.LoadAll())
        return false;

    player_.Reset({
        dxgame::config::ScreenWidth * 0.5f,
        dxgame::config::ScreenHeight * 0.5f
    });
    return true;
}

void Game::Draw() const
{
    DrawBackground();
    player_.Draw(images_);
    DrawHud();
}

void Game::DrawBackground() const
{
    images_.Draw(ImageId::Background, { 0.0f, 0.0f,
        static_cast<float>(dxgame::config::ScreenWidth),
        static_cast<float>(dxgame::config::ScreenHeight) });
}

void Game::DrawHud() const
{
    const int textColor = GetColor(255, 255, 255);
    DrawString(12, 10, L"ESC: QUIT", textColor);
}

次回は既存のPlayerへ左右移動を加えます。

チェックリスト
  • 背景の前にプレイヤーが静止表示される。
  • Escapeを押すと終了する。
  • MainLoopの待機処理を残している。

参考実装

今回追加・変更するファイルの一例です。宣言や補助処理を確認したいときに開いてください。自分のコードへ必要な変更を反映し、既存の調整値や素材をそのまま上書きしないようにします。

Source/main.cpp
Source/main.cppcpp
#include "DxLib.h"

#include "Config/ApplicationConfig.h"
#include "Platformer/Game.h"

// DXライブラリを初期化し、ゲームの所有者を先に破棄する
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
    ChangeWindowMode(TRUE);
    SetGraphMode(dxgame::config::ScreenWidth,
        dxgame::config::ScreenHeight, dxgame::config::ColorBits);
    SetWaitVSyncFlag(FALSE);
    SetMainWindowText(dxgame::config::WindowTitle);

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

    SetDrawScreen(DX_SCREEN_BACK);

    int result = 0;
    {
        dxgame::platformer::Game game;
        if (!game.Initialize())
        {
            result = -1;
        }
        else
        {
            game.MainLoop();
        }
    }

    DxLib_End();
    return result;
}
Source/DxGame/Rect.h
Source/DxGame/Rect.hcpp
#pragma once

#include "DxGame/Vector2.h"

namespace dxgame
{
    // 左上座標と幅・高さで表す矩形
    struct Rect
    {
        float x = 0.0f;
        float y = 0.0f;
        float width = 0.0f;
        float height = 0.0f;

        // 中央を原点とする矩形を作る
        static constexpr Rect Centered(float width, float height)
        {
            return { -width * 0.5f, -height * 0.5f, width, height };
        }

        // 矩形を移動した値を返す
        constexpr Rect MovedBy(const Vector2& offset) const
        {
            return { x + offset.x, y + offset.y, width, height };
        }

        // 矩形の中央座標を返す
        constexpr Vector2 GetCenter() const
        {
            return { x + width * 0.5f, y + height * 0.5f };
        }

        // 面積を持って重なっているかを返す
        constexpr bool Overlaps(const Rect& other) const
        {
            return
                x < other.x + other.width && other.x < x + width &&
                y < other.y + other.height && other.y < y + height;
        }
    };
}
Source/DxGame/Vector2.h
Source/DxGame/Vector2.hcpp
#pragma once

namespace dxgame
{
    // 2D空間の位置や速度を表す値型
    struct Vector2
    {
        float x = 0.0f;
        float y = 0.0f;
    };
}
Source/Platformer/Game.cpp
Source/Platformer/Game.cppcpp
#include "DxLib.h"

#include "Config/ApplicationConfig.h"
#include "Platformer/Game.h"

namespace dxgame::platformer
{
    bool Game::Initialize()
    {
        if (!images_.LoadAll())
            return false;

        player_.Reset({
            dxgame::config::ScreenWidth * 0.5f,
            dxgame::config::ScreenHeight * 0.5f
        });
        return true;
    }

    void Game::MainLoop()
    {
        // 小数分の待ち時間を次フレームへ持ち越す
        double nextFrame = GetNowCount();

        while (ProcessMessage() == 0)
        {
            input_.Update();
            if (input_.IsHeld(Key::Escape))
                break;

            ClearDrawScreen();
            Draw();
            ScreenFlip();

            nextFrame += dxgame::config::FrameMilliseconds;
            const int waitMilliseconds =
                static_cast<int>(nextFrame - GetNowCount());

            if (waitMilliseconds > 0)
                WaitTimer(waitMilliseconds);
            else
                nextFrame = GetNowCount();
        }
    }

    void Game::Draw() const
    {
        DrawBackground();
        player_.Draw(images_);
        DrawHud();
    }

    void Game::DrawBackground() const
    {
        images_.Draw(ImageId::Background, { 0.0f, 0.0f,
            static_cast<float>(dxgame::config::ScreenWidth),
            static_cast<float>(dxgame::config::ScreenHeight) });
    }

    void Game::DrawHud() const
    {
        const int textColor = GetColor(255, 255, 255);
        DrawString(12, 10, L"ESC: QUIT", textColor);
    }

}
Source/Platformer/Game.h
Source/Platformer/Game.hcpp
#pragma once

#include "Platformer/ImageManager.h"
#include "Platformer/Input.h"
#include "Platformer/Player.h"

namespace dxgame::platformer
{
    // 入力、プレイヤー、描画順を調整する
    class Game
    {
    public:
        // 必須画像を読み込む
        bool Initialize();
        // ウィンドウ終了まで更新と描画を繰り返す
        void MainLoop();

    private:
        void Draw() const;
        void DrawBackground() const;
        void DrawHud() const;

        Input input_;
        ImageManager images_;
        Player player_;
    };
}
Source/Platformer/ImageManager.cpp
Source/Platformer/ImageManager.cppcpp
#include <Windows.h>
#include <array>
#include <string>

#include "DxLib.h"

#include "Platformer/ImageManager.h"

namespace dxgame::platformer
{
    namespace
    {
        struct ImageDefinition
        {
            ImageId id;
            const wchar_t* path;
        };

        constexpr ImageDefinition imageDefinitions[] =
        {
            { ImageId::Background, L"Assets/Images/Background.png" },
            { ImageId::Player, L"Assets/Images/Player.png" },
            { ImageId::Enemy, L"Assets/Images/Enemy.png" },
            { ImageId::Tile, L"Assets/Images/Tile.png" },
            { ImageId::TileMoss, L"Assets/Images/TileMoss.png" },
            { ImageId::Goal, L"Assets/Images/Goal.png" }
        };

        constexpr std::size_t ImageIdCount =
            static_cast<std::size_t>(ImageId::Count);
        constexpr std::size_t ImageDefinitionCount =
            sizeof(imageDefinitions) / sizeof(imageDefinitions[0]);

        static_assert(ImageDefinitionCount == ImageIdCount,
            "Register one imageDefinitions entry for every ImageId.");

        bool HasCompleteImageDefinitions()
        {
            std::array<bool, ImageIdCount> registered = {};

            for (const ImageDefinition& definition : imageDefinitions)
            {
                const std::size_t index =
                    static_cast<std::size_t>(definition.id);
                if (index >= ImageIdCount || registered[index] ||
                    definition.path == nullptr ||
                    definition.path[0] == L'\0')
                {
                    return false;
                }
                registered[index] = true;
            }

            for (bool isRegistered : registered)
            {
                if (!isRegistered)
                    return false;
            }
            return true;
        }
    }

    ImageManager::~ImageManager()
    {
        Release();
    }

    bool ImageManager::LoadAll()
    {
        Release();

        if (!HasCompleteImageDefinitions())
        {
            MessageBoxW(nullptr,
                L"ImageIdと画像定義の登録に重複または不足があります。",
                L"Image Definition Error", MB_OK);
            return false;
        }

        for (const ImageDefinition& definition : imageDefinitions)
        {
            const std::size_t index = static_cast<std::size_t>(definition.id);
            ImageResource& resource = resources_[index];
            resource.handle = LoadGraph(definition.path);

            if (resource.handle == -1 ||
                GetGraphSize(resource.handle, &resource.width,
                    &resource.height) == -1)
            {
                std::wstring message = definition.path;
                message += L" の読み込みまたはサイズ取得に失敗しました。";
                MessageBoxW(nullptr, message.c_str(), L"Asset Load Error", MB_OK);
                Release();
                return false;
            }
        }

        return true;
    }

    const ImageResource& ImageManager::Get(ImageId id) const
    {
        return resources_[static_cast<std::size_t>(id)];
    }

    void ImageManager::Draw(ImageId id, const dxgame::Rect& rect) const
    {
        const ImageResource& image = Get(id);
        DrawExtendGraph(
            static_cast<int>(rect.x),
            static_cast<int>(rect.y),
            static_cast<int>(rect.x + rect.width),
            static_cast<int>(rect.y + rect.height),
            image.handle, TRUE);
    }

    void ImageManager::DrawFrame(ImageId id, int frameIndex, int frameCount,
        const dxgame::Rect& rect, bool flipX) const
    {
        const ImageResource& image = Get(id);
        const int sourceWidth = image.width / frameCount;
        const int sourceX = sourceWidth * frameIndex;
        const double scaleX = static_cast<double>(rect.width) / sourceWidth;
        const double scaleY = static_cast<double>(rect.height) / image.height;

        DrawRectRotaGraph3(
            static_cast<int>(rect.x + rect.width * 0.5f),
            static_cast<int>(rect.y + rect.height * 0.5f),
            sourceX, 0, sourceWidth, image.height,
            sourceWidth / 2, image.height / 2,
            scaleX, scaleY, 0.0,
            image.handle, TRUE, flipX ? TRUE : FALSE);
    }

    void ImageManager::Release()
    {
        for (ImageResource& resource : resources_)
        {
            if (resource.handle != -1)
                DeleteGraph(resource.handle);

            resource = {};
        }
    }
}
Source/Platformer/ImageManager.h
Source/Platformer/ImageManager.hcpp
#pragma once

#include <array>
#include <cstddef>

#include "DxGame/Rect.h"

namespace dxgame::platformer
{
    enum class ImageId
    {
        Background,
        Player,
        Enemy,
        Tile,
        TileMoss,
        Goal,
        Count
    };

    // DXライブラリの画像ハンドルと元画像の大きさを所有する
    struct ImageResource
    {
        int handle = -1;
        int width = 0;
        int height = 0;
    };

    // 画像の読み込み、解放、矩形描画をまとめて管理する
    class ImageManager
    {
    public:
        ImageManager() = default;
        ~ImageManager();

        ImageManager(const ImageManager&) = delete;
        ImageManager& operator=(const ImageManager&) = delete;

        // 必須画像をすべて読み込む
        bool LoadAll();
        // 識別子から画像情報を取得する
        const ImageResource& Get(ImageId id) const;
        // 画像を指定した矩形へ拡大縮小して描画する
        void Draw(ImageId id, const dxgame::Rect& rect) const;
        // スプライトシートの1フレームを指定した矩形へ描画する
        void DrawFrame(ImageId id, int frameIndex, int frameCount,
            const dxgame::Rect& rect, bool flipX) const;

    private:
        void Release();

        static constexpr std::size_t imageCount =
            static_cast<std::size_t>(ImageId::Count);

        std::array<ImageResource, imageCount> resources_ = {};
    };
}
Source/Platformer/Input.cpp
Source/Platformer/Input.cppcpp
#include "DxLib.h"

#include "Platformer/Input.h"

namespace dxgame::platformer
{
    void Input::Update()
    {
        previous_ = current_;

        for (std::size_t index = 0; index < keyCount; index++)
        {
            const Key key = static_cast<Key>(index);
            current_[index] = CheckHitKey(ToDxKey(key)) != 0;
        }
    }

    bool Input::IsHeld(Key key) const
    {
        return current_[static_cast<std::size_t>(key)];
    }

    bool Input::IsPressed(Key key) const
    {
        const std::size_t index = static_cast<std::size_t>(key);
        return current_[index] && !previous_[index];
    }

    bool Input::IsReleased(Key key) const
    {
        const std::size_t index = static_cast<std::size_t>(key);
        return !current_[index] && previous_[index];
    }

    int Input::ToDxKey(Key key)
    {
        switch (key)
        {
        case Key::Left:
            return KEY_INPUT_LEFT;
        case Key::Right:
            return KEY_INPUT_RIGHT;
        case Key::Jump:
            return KEY_INPUT_Z;
        case Key::Debug:
            return KEY_INPUT_F1;
        case Key::Escape:
            return KEY_INPUT_ESCAPE;
        case Key::Count:
            break;
        }

        return 0;
    }
}
Source/Platformer/Input.h
Source/Platformer/Input.hcpp
#pragma once

#include <array>
#include <cstddef>

namespace dxgame::platformer
{
    enum class Key
    {
        Left,
        Right,
        Jump,
        Debug,
        Escape,
        Count
    };

    // キーの現在状態と前フレーム状態を管理する
    class Input
    {
    public:
        // DXライブラリからキー状態を読み取り、前フレームとの差を更新する
        void Update();

        // キーが押され続けているかを返す
        bool IsHeld(Key key) const;
        // 今フレームに押されたかを返す
        bool IsPressed(Key key) const;
        // 今フレームに離されたかを返す
        bool IsReleased(Key key) const;

    private:
        static constexpr std::size_t keyCount =
            static_cast<std::size_t>(Key::Count);

        static int ToDxKey(Key key);

        std::array<bool, keyCount> current_ = {};
        std::array<bool, keyCount> previous_ = {};
    };
}
Source/Platformer/Player.cpp
Source/Platformer/Player.cppcpp
#include "DxGame/Rect.h"
#include "Platformer/ImageManager.h"
#include "Platformer/Player.h"

namespace dxgame::platformer
{
    void Player::Reset(const dxgame::Vector2& position)
    {
        position_ = position;
    }

    void Player::Draw(const ImageManager& images) const
    {
        const dxgame::Rect drawRect =
            dxgame::Rect::Centered(32.0f, 48.0f).MovedBy(position_);
        images.Draw(ImageId::Player, drawRect);
    }
}
Source/Platformer/Player.h
Source/Platformer/Player.hcpp
#pragma once

#include "DxGame/Vector2.h"

namespace dxgame::platformer
{
    class ImageManager;

    // 第13回では中央位置と描画だけを管理する
    class Player
    {
    public:
        void Reset(const dxgame::Vector2& position);
        void Draw(const ImageManager& images) const;

    private:
        dxgame::Vector2 position_ = {};
    };
}

ゲームプログラミング実践