Skip to content
第05回 プレイヤーを狙う敵弾

4 / 4 敵弾の動作確認 ​

このステップの目的

敵弾が発射時の自機を狙うこと、速さが一定であること、プールで再利用されることを確認します。次回は被弾処理を追加します。

ゲームループへの組み込みと描画順序 ​

完成したEnemyShotManagerをGameクラスの更新・描画ループへ接続します。

Game.cppcpp
void Game::Update(float deltaTime)
{
    player.Update(input, deltaTime);
    playerShots.Update(deltaTime);
    enemies.Update(deltaTime);
    enemyShots.Update(deltaTime);

    ResolveCollisions();

    Vector2 playerPosition = player.GetHitbox().GetCenter();
    for (Enemy& enemy : enemies.GetAll())
    {
        enemy.TryShoot(enemyShots, playerPosition);
    }

    player.TryShoot(input, playerShots);
}

void Game::Draw() const
{
    // 描画順はプレイヤー、プレイヤーの弾、敵、敵の弾の順
    player.Draw(images);
    playerShots.Draw(images);
    enemies.Draw(images);
    enemyShots.Draw(images);
    DrawFormatString(16, 16, GetColor(255, 255, 255), L"SCORE %02d", score);
}

敵弾を敵より手前、または自然に見える層へ描くと、どの敵から撃たれたかが分かりやすくなります。

動作確認の手順 ​

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

  1. 敵弾の発射: 画面上から出現した敵が、機体の先端から敵弾を発射することを確認します。
  2. 発射時の狙い撃ち: 敵弾が発射された瞬間のプレイヤー位置へ向かってまっすぐ進むことを確認します。
  3. 追尾しないことの確認: 弾が発射された後に自機を左右へ移動させても、すでに飛んでいる敵弾が方向を変えて追いかけてこない(直線移動を保つ)ことを確認します。
  4. 斜め移動時の速度の均一性: 近い位置から撃たれた弾も、遠い位置から撃たれた弾も、すべて同じスピードで飛んでくることを確認します(正規化の確認)。
  5. 画面外での消滅: 画面の左右や下端を越えた敵弾が安全に消滅し、プールの空きスロットに戻ることを確認します。

この段階では、敵弾に触れてもダメージを受けずにすり抜けます。次回追加する被弾処理が、まだ実装されていないためです。

チェックリスト
  • 敵が発射時点のプレイヤー位置を狙って弾を発射することを確認できる
  • 敵弾が追尾せず、発射時に決まった方向へ等速直進することを確認できる
  • 画面外へ出た敵弾が停止し、プールへ戻って再利用されることを確認できる
  • プレイヤー弾と敵弾が独立したクラス・管理クラスで安全に管理されていることを説明できる

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

第05回では、2点間の差分ベクトルと正規化を用いた自機狙い弾を実装しました。発射後に自機を動かして、弾が発射時の方向へ進み続けることを確認します。接触によるダメージは次回に追加します。

第06回では、プレイヤーの被弾処理と無敵時間を実装します。敵や敵弾に当たるとHPを減らし、その後の一定時間はダメージを受けないようにして連続被弾を防ぎます。

参考実装 ​

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

Source/Shooting/Enemy.cpp
Source/Shooting/Enemy.cppcpp
#include <cmath>

#include "Shooting/ImageManager.h"
#include "Shooting/Tuning/GameTuning.h"
#include "Shooting/EnemyShotManager.h"

#include "Shooting/Enemy.h"

namespace dxgame::shooting
{
    void Enemy::Spawn(const Vector2& startPosition, float speed, Behaviour spawnBehaviour)
    {
        position = startPosition;
        baseX = startPosition.x;
        moveSpeed = speed;
        age = 0.0f;
        shotTimer = tuning::enemy::ShotDelay;
        hp = tuning::enemy::MaxHp;
        behaviour = spawnBehaviour;
        isActive = true;
    }

