Skip to content
第14回 調整できるプレイヤー表示と操作

4 / 4 デバッグ表示による矩形の可視化と動作確認

このステップの目的

F1 キーによるデバッグ表示切り替え機能を実装し、画面上のグラフィック、表示矩形、および命中矩形の関係を視覚的に検証します。

トグル入力によるデバッグ表示の切り替え

デバッグ表示のON/OFF切り替えには、キーを押し続けている状態(IsHeld)ではなく、「押された瞬間(IsPressed)」を使用します。

Game.cppcpp
// F1キーが押された瞬間だけ、デバッグ表示フラグを反転させる(トグル)
if (input_.IsPressed(Key::Debug))
{
    debugVisible_ = !debugVisible_;
}

IsHeld で切り替えると、キーを押している間、毎フレーム表示と非表示が反転して点滅してしまいます。1回の押下で状態を反転させるトグル処理には、トリガー判定(IsPressed)を用います。

表示矩形と命中矩形の枠線描画

debugVisible_true の場合、通常の描画の上に重ねてデバッグ枠線を描画します。
DXライブラリの DrawBox() の最後の引数を FALSE に設定することで、塗りつぶさずに外枠(ワイヤーフレーム)だけを描画できます。

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

動作確認の手順

プロジェクトをビルドして実行し、以下の項目を順に確認します。

  1. 左右移動の確認:
    • キーを押すと左へ、 キーを押すと右へ、プレイヤーが移動することを確認します。
    • 左右のキーを同時に押した際、プレイヤーが静止することを確認します。
  2. 画面端制限の確認:
    • 画面の左右両端まで移動させた際、プレイヤーの命中矩形が画面外へ出ずに停止することを確認します。
  3. デバッグ表示の確認:
    • F1 キーを1回押すと、水色の枠(表示領域)と赤色の枠(当たり判定)が表示されることを確認します。
    • もう一度 F1 キーを押すと、枠線が非表示になることを確認します。
    • 赤い枠(命中矩形)が表示領域よりも一回り小さく、内側に収まっていることを確認します。

まとめと次回への引き継ぎ

第14回では、中心座標系を用いた矩形計算、GameTuning.h による表示と判定の分離、そしてデバッグ可視化機能を実装しました。これでプレイヤーキャラクターの移動と当たり判定の検証環境が整いました。

次回(第15回)は、ステージのマップデータをCSVファイルから読み込み、列挙型 StageCell を用いてブロックやゴールを画面上に配置するステージシステムを構築します。

チェックリスト
  • F1 キーを押すたびに、デバッグ枠線の表示・非表示が切り替わる
  • 水色の枠(DrawRect)と赤色の枠(Hitbox)が正しく重ねて描画される
  • 左右移動および同時押しの相殺が正常に機能していることを確認できる
  • 画面の左右端で命中矩形が境界制限されていることを確認できる

参考実装

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

Source/Platformer/Game.cpp
Source/Platformer/Game.cppcpp
#include "DxLib.h"

