Skip to content
第20回 アニメーションと音声イベント

4 / 4 ジャンプと着地に音を付ける

このステップの目的

音声の読み込みを SoundManager へまとめ、発生したイベントに応じて音を再生します。

音声の対応表を用意する

SoundManager.hSoundId を定義し、SoundManager.cpp の対応表へ音声を登録します。第20回で使う音は PlayingBgmJumpLand の3種類です。

SoundManager.cpp(名前空間内の定義)cpp
struct SoundDefinition
{
    SoundId id;
    const wchar_t* path;
    int volume;
};

constexpr SoundDefinition soundDefinitions[] =
{
    { SoundId::PlayingBgm, L"Assets/Sounds/PlayingBgm.wav", 140 },
    { SoundId::Jump, L"Assets/Sounds/Jump.wav", 200 },
    { SoundId::Land, L"Assets/Sounds/Land.wav", 180 }
};

volume は 0〜255 の音量です。件数の static_assert と登録内容を調べる補助関数は、参考実装を利用します。BGMとSEの区別は、再生するときに PlayBgm()PlaySe() を選んで指定します。

Gameから音声を利用する

Game.hSoundManager.h をインクルードし、sounds_ をメンバへ追加します。Initialize()sounds_.LoadAll() の結果を確認し、初期化成功後に sounds_.PlayBgm(SoundId::PlayingBgm) を呼び出します。

Game::Update()Player::Update() の戻り値を受け取り、HandlePlayerEvents() へ渡します。

Game.cppcpp
void Game::HandlePlayerEvents(const PlayerEvents& events)
{
    if (events.jumped)
        sounds_.PlaySe(SoundId::Jump);
    if (events.landed)
        sounds_.PlaySe(SoundId::Land);
}

ジャンプキーを押しただけでは音を鳴らしません。Player がジャンプを成立させた場合のみ鳴らします。

再生方法を確認する

PlayBgm() は再生中のBGMを停止してループ再生を開始します。PlaySe() は1回再生を開始してゲーム処理へ戻ります。同じ音を短い間隔で鳴らした場合の聞こえ方も確認します。

SoundManager.cppcpp
void SoundManager::PlayBgm(SoundId id)
{
    if (currentBgm_ == id)
        return;

    StopBgm();
    PlaySoundMem(handles_[static_cast<std::size_t>(id)], DX_PLAYTYPE_LOOP);
    currentBgm_ = id;
}

void SoundManager::PlaySe(SoundId id) const
{
    PlaySoundMem(handles_[static_cast<std::size_t>(id)], DX_PLAYTYPE_BACK);
}
チェックリスト
  • プレイ開始時にBGMが鳴る
  • ジャンプと着地の瞬間に、それぞれの音が1回鳴る
  • 空中で Z キーを押してもジャンプ音が鳴らない

参考実装

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

Source/Platformer/Game.cpp
Source/Platformer/Game.cppcpp
#include <Windows.h>
#include <string>