    void Enemy::Update(float deltaTime)
    {
        if (!isActive)
            return;

        age += deltaTime;
        shotTimer -= deltaTime;
        position.y += moveSpeed * deltaTime;

        if (behaviour == Behaviour::Wave)
        {
            float waveOffset = std::sin(age * tuning::enemy::WaveSpeed)
                * tuning::enemy::WaveAmplitude;
            position.x = baseX + waveOffset;
        }

        if (GetHitbox().y > tuning::ScreenHeight)
            Deactivate();
    }

    void Enemy::Draw(const ImageManager& images) const
    {
        if (!isActive)
            return;

        images.Draw(ImageManager::Id::Enemy, GetDrawRect());
    }

    void Enemy::TakeDamage(int damage)
    {
        hp -= damage;

        if (hp <= 0)
            Deactivate();
    }

    bool Enemy::TryShoot(EnemyShotManager& shots, const Vector2& targetPosition)
    {
        if (!isActive || shotTimer > 0.0f)
            return false;

        if (!shots.Spawn(GetMuzzlePosition(), targetPosition))
            return false;

        shotTimer = tuning::enemy::ShotInterval;
        return true;
    }

    bool Enemy::IsActive() const
    {
        return isActive;
    }

    void Enemy::Deactivate()
    {
        isActive = false;
    }

    Rect Enemy::GetDrawRect() const
    {
        return tuning::enemy::DrawRect.MovedBy(position);
    }

    Rect Enemy::GetHitbox() const
    {
        return tuning::enemy::Hitbox.MovedBy(position);
    }

    Vector2 Enemy::GetMuzzlePosition() const
    {
        Rect hitbox = GetHitbox();

        return
        {
            hitbox.GetCenter().x,
            hitbox.y + hitbox.height + tuning::enemyShot::Hitbox.height * 0.5f
        };
    }
}
Source/Shooting/Enemy.h
Source/Shooting/Enemy.hcpp
#pragma once

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

namespace dxgame::shooting
{
    class ImageManager;
    class EnemyShotManager;

    // 敵一体の位置、移動、発射間隔、表示、実行状態を管理する
    // 敵1体の位置、移動、発射間隔、表示、実行状態を管理する
    class Enemy
    {
    public:
        // 下方向へ直進するか左右へ波打ちながら進むかを表す
        enum class Behaviour
        {
            Straight,
            Wave
        };

        // 中心座標、速度、移動方法を設定して敵を出現させる
        void Spawn(const Vector2& startPosition, float speed, Behaviour spawnBehaviour);

        // 敵を移動させ、画面の下へ出たら停止する
        void Update(float deltaTime);

        // 実行中の敵を画像で描く
        void Draw(const ImageManager& images) const;

        // 発射間隔を確認し、敵弾を生成できたらtrueを返す
        bool TryShoot(EnemyShotManager& shots, const Vector2& targetPosition);

        // HPからdamageを減らし0以下になったら停止する
        void TakeDamage(int damage);

        // 更新と描画の対象ならtrueを返す
        bool IsActive() const;

        // 敵を更新と描画の対象外にする
        void Deactivate();

        // 表示用Rectを現在位置へ移動して返す
        Rect GetDrawRect() const;

        // 現在位置から求めた命中判定用Rectを返す
        Rect GetHitbox() const;

    private:
        // 敵の下端から発射する弾の中心座標を返す
        Vector2 GetMuzzlePosition() const;
        // 敵の中心座標
        Vector2 position = {};
        float baseX = 0.0f;
        float moveSpeed = 0.0f;
        float age = 0.0f;
        float shotTimer = 0.0f;
        int hp = 0;
        Behaviour behaviour = Behaviour::Straight;
        bool isActive = false;
    };
}
Source/Shooting/EnemyShot.cpp
Source/Shooting/EnemyShot.cppcpp
#include <cmath>

#include "Shooting/ImageManager.h"
#include "Shooting/Tuning/GameTuning.h"

#include "Shooting/EnemyShot.h"