#include "Config/ApplicationConfig.h"
#include "Platformer/Game.h"
#include "Platformer/Tuning/GameTuning.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;

            Update(dxgame::config::DeltaTime);

            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::Update(float deltaTime)
    {
        UpdateDebugToggle();
        player_.Update(input_, deltaTime);
    }

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

    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"LEFT/RIGHT: MOVE", textColor);
        DrawString(12, 30, L"Z: JUMP", textColor);
        DrawString(12, 50, L"F1: DEBUG    ESC: QUIT", textColor);
    }

    void Game::DrawDebug() const
    {
        if (!debugVisible_)
            return;

        const dxgame::Rect drawRect = player_.GetDrawRect();
        DrawBox(static_cast<int>(drawRect.x), static_cast<int>(drawRect.y),
            static_cast<int>(drawRect.x + drawRect.width),
            static_cast<int>(drawRect.y + drawRect.height),
            GetColor(80, 240, 255), FALSE);

        const dxgame::Rect hitbox = player_.GetHitbox();
        DrawBox(static_cast<int>(hitbox.x), static_cast<int>(hitbox.y),
            static_cast<int>(hitbox.x + hitbox.width),
            static_cast<int>(hitbox.y + hitbox.height),
            GetColor(255, 80, 80), FALSE);

        DrawBox(8, 76, 300, 178, GetColor(0, 0, 0), TRUE);
        const int textColor = GetColor(255, 255, 255);
        const dxgame::Vector2& position = player_.GetPosition();
        DrawFormatString(14, 84, textColor,
            L"pos: %.1f, %.1f", position.x, position.y);
        DrawString(14, 104, L"cyan: draw  red: hitbox", textColor);
    }

    void Game::UpdateDebugToggle()
    {
        if (input_.IsPressed(Key::Debug))
            debugVisible_ = !debugVisible_;
    }

}
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 Update(float deltaTime);
        void Draw() const;
        void DrawBackground() const;
        void DrawHud() const;
        void DrawDebug() const;
        void UpdateDebugToggle();

        Input input_;
        ImageManager images_;
        Player player_;
        bool debugVisible_ = false;
    };
}
Source/Platformer/Player.cpp
Source/Platformer/Player.cppcpp
#include <algorithm>

#include "Config/ApplicationConfig.h"
#include "Platformer/ImageManager.h"
#include "Platformer/Input.h"
#include "Platformer/Player.h"
#include "Platformer/Tuning/GameTuning.h"

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

    void Player::Update(const Input& input, float deltaTime)
    {
        float direction = 0.0f;
        if (input.IsHeld(Key::Left) && !input.IsHeld(Key::Right))
            direction = -1.0f;
        else if (input.IsHeld(Key::Right) && !input.IsHeld(Key::Left))
            direction = 1.0f;

        position_.x += direction *
            tuning::player::MoveSpeed * deltaTime;

        const float minX = -tuning::player::Hitbox.x;
        const float maxX = dxgame::config::ScreenWidth -
            tuning::player::Hitbox.x - tuning::player::Hitbox.width;
        position_.x = std::clamp(position_.x, minX, maxX);
    }

    void Player::Draw(const ImageManager& images) const
    {
        images.Draw(ImageId::Player, GetDrawRect());
    }

    dxgame::Rect Player::GetHitbox() const
    {
        return tuning::player::Hitbox.MovedBy(position_);
    }

    dxgame::Rect Player::GetDrawRect() const
    {
        return tuning::player::DrawRect.MovedBy(position_);
    }

    const dxgame::Vector2& Player::GetPosition() const
    {
        return position_;
    }
}
Source/Platformer/Player.h
Source/Platformer/Player.hcpp
#pragma once

#include "DxGame/Rect.h"
#include "DxGame/Vector2.h"

namespace dxgame::platformer
{
    class ImageManager;
    class Input;

    // プレイヤーのワールド位置を管理する
    class Player
    {
    public:
        void Reset(const dxgame::Vector2& position);
        void Update(const Input& input, float deltaTime);
        void Draw(const ImageManager& images) const;

        dxgame::Rect GetHitbox() const;
        dxgame::Rect GetDrawRect() const;
        const dxgame::Vector2& GetPosition() const;

    private:
        dxgame::Vector2 position_ = {};
    };
}
Source/Platformer/Tuning/GameTuning.h
Source/Platformer/Tuning/GameTuning.hcpp
#pragma once

#include "DxGame/Rect.h"

namespace dxgame::platformer::tuning
{
    // 距離はピクセル、時間は秒、速度はピクセル/秒、
    // 加速度と重力はピクセル/秒²を基本単位とする。
    // 加速度と重力はピクセル/秒²を基本単位とする
    namespace player
    {
        // 中心座標を基準にした表示範囲と地形判定範囲
        // 画像本来の大きさとは独立して調整する
        inline constexpr Rect DrawRect =
            Rect::Centered(32.0f, 48.0f).MovedBy({ 0.0f, -8.0f });
        inline constexpr Rect Hitbox = Rect::Centered(24.0f, 30.0f);

        // 第16回までは一定速度で左右へ移動する
        inline constexpr float MoveSpeed = 240.0f;

    }

}

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