#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;

        const ImageResource& playerImage = images_.Get(ImageId::Player);
        if (playerImage.width % tuning::animation::PlayerFrameCount != 0)
        {
            MessageBoxW(nullptr,
                L"Player.pngの横幅を8で割り切れません。",
                L"Player Image Error", MB_OK);
            return false;
        }

        if (!sounds_.LoadAll())
            return false;
        constexpr wchar_t StagePath[] = L"Assets/Maps/Stage01.csv";
        if (!stage_.Initialize(StagePath))
        {
            const std::wstring message = std::wstring(StagePath) + L"\n" +
                stage_.GetLastLoadError();
            MessageBoxW(nullptr, message.c_str(), L"Map Load Error", MB_OK);
            return false;
        }

        ResetStage();
        sounds_.PlayBgm(SoundId::PlayingBgm);
        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();
        const PlayerEvents events = player_.Update(input_, stage_, deltaTime);
        HandlePlayerEvents(events);
        ResetAfterFall();
        camera_.Update(player_.GetPosition(), stage_.GetWorldWidth());
    }

    void Game::Draw() const
    {
        DrawBackground();
        stage_.Draw(camera_.GetX(), images_);
        player_.Draw(camera_, 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;

        stage_.DrawCollisionDebug(camera_.GetX());

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

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

        const dxgame::Rect goalDrawRect =
            camera_.WorldToScreen(stage_.GetGoalDrawRect());
        DrawBox(static_cast<int>(goalDrawRect.x),
            static_cast<int>(goalDrawRect.y),
            static_cast<int>(goalDrawRect.x + goalDrawRect.width),
            static_cast<int>(goalDrawRect.y + goalDrawRect.height),
            GetColor(80, 240, 255), FALSE);

        const dxgame::Rect goalRect =
            camera_.WorldToScreen(stage_.GetGoalTrigger());
        DrawBox(static_cast<int>(goalRect.x), static_cast<int>(goalRect.y),
            static_cast<int>(goalRect.x + goalRect.width),
            static_cast<int>(goalRect.y + goalRect.height),
            GetColor(255, 220, 40), FALSE);

        DrawBox(8, 76, 300, 178, GetColor(0, 0, 0), TRUE);
        const int textColor = GetColor(255, 255, 255);
        const dxgame::Vector2& position = player_.GetPosition();
        const dxgame::Vector2& velocity = player_.GetVelocity();
        DrawFormatString(14, 84, textColor,
            L"pos: %.1f, %.1f", position.x, position.y);
        DrawFormatString(14, 104, textColor,
            L"velocity: %.2f, %.2f", velocity.x, velocity.y);
        DrawFormatString(14, 124, textColor,
            L"ground: %d camera: %d", player_.IsOnGround() ? 1 : 0,
            camera_.GetX());
        DrawFormatString(14, 144, textColor, L"csv: %d",
            stage_.IsLoadedFromCsv() ? 1 : 0);
        DrawString(14, 164,
            L"cyan: draw  red/yellow: hit  blue: solid", textColor);
    }

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

    void Game::ResetAfterFall()
    {
        if (player_.GetPosition().y <=
            stage_.GetWorldHeight() + tuning::stage::FallResetMargin)
        {
            return;
        }

        ResetStage();
    }

    void Game::ResetStage()
    {
        player_.Reset(stage_.GetPlayerSpawn());
        camera_.Update(player_.GetPosition(), stage_.GetWorldWidth());
    }

    void Game::HandlePlayerEvents(const PlayerEvents& events)
    {
        if (events.jumped)
            sounds_.PlaySe(SoundId::Jump);
        if (events.landed)
            sounds_.PlaySe(SoundId::Land);
    }
}
Source/Platformer/Game.h
Source/Platformer/Game.hcpp
#pragma once

#include "Platformer/Camera.h"
#include "Platformer/ImageManager.h"
#include "Platformer/Input.h"
#include "Platformer/Player.h"
#include "Platformer/SoundManager.h"
#include "Platformer/Stage.h"

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

    private:
        void Update(float deltaTime);
        void Draw() const;
        void DrawBackground() const;
        void DrawHud() const;
        void DrawDebug() const;
        void UpdateDebugToggle();
        void ResetAfterFall();
        void ResetStage();
        void HandlePlayerEvents(const PlayerEvents& events);

        Input input_;
        ImageManager images_;
        SoundManager sounds_;
        Stage stage_;
        Camera camera_;
        Player player_;
        bool debugVisible_ = false;
    };
}
Source/Platformer/Player.cpp
Source/Platformer/Player.cppcpp
#include <algorithm>
#include <cmath>

#include "Platformer/Camera.h"
#include "Platformer/ImageManager.h"
#include "Platformer/Input.h"
#include "Platformer/Player.h"
#include "Platformer/Stage.h"
#include "Platformer/Tuning/GameTuning.h"

namespace dxgame::platformer
{
    void Player::Reset(const dxgame::Vector2& position)
    {
        position_ = position;
        velocity_ = {};
        remainderX_ = 0.0f;
        remainderY_ = 0.0f;
        onGround_ = false;
        animation_ = PlayerAnimation::Idle;
        animationFrame_ = 0;
        animationTimer_ = 0;
        facingRight_ = true;
    }

    PlayerEvents Player::Update(const Input& input, const Stage& stage,
        float deltaTime)
    {
        PlayerEvents events;
        UpdateInput(input, deltaTime, events);
        UpdatePhysics(stage, deltaTime, events);
        UpdateAnimation();
        return events;
    }

    void Player::Draw(const Camera& camera, const ImageManager& images) const
    {
        const dxgame::Rect screenRect = camera.WorldToScreen(GetDrawRect());
        images.DrawFrame(ImageId::Player, GetAnimationFrame(),
            tuning::animation::PlayerFrameCount, screenRect, !facingRight_);
    }

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

    const dxgame::Vector2& Player::GetVelocity() const
    {
        return velocity_;
    }

    bool Player::IsOnGround() const
    {
        return onGround_;
    }

    PlayerAnimation Player::GetAnimation() const
    {
        return animation_;
    }