namespace dxgame::shooting
{
    void EnemyShot::Spawn(const Vector2& startPosition, const Vector2& targetPosition)
    {
        position = startPosition;
        Vector2 direction = {
            targetPosition.x - position.x,
            targetPosition.y - position.y
        };
        float distance = std::sqrt(direction.x * direction.x + direction.y * direction.y);

        // 敵とプレイヤーの位置が異なるためdistanceは0にならない
        // 方向を長さ1にそろえ角度によらず同じ速さにする
        velocity = {
            direction.x / distance * tuning::enemyShot::Speed,
            direction.y / distance * tuning::enemyShot::Speed
        };
        isActive = true;
    }

    void EnemyShot::Update(float deltaTime)
    {
        if (!isActive)
            return;

        position.x += velocity.x * deltaTime;
        position.y += velocity.y * deltaTime;

        // Hitbox全体が画面外へ出た弾を再利用できる状態に戻す
        Rect hitbox = GetHitbox();
        if (hitbox.x + hitbox.width < 0.0f ||
            hitbox.x > tuning::ScreenWidth ||
            hitbox.y + hitbox.height < 0.0f ||
            hitbox.y > tuning::ScreenHeight)
            Deactivate();
    }

    void EnemyShot::Draw(const ImageManager& images) const
    {
        if (!isActive)
            return;

        images.Draw(ImageManager::Id::EnemyShot, GetDrawRect());
    }

    bool EnemyShot::IsActive() const
    {
        return isActive;
    }

    void EnemyShot::Deactivate()
    {
        isActive = false;
    }

    Rect EnemyShot::GetDrawRect() const
    {
        return tuning::enemyShot::DrawRect.MovedBy(position);
    }

    Rect EnemyShot::GetHitbox() const
    {
        return tuning::enemyShot::Hitbox.MovedBy(position);
    }
}
Source/Shooting/EnemyShot.h
Source/Shooting/EnemyShot.hcpp
#pragma once

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

namespace dxgame::shooting
{
    // 敵が発射する1発の弾の位置、速度、実行状態を管理する
    class EnemyShot
    {
    public:
        // 中心をstartPositionへ移動し発射時点のtargetPositionへ向かう速度を設定する
        // 発射後はtargetPositionを追尾しない
        void Spawn(const Vector2& startPosition, const Vector2& targetPosition);

        // 発射時の速度で移動し画面外へ出たら停止する
        // deltaTimeは1フレームの経過秒数
        void Update(float deltaTime);

        // 実行中の弾を表示用Rectに合わせて描く
        void Draw(const class ImageManager& images) const;

        // 更新、描画、命中判定の対象ならtrueを返す
        bool IsActive() const;

        // 弾を更新、描画、命中判定の対象外にする
        void Deactivate();

        // 表示用Rectを現在位置へ移動して返す
        Rect GetDrawRect() const;

        // 現在位置から求めた命中判定用Rectを返す
        Rect GetHitbox() const;

    private:
        // 弾の中心座標
        Vector2 position = {};
        Vector2 velocity = {};
        bool isActive = false;
    };
}
Source/Shooting/EnemyShotManager.cpp
Source/Shooting/EnemyShotManager.cppcpp
#include "Shooting/EnemyShotManager.h"

namespace dxgame::shooting
{
    bool EnemyShotManager::Spawn(const Vector2& startPosition, const Vector2& targetPosition)
    {
        // 先頭から停止中の弾を探して再利用する
        for (EnemyShot& shot : shots)
        {
            if (shot.IsActive())
                continue;

            shot.Spawn(startPosition, targetPosition);
            return true;
        }
        return false;
    }

    void EnemyShotManager::Update(float deltaTime)
    {
        for (EnemyShot& shot : shots)
            shot.Update(deltaTime);
    }

    void EnemyShotManager::Draw(const ImageManager& images) const
    {
        for (const EnemyShot& shot : shots)
            shot.Draw(images);
    }

    void EnemyShotManager::Reset()
    {
        for (EnemyShot& shot : shots)
            shot.Deactivate();
    }
}
Source/Shooting/EnemyShotManager.h
Source/Shooting/EnemyShotManager.hcpp
#pragma once

