Skip to content

第07回 クラス分割入門

前回: 第06回 敵と衝突判定 / 次回: 第08回 画像とリソース管理

今回の授業内容

  • Gameに集まった処理を役割ごとに分ける
  • PlayerBulletEnemyクラスを追加する
  • データと処理を同じ型にまとめる
  • publicprivateで触れる範囲を整理する
  • 分割前後で動作が変わらないことを確認する

今回の開始状態

第06回終了時点では、Game内にプレイヤー、弾、敵のデータと処理があります。
今回の終了時点では、それらを別ファイルのクラスへ移します。

新しい機能を増やす回ではありません。
動作を保ったまま、コードの置き場所を整理する回です。

変更するファイル

ファイル変更内容
Player.h / Player.cppプレイヤーの位置、移動、描画、HPを移す
Bullet.h / Bullet.cpp弾の発射、更新、描画、無効化を移す
Enemy.h / Enemy.cpp敵の出現、更新、描画、無効化を移す
Game.h / Game.cpp各クラスを呼び出す側へ整理する

まずBulletを分ける

最初に、影響範囲の小さいBulletから分けます。
Bullet.hを作り、弾が持つ値と操作をまとめます。

cpp
class Bullet
{
public:
    void Spawn(float startX, float startY);
    void Update();
    void Draw() const;

    bool IsActive() const;
    void Deactivate();
    Rect GetRect() const;

private:
    Vec2 position = {};
    float speed = 8.0f;
    bool active = false;
};

Game側は、bullet.activeを直接触るのではなく、関数を通して操作します。

cpp
if (!bullet.IsActive())
{
    bullet.Spawn(playerPosition.x, playerPosition.y - 22.0f);
    return;
}

PlayerとEnemyを分ける

Bulletを分けた後で、Playerを移します。
プレイヤーは入力を使うため、Update()Inputを参照として受け取ります。

cpp
void Player::Update(const Input& input)
{
    Vec2 move = {};

    if (input.IsDown(KEY_INPUT_LEFT) || input.IsDown(KEY_INPUT_A))
        move.x -= 1.0f;

    position.x += move.x * speed;
}

最後にEnemyを分けます。
敵は、出現位置と速度をSpawn()で受け取る形にします。

cpp
void Enemy::Spawn(float startX, float startY, float moveSpeed)
{
    position = { startX, startY };
    speed = moveSpeed;
    active = true;
}

Gameの役割を整理する

分割後のGameは、細かい移動や描画ではなく、全体の呼び出し順を管理します。

cpp
player.Update(input);

for (Bullet& bullet : bullets)
    bullet.Update();

for (Enemy& enemy : enemies)
    enemy.Update();

CheckCollisions();

衝突判定では、それぞれのクラスからRectを受け取ります。

cpp
if (IsOverlapping(enemy.GetRect(), bullet.GetRect()))
{
    enemy.Deactivate();
    bullet.Deactivate();
    ++score;
}

動作確認

  1. Bulletだけを分けた時点でビルドして、弾が出ることを確認する
  2. Playerを分けた時点でビルドして、移動とHP表示を確認する
  3. Enemyを分けた時点でビルドして、敵生成と衝突を確認する
  4. 分割前と同じ操作で同じ結果になることを確認する

よくある問題

  • .cppを追加した後は、Visual Studioプロジェクトへ登録されているか確認する
  • privateにした変数へGameから直接アクセスしている箇所は、関数経由に直す
  • 循環インクルードが起きた場合は、必要なヘッダーだけをインクルードしているか確認する

今回の完成条件

  • PlayerBulletEnemyを別ファイルへ分けられる
  • publicprivateの役割を説明できる
  • Gameが全体の呼び出し順を管理する形に整理できる
  • 分割前後でゲームの動作を維持できる

まとめ

今回は、増えてきた処理をクラスへ分けました。
次回は、図形描画を画像描画へ置き換えるため、画像管理を追加します。