    int Player::GetAnimationFrame() const
    {
        switch (animation_)
        {
        case PlayerAnimation::Idle:
            return animationFrame_;
        case PlayerAnimation::Run:
            return 2 + animationFrame_;
        case PlayerAnimation::Jump:
            return 6;
        case PlayerAnimation::Fall:
            return 7;
        }

        return 0;
    }

    void Player::UpdateInput(const Input& input, float deltaTime,
        PlayerEvents& events)
    {
        const bool moveLeft = input.IsHeld(Key::Left);
        const bool moveRight = input.IsHeld(Key::Right);

        if (moveLeft && !moveRight)
        {
            velocity_.x -= tuning::player::Acceleration * deltaTime;
            facingRight_ = false;
        }
        else if (moveRight && !moveLeft)
        {
            velocity_.x += tuning::player::Acceleration * deltaTime;
            facingRight_ = true;
        }
        else if (velocity_.x > 0.0f)
        {
            velocity_.x = std::max(0.0f,
                velocity_.x - tuning::player::Friction * deltaTime);
        }
        else if (velocity_.x < 0.0f)
        {
            velocity_.x = std::min(0.0f,
                velocity_.x + tuning::player::Friction * deltaTime);
        }

        velocity_.x = std::clamp(velocity_.x,
            -tuning::player::MaxSpeed, tuning::player::MaxSpeed);

        if (onGround_ && input.IsPressed(Key::Jump))
        {
            velocity_.y = tuning::player::JumpPower;
            remainderY_ = 0.0f;
            onGround_ = false;
            events.jumped = true;
        }
    }

    void Player::UpdatePhysics(const Stage& stage, float deltaTime,
        PlayerEvents& events)
    {
        const bool wasOnGround = onGround_;
        const float gravity = velocity_.y < 0.0f
            ? tuning::player::RiseGravity
            : tuning::player::FallGravity;
        velocity_.y += gravity * deltaTime;
        velocity_.y = std::min(velocity_.y,
            tuning::player::MaxFallSpeed);

        MoveHorizontal(stage, deltaTime);
        MoveVertical(stage, deltaTime);

        dxgame::Rect groundProbe = GetHitbox();
        groundProbe.y += 1.0f;
        onGround_ = stage.IsSolidAtRect(groundProbe);
        if (onGround_ && velocity_.y >= 0.0f)
        {
            velocity_.y = 0.0f;
            remainderY_ = 0.0f;
        }

        if (!wasOnGround && onGround_)
            events.landed = true;
    }

    void Player::UpdateAnimation()
    {
        PlayerAnimation nextAnimation = PlayerAnimation::Idle;
        if (!onGround_)
        {
            nextAnimation = velocity_.y < 0.0f
                ? PlayerAnimation::Jump
                : PlayerAnimation::Fall;
        }
        else if (std::abs(velocity_.x) > 0.1f)
        {
            nextAnimation = PlayerAnimation::Run;
        }

        if (nextAnimation != animation_)
        {
            animation_ = nextAnimation;
            animationFrame_ = 0;
            animationTimer_ = 0;
        }

        const int frameCount = animation_ == PlayerAnimation::Idle
            ? tuning::animation::IdleFrameCount
            : animation_ == PlayerAnimation::Run
            ? tuning::animation::RunFrameCount
            : 1;
        const int frameDuration = animation_ == PlayerAnimation::Idle
            ? tuning::animation::IdleFrameDuration
            : tuning::animation::RunFrameDuration;

        animationTimer_++;
        if (animationTimer_ >= frameDuration)
        {
            animationTimer_ = 0;
            animationFrame_ = (animationFrame_ + 1) % frameCount;
        }
    }

    void Player::MoveHorizontal(const Stage& stage, float deltaTime)
    {
        remainderX_ += velocity_.x * deltaTime;
        const int pixelMove = static_cast<int>(remainderX_);
        remainderX_ -= static_cast<float>(pixelMove);

        if (pixelMove == 0)
        {
            const int probeMove = velocity_.x > 0.0f ? 1
                : velocity_.x < 0.0f ? -1 : 0;
            if (probeMove != 0)
            {
                dxgame::Rect probe = GetHitbox();
                if (stage.ResolveHorizontal(probe, probeMove))
                {
                    velocity_.x = 0.0f;
                    remainderX_ = 0.0f;
                }
            }
            return;
        }

        dxgame::Rect movedRect = GetHitbox();
        if (stage.ResolveHorizontal(movedRect, pixelMove))
        {
            velocity_.x = 0.0f;
            remainderX_ = 0.0f;
        }

        position_.x = movedRect.GetCenter().x;
    }