#include <array>
#include <cstddef>

#include "Shooting/EnemyShot.h"

namespace dxgame::shooting
{
    // 敵弾を固定数所有し生成、更新、描画、再利用を管理する
    class EnemyShotManager
    {
    public:
        // 停止中の弾の中心をstartPositionに合わせtargetPositionへ向けて生成する
        // 空きがない場合は生成せずfalseを返す
        bool Spawn(const Vector2& startPosition, const Vector2& targetPosition);

        // 所有するすべての敵弾を更新する
        // deltaTimeは1フレームの経過秒数
        void Update(float deltaTime);

        // 所有するすべての敵弾を描く
        void Draw(const class ImageManager& images) const;

        // 所有するすべての敵弾を停止して空の状態へ戻す
        void Reset();

    private:
        // Gameだけが命中判定とデバッグ表示のために内部配列を参照できる
        friend class Game;

        // 停止中の要素を再利用しゲーム中にはメモリを確保しない
        static constexpr std::size_t Capacity = 48;

        // Gameが命中判定で状態を変更できるようすべての弾への参照を返す
        std::array<EnemyShot, Capacity>& GetAll() { return shots; }

        // Gameが状態を変更せず参照できるようすべての弾へのconst参照を返す
        const std::array<EnemyShot, Capacity>& GetAll() const { return shots; }

        std::array<EnemyShot, Capacity> shots = {};
    };
}
Source/Shooting/Game.cpp
Source/Shooting/Game.cppcpp
#include <DxLib.h>

#include <string>

#include <Windows.h>

#include "Config/ApplicationConfig.h"
#include "Shooting/Tuning/GameTuning.h"

#include "Shooting/Game.h"

namespace
{
    void ShowResourceLoadError(
        const wchar_t* resourceType,
        const std::wstring& failedPath)
    {
        std::wstring message = resourceType;
        message += L"の読み込みに失敗しました。\n\n対象: ";
        message += failedPath;
        message += L"\n\nAssetsフォルダーとファイル名を確認してください。";

        MessageBoxW(
            nullptr,
            message.c_str(),
            L"リソース読み込みエラー",
            MB_OK | MB_ICONERROR);
    }
}

namespace dxgame::shooting
{
    namespace config = dxgame::config;

    bool Game::Run()
    {
        const ImageManager::LoadResult imageResult = images.LoadAll();
        if (!imageResult.succeeded)
        {
            ShowResourceLoadError(L"画像", imageResult.failedPath);
            return false;
        }

        player.Reset();
        playerShots.Reset();
        enemies.Reset();
        enemyShots.Reset();
        score = 0;

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

        while (ProcessMessage() == 0)
        {
            input.Update();
            if (input.IsDown(Input::Action::Exit))
                break;

            Update(config::DeltaTime);
            ClearDrawScreen();
            Draw();
            ScreenFlip();

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

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

        return true;
    }

    void Game::Update(float deltaTime)
    {
        player.Update(input, deltaTime);
        playerShots.Update(deltaTime);
        enemies.Update(deltaTime);
        enemyShots.Update(deltaTime);

        ResolveCollisions();

        Vector2 playerPosition = player.GetHitbox().GetCenter();
        for (Enemy& enemy : enemies.GetAll())
        {
            enemy.TryShoot(enemyShots, playerPosition);
        }

        player.TryShoot(input, playerShots);
    }

    void Game::Draw() const
    {
        // 描画順はプレイヤー、プレイヤーの弾、敵、敵の弾の順
        player.Draw(images);
        playerShots.Draw(images);
        enemies.Draw(images);
        enemyShots.Draw(images);
        DrawFormatString(16, 16, GetColor(255, 255, 255), L"SCORE %02d", score);
    }

    void Game::ResolveCollisions()
    {
        for (PlayerShot& playerShot : playerShots.GetAll())
        {
            if (!playerShot.IsActive())
                continue;

            for (Enemy& enemy : enemies.GetAll())
            {
                if (!enemy.IsActive())
                    continue;

                if (!playerShot.GetHitbox().Overlaps(enemy.GetHitbox()))
                    continue;

                enemy.TakeDamage(tuning::playerShot::Damage);
                playerShot.Deactivate();

                if (!enemy.IsActive())
                    ++score;

                break;
            }
        }
    }
}
Source/Shooting/Game.h
Source/Shooting/Game.hcpp
#pragma once

#include "Shooting/ImageManager.h"
#include "Shooting/Input.h"
#include "Shooting/EnemyManager.h"
#include "Shooting/EnemyShotManager.h"
#include "Shooting/Player.h"
#include "Shooting/PlayerShotManager.h"

namespace dxgame::shooting
{
    // ゲームに必要なオブジェクトを所有し1フレームの処理を取りまとめる
    class Game
    {
    public:
        // 必須画像を読み込んでゲームループを開始する
        // 画像を読み込めない場合はループを開始せずfalseを返す
        bool Run();