    void Player::MoveVertical(const Stage& stage, float deltaTime)
    {
        remainderY_ += velocity_.y * deltaTime;
        const int pixelMove = static_cast<int>(remainderY_);
        remainderY_ -= static_cast<float>(pixelMove);
        if (pixelMove == 0)
            return;

        dxgame::Rect movedRect = GetHitbox();
        if (stage.ResolveVertical(movedRect, pixelMove))
        {
            velocity_.y = 0.0f;
            remainderY_ = 0.0f;
        }

        position_.y = movedRect.GetCenter().y;
    }
}
Source/Platformer/Player.h
Source/Platformer/Player.hcpp
#pragma once

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

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

    // Player内で成立した出来事を、音や演出へ接続するためにGameへ返す
    struct PlayerEvents
    {
        bool jumped = false;
        bool landed = false;
    };

    enum class PlayerAnimation
    {
        Idle,
        Run,
        Jump,
        Fall
    };

    // プレイヤーの位置、速度、接地状態、表示アニメーションを管理する
    class Player
    {
    public:
        // プレイ開始時の状態へ戻す
        void Reset(const dxgame::Vector2& position);
        // 入力、重力、地形衝突、アニメーションを1フレーム更新する
        PlayerEvents Update(const Input& input, const Stage& stage,
            float deltaTime);
        // カメラを考慮してプレイヤーを描画する
        void Draw(const Camera& camera, const ImageManager& images) const;

        dxgame::Rect GetHitbox() const;
        dxgame::Rect GetDrawRect() const;
        const dxgame::Vector2& GetPosition() const;
        const dxgame::Vector2& GetVelocity() const;
        bool IsOnGround() const;
        PlayerAnimation GetAnimation() const;
        int GetAnimationFrame() const;

    private:
        void UpdateInput(const Input& input, float deltaTime,
            PlayerEvents& events);
        void UpdatePhysics(const Stage& stage, float deltaTime,
            PlayerEvents& events);
        void UpdateAnimation();
        void MoveHorizontal(const Stage& stage, float deltaTime);
        void MoveVertical(const Stage& stage, float deltaTime);

        dxgame::Vector2 position_ = {};
        dxgame::Vector2 velocity_ = {};
        float remainderX_ = 0.0f;
        float remainderY_ = 0.0f;
        bool onGround_ = false;
        PlayerAnimation animation_ = PlayerAnimation::Idle;
        int animationFrame_ = 0;
        int animationTimer_ = 0;
        bool facingRight_ = true;
    };
}
Source/Platformer/SoundManager.cpp
Source/Platformer/SoundManager.cppcpp
#include <Windows.h>
#include <array>
#include <string>

#include "DxLib.h"

#include "Platformer/SoundManager.h"

namespace dxgame::platformer
{
    namespace
    {
        struct SoundDefinition
        {
            SoundId id;
            const wchar_t* path;
            int volume;
        };

        constexpr SoundDefinition soundDefinitions[] =
        {
            { SoundId::PlayingBgm, L"Assets/Sounds/PlayingBgm.wav", 140 },
            { SoundId::Jump, L"Assets/Sounds/Jump.wav", 200 },
            { SoundId::Land, L"Assets/Sounds/Land.wav", 180 }
        };

        constexpr std::size_t SoundIdCount =
            static_cast<std::size_t>(SoundId::Count);
        constexpr std::size_t SoundDefinitionCount =
            sizeof(soundDefinitions) / sizeof(soundDefinitions[0]);

        static_assert(SoundDefinitionCount == SoundIdCount,
            "Register one soundDefinitions entry for every SoundId.");

        bool HasCompleteSoundDefinitions()
        {
            std::array<bool, SoundIdCount> registered = {};

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

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

    SoundManager::SoundManager()
    {
        handles_.fill(-1);
    }

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

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

        if (!HasCompleteSoundDefinitions())
        {
            MessageBoxW(nullptr,
                L"SoundIdと音声定義の登録に重複、不足、または不正な音量があります。",
                L"Sound Definition Error", MB_OK);
            return false;
        }

        for (const SoundDefinition& definition : soundDefinitions)
        {
            const std::size_t index = static_cast<std::size_t>(definition.id);
            handles_[index] = LoadSoundMem(definition.path);
            if (handles_[index] == -1)
            {
                std::wstring message = definition.path;
                message += L" の読み込みに失敗しました。";
                MessageBoxW(nullptr, message.c_str(), L"Sound Load Error", MB_OK);
                Release();
                return false;
            }

            ChangeVolumeSoundMem(definition.volume, handles_[index]);
        }

        return true;
    }