    private:
        // プレイヤーを1フレーム分更新する
        // deltaTimeは移動計算で1フレーム分として使う秒数
        void Update(float deltaTime);

        // プレイヤーを描く
        void Draw() const;

        // プレイヤー弾と敵の命中を判定する
        void ResolveCollisions();

        ImageManager images;
        Input input;
        Player player;
        PlayerShotManager playerShots;
        EnemyManager enemies;
        EnemyShotManager enemyShots;
        int score = 0;
    };
}
Source/Shooting/ImageManager.cpp
Source/Shooting/ImageManager.cppcpp
#include <array>
#include <cstddef>

#include <DxLib.h>

#include "Shooting/ImageManager.h"

namespace dxgame::shooting
{
    namespace
    {
        using ImageId = ImageManager::Id;

        struct ImageEntry
        {
            ImageId id;
            const wchar_t* path;
        };

        constexpr std::array<ImageEntry, static_cast<std::size_t>(ImageId::Count)> ImageTable =
        {
            ImageEntry { ImageId::Player, L"Assets/Images/Player.png" },
            ImageEntry { ImageId::PlayerShot, L"Assets/Images/Bullet.png" },
            ImageEntry { ImageId::Enemy, L"Assets/Images/Enemy.png" },
            ImageEntry { ImageId::EnemyShot, L"Assets/Images/EnemyShot.png" }
        };

        std::size_t ToIndex(ImageId id)
        {
            return static_cast<std::size_t>(id);
        }
    }

    ImageManager::ImageManager()
    {
        handles.fill(-1);
    }

    ImageManager::~ImageManager()
    {
        for (int handle : handles)
        {
            if (handle != -1)
                DeleteGraph(handle);
        }
    }

    ImageManager::LoadResult ImageManager::LoadAll()
    {
        for (const ImageEntry& entry : ImageTable)
        {
            std::size_t index = ToIndex(entry.id);
            handles[index] = LoadGraph(entry.path);

            if (handles[index] == -1)
                return { false, entry.path };
        }

        return { true, {} };
    }

    void ImageManager::Draw(Id id, const Rect& rect) const
    {
        int handle = handles[ToIndex(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),
            handle,
            TRUE);
    }
}
Source/Shooting/ImageManager.h
Source/Shooting/ImageManager.hcpp
#pragma once

#include <array>
#include <cstddef>
#include <string>

#include "DxGame/Rect.h"

namespace dxgame::shooting
{
    // 画像の読み込み、ハンドルの所有、拡大縮小描画、解放を管理する
    class ImageManager
    {
    public:
        // ゲーム内で使う画像を種類ごとに識別する
        enum class Id
        {
            Player,
            PlayerShot,
            Enemy,
            EnemyShot,
            Count
        };

        // 全ハンドルを未取得の状態で初期化する
        ImageManager();

        // 取得済みの画像ハンドルをすべて解放する
        ~ImageManager();