    void SoundManager::PlayBgm(SoundId id)
    {
        if (currentBgm_ == id)
            return;

        StopBgm();
        PlaySoundMem(handles_[static_cast<std::size_t>(id)], DX_PLAYTYPE_LOOP);
        currentBgm_ = id;
    }

    void SoundManager::PlaySe(SoundId id) const
    {
        PlaySoundMem(handles_[static_cast<std::size_t>(id)], DX_PLAYTYPE_BACK);
    }

    void SoundManager::StopBgm()
    {
        if (currentBgm_ == SoundId::Count)
            return;

        StopSoundMem(handles_[static_cast<std::size_t>(currentBgm_)]);
        currentBgm_ = SoundId::Count;
    }

    void SoundManager::Release()
    {
        StopBgm();

        for (int& handle : handles_)
        {
            if (handle != -1)
                DeleteSoundMem(handle);

            handle = -1;
        }
    }
}
Source/Platformer/SoundManager.h
Source/Platformer/SoundManager.hcpp
#pragma once

#include <array>
#include <cstddef>

namespace dxgame::platformer
{
    enum class SoundId
    {
        PlayingBgm,
        Jump,
        Land,
        Count
    };

    // DXライブラリの音声ハンドルを所有し、BGMとSEを管理する
    class SoundManager
    {
    public:
        SoundManager();
        ~SoundManager();

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

        // 必須音声をすべて読み込む
        bool LoadAll();
        // BGMをループ再生する。同じBGMは再起動しない
        // BGMをループ再生する(同一BGMの多重再生を防止)
        void PlayBgm(SoundId id);
        // SEを一度再生する
        void PlaySe(SoundId id) const;
        // 再生中のBGMを停止する
        void StopBgm();

    private:
        void Release();

        static constexpr std::size_t soundCount =
            static_cast<std::size_t>(SoundId::Count);

        std::array<int, soundCount> handles_ = {};
        SoundId currentBgm_ = SoundId::Count;
    };
}
Source/Platformer/Tuning/GameTuning.h
Source/Platformer/Tuning/GameTuning.hcpp
#pragma once

#include "Config/ApplicationConfig.h"
#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);

        // 左右入力中の加速度、最大速度、無入力時の減速度
        // 速度の単位はピクセル/秒、加減速度はピクセル/秒²
        inline constexpr float Acceleration = 1260.0f;
        inline constexpr float MaxSpeed = 270.0f;
        inline constexpr float Friction = 900.0f;

        // 上昇中と落下中の重力、および最大落下速度
        // 重力の単位はピクセル/秒²、速度はピクセル/秒
        inline constexpr float RiseGravity = 1620.0f;
        inline constexpr float FallGravity = 2700.0f;
        inline constexpr float MaxFallSpeed = 720.0f;

        // ジャンプ開始時の上向き速度 単位はピクセル/秒
        // ジャンプ開始時の上向き速度(単位:ピクセル/秒)
        // 画面上方向はY座標が減るため負の値にする
        inline constexpr float JumpPower = -660.0f;

    }

    namespace stage
    {
        // タイル画像の元サイズとは独立した、ワールド上の1マスの大きさ
        inline constexpr int TileSize = 32;

        // マップ下端からこの距離を越えたら落下とみなす
        inline constexpr float FallResetMargin = 128.0f;
    }

    namespace camera
    {
        // 追従対象を画面内のどのX座標へ置くか
        inline constexpr float TargetScreenX =
            dxgame::config::ScreenWidth * 0.5f;
    }

    namespace animation
    {
        // Player.pngに横一列で並ぶフレームの総数
        inline constexpr int PlayerFrameCount = 8;

        // 待機と走行に使うフレーム数
        inline constexpr int IdleFrameCount = 2;
        inline constexpr int RunFrameCount = 4;

        // 各1枚を表示し続ける更新フレーム数
        inline constexpr int IdleFrameDuration = 20;
        inline constexpr int RunFrameDuration = 6;
    }

    namespace enemy
    {
        // 敵の中心座標を基準にした表示範囲と命中範囲
        inline constexpr Rect DrawRect = Rect::Centered(32.0f, 32.0f);
        inline constexpr Rect Hitbox = Rect::Centered(28.0f, 28.0f);
    }

    namespace goal
    {
        // ゴール画像の表示範囲と、クリア成立に使う範囲
        inline constexpr Rect DrawRect = Rect::Centered(32.0f, 32.0f);
        inline constexpr Rect Trigger = Rect::Centered(20.0f, 28.0f);
    }
}

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