        // ハンドルを二重に解放しないようコピー構築を禁止する
        ImageManager(const ImageManager&) = delete;

        // ハンドルを二重に解放しないようコピー代入を禁止する
        ImageManager& operator=(const ImageManager&) = delete;

        // 画像の読み込み結果と、失敗した場合の相対パスを表す
        struct LoadResult
        {
            bool succeeded = true;
            std::wstring failedPath;
        };

        // 対応表にある画像をすべて読み込む
        // 1つでも読み込めない場合は、その画像の相対パスとともに失敗を返す
        LoadResult LoadAll();

        // idの画像をrectの大きさへ拡大縮小して描く
        void Draw(Id id, const Rect& rect) const;

    private:
        std::array<int, static_cast<std::size_t>(Id::Count)> handles = {};
    };
}
Source/Shooting/Player.cpp
Source/Shooting/Player.cppcpp
#include <algorithm>
#include <cmath>

#include "Shooting/ImageManager.h"
#include "Shooting/Input.h"
#include "Shooting/PlayerShotManager.h"
#include "Shooting/Tuning/GameTuning.h"

#include "Shooting/Player.h"

namespace dxgame::shooting
{
    using Action = Input::Action;

    void Player::Reset()
    {
        position = tuning::player::StartPosition;
        shotTimer = 0.0f;
    }

    void Player::Update(const Input& input, float deltaTime)
    {
        Vector2 direction;

        if (input.IsDown(Action::MoveLeft))
            direction.x -= 1.0f;
        if (input.IsDown(Action::MoveRight))
            direction.x += 1.0f;
        if (input.IsDown(Action::MoveUp))
            direction.y -= 1.0f;
        if (input.IsDown(Action::MoveDown))
            direction.y += 1.0f;

        // 斜め入力を長さ1に収める
        // 方向と長さの計算は第05回で詳しく扱う
        float length = std::sqrt(direction.x * direction.x + direction.y * direction.y);

        if (length > 1.0f)
        {
            direction.x /= length;
            direction.y /= length;
        }

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

        // 指定の範囲全体が画面内に残るよう位置を制限する
        const Rect& bounds = tuning::player::Bounds;

        float minX = -bounds.x;
        float minY = -bounds.y;
        float maxX = tuning::ScreenWidth - bounds.x - bounds.width;
        float maxY = tuning::ScreenHeight - bounds.y - bounds.height;
        position.x = std::clamp(position.x, minX, maxX);
        position.y = std::clamp(position.y, minY, maxY);

        shotTimer = std::max(0.0f, shotTimer - deltaTime);
    }

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

    bool Player::TryShoot(const Input& input, PlayerShotManager& shots)
    {
        if (!input.IsDown(Action::Shoot) || shotTimer > 0.0f)
            return false;

        if (!shots.Spawn(GetMuzzlePosition()))
            return false;

        shotTimer = tuning::player::ShotInterval;
        return true;
    }

Vector2 Player::GetMuzzlePosition() const
{
    return
    {
        position.x,
        position.y - 32.0f
    };
}

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

    Rect Player::GetHitbox() const
    {
        return tuning::player::Hitbox.MovedBy(position);
    }
}
Source/Shooting/Player.h
Source/Shooting/Player.hcpp
#pragma once

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

namespace dxgame::shooting
{
    class Input;
    class ImageManager;
    class PlayerShotManager;

    // プレイヤーの中心座標、移動、発射間隔を管理する
    class Player
    {
    public:
        // プレイヤーを開始位置へ戻す
        void Reset();

        // 入力から移動方向を求めプレイヤーを画面内で移動させる
        // deltaTimeは移動計算で1フレーム分として使う秒数
        void Update(const Input& input, float deltaTime);

        // プレイヤー画像を表示用Rectに合わせて描く
        void Draw(const ImageManager& images) const;

        // 入力と発射間隔を確認し、弾を生成できたらtrueを返す
        bool TryShoot(const Input& input, PlayerShotManager& shots);

        // 表示用Rectを現在位置へ移動して返す
        Rect GetDrawRect() const;

        // 現在位置から求めた命中判定用Rectを返す
        Rect GetHitbox() const;

    private:
        // 発射する弾の中心座標を返す
        Vector2 GetMuzzlePosition() const;
        // プレイヤーの中心座標
        Vector2 position = {};
        float shotTimer = 0.0f;
    };
}
Source/Shooting/Tuning/GameTuning.h
Source/Shooting/Tuning/GameTuning.hcpp
#pragma once

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

#include "Config/ApplicationConfig.h"

namespace dxgame::shooting::tuning
{
    // ゲーム画面の横幅 単位はピクセル
    // ゲーム画面の横幅(単位:ピクセル)
    inline constexpr int ScreenWidth = 640;

    // ゲーム画面の縦幅 単位はピクセル
    // ゲーム画面の縦幅(単位:ピクセル)
    inline constexpr int ScreenHeight = 480;

    // 座標、距離、Rectの各値はピクセル単位で指定する
    // 時間は秒、速度は1秒あたりの移動量で指定する
    // プレイヤーのpositionは中心座標を表す
    // 各Rectはpositionからの相対範囲として指定する

    namespace player
    {
        // ゲーム開始時のプレイヤー中心座標
        inline constexpr Vector2 StartPosition = { 320.0f, 424.0f };

        // プレイヤー中心を基準にした画像の表示範囲
        inline constexpr Rect DrawRect = Rect::Centered(48.0f, 58.0f).MovedBy({ 0.0f, 3.0f });

        inline constexpr Rect Bounds = Rect::Centered(28.0f, 28.0f);

        // プレイヤー中心を基準にした命中範囲
        inline constexpr Rect Hitbox = Rect::Centered(28.0f, 28.0f);

        // プレイヤーの速度 単位はピクセル/秒
        // プレイヤーの速度(単位:ピクセル/秒)
        inline constexpr float MoveSpeed = 240.0f;

        // 弾を発射してから次に発射できるまでの時間 ここでは8フレーム間隔の意味
        // 弾を発射してから次に発射できるまでの時間(ここでは8フレーム間隔)
        inline constexpr float ShotInterval = 8.0f * config::DeltaTime;
    }

    namespace playerShot
    {
        // 弾の中心を基準にした画像の表示範囲
        inline constexpr Rect DrawRect = Rect::Centered(10.0f, 28.0f);

        // 弾の中心を基準にした命中範囲
        inline constexpr Rect Hitbox = Rect::Centered(8.0f, 20.0f);

        // 弾の速度 単位はピクセル/秒
        // 弾の速度(単位:ピクセル/秒)
        inline constexpr float Speed = 480.0f;

        // 敵へ与えるダメージ
        inline constexpr int Damage = 1;
    }

    namespace enemy
    {
        inline constexpr int LaneCount = 8;
        inline constexpr float LaneStart = 64.0f;
        inline constexpr float LaneSpacing = 72.0f;
        inline constexpr Rect DrawRect = Rect::Centered(42.0f, 42.0f);
        inline constexpr Rect Hitbox = Rect::Centered(32.0f, 32.0f);
        inline constexpr float BaseSpeed = 120.0f;
        inline constexpr float SpeedStep = 12.0f;
        inline constexpr int SpeedLevels = 4;
        inline constexpr float SpawnInterval = 0.60f;
        inline constexpr float SpawnDelay = 0.40f;
        inline constexpr float ShotDelay = 0.80f;
        inline constexpr float ShotInterval = 1.80f;
        inline constexpr float WaveAmplitude = 30.0f;
        inline constexpr float WaveSpeed = 3.0f;
        inline constexpr int WaveInterval = 3;
        inline constexpr int MaxHp = 1;
    }

    namespace enemyShot
    {
        inline constexpr Rect DrawRect = Rect::Centered(14.0f, 14.0f);
        inline constexpr Rect Hitbox = Rect::Centered(10.0f, 10.0f);
        inline constexpr float Speed = 180.0f;
    }
}