【保存版】C++初心者でも作れる!簡単2Dゲーム制作の完全ガイド

C++でゲーム制作を始める前に知っておきたいこと

プログラミング未経験でもC++が最適な理由

C++は確かに難しい言語だと言われていますが、ゲーム開発において非常に強力なツールとなります。その理由をご説明しましょう:

  1. 優れたパフォーマンス
  • メモリ管理を直接制御できるため、高速な処理が可能
  • ハードウェアに近いレベルでの最適化が可能
  • 大規模なゲームエンジンの多くがC++で開発されている
  1. 豊富な学習リソース
  • ゲーム開発に特化した書籍やオンライン教材が充実
  • コミュニティが活発で、技術的なサポートを得やすい
  • サンプルコードや実装例が多数存在
  1. 将来性の高さ
  • 業界標準として広く使用されている
  • 習得した知識が他の言語の学習にも活かせる
  • 商用ゲーム開発でも活用できるスキルが身につく

必要な開発環境とツールの準備方法

効率的なゲーム開発のために、以下の環境を整えましょう:

  1. コンパイラとIDE
  • Visual Studio Community(Windows推奨)
    • 無料で使用可能
    • デバッグ機能が充実
    • インストール手順:
    1. Visual Studio のダウンロードページにアクセス
    2. インストーラーをダウンロード
    3. 「C++によるデスクトップ開発」を選択してインストール
  • Code::Blocks(クロスプラットフォーム)
    • 軽量で扱いやすい
    • GCCコンパイラが付属
  1. 必要なライブラリ
  • SDL2(Simple DirectMedia Layer)
    • 2Dグラフィックス、サウンド、入力処理に最適
    • インストール方法:
// Windows + Visual Studioの場合
// 1. SDLのダウンロード
// 2. プロジェクトの設定
#include <SDL.h>
#include <SDL_image.h>

// 初期化例
SDL_Init(SDL_INIT_EVERYTHING);
  • SFML(Simple and Fast Multimedia Library)
    • より現代的なC++風の設計
    • 使用例:
#include <SFML/Graphics.hpp>

int main() {
    // ウィンドウの作成
    sf::RenderWindow window(sf::VideoMode(800, 600), "My Game");

    while (window.isOpen()) {
        sf::Event event;
        while (window.pollEvent(event)) {
            if (event.type == sf::Event::Closed)
                window.close();
        }
    }
    return 0;
}

ゲーム開発に必要なC++の基礎知識

ゲーム開発を始めるために必要な最低限のC++知識をまとめました:

  1. 基本的な文法
// 変数の宣言と初期化
int playerScore = 0;
float playerSpeed = 5.0f;

// 配列の使用
int enemyPositions[10];

// 条件分岐
if (playerScore > 100) {
    // レベルアップ処理
}

// ループ処理
for (int i = 0; i < 10; i++) {
    // 敵キャラクターの更新処理
}
  1. クラスとオブジェクト指向
class GameObject {
private:
    float x, y;  // 位置
    float speed; // 速度

public:
    GameObject(float startX, float startY) : x(startX), y(startY), speed(0) {}

    void move(float dx, float dy) {
        x += dx * speed;
        y += dy * speed;
    }

    void setSpeed(float newSpeed) {
        speed = newSpeed;
    }
};

// 使用例
GameObject player(100, 100);
player.setSpeed(5.0f);
player.move(1.0f, 0.0f); // 右に移動
  1. ポインタとメモリ管理
// 動的メモリ割り当て
GameObject* player = new GameObject(100, 100);

// メモリの解放を忘れずに
delete player;

// スマートポインタの使用(推奨)
#include <memory>
std::unique_ptr<GameObject> player = std::make_unique<GameObject>(100, 100);
// 自動的にメモリ解放される
  1. イベント処理の基本
void handleInput() {
    SDL_Event event;
    while (SDL_PollEvent(&event)) {
        switch (event.type) {
            case SDL_QUIT:
                // ゲーム終了処理
                break;
            case SDL_KEYDOWN:
                // キー入力処理
                if (event.key.keysym.sym == SDLK_SPACE) {
                    // スペースキーが押された時の処理
                }
                break;
        }
    }
}

これらの基礎を押さえておくことで、次のステップである実際のゲーム開発へスムーズに移行できます。初めは完璧に理解する必要はありません。実践的なコーディングを通じて徐々に理解を深めていきましょう。

2Dゲーム制作の基礎を理解しよう

ゲームループの仕組みと実装方法

ゲームループは、ゲームプログラムの中核となる部分です。主に以下の3つの処理を繰り返し行います:

  1. 入力処理(Input)
  2. 更新処理(Update)
  3. 描画処理(Render)

基本的なゲームループの実装例:

#include <SDL.h>
#include <chrono>

class Game {
private:
    SDL_Window* window;
    SDL_Renderer* renderer;
    bool running;

    // フレームレート制御用
    const int FPS = 60;
    const int FRAME_DELAY = 1000 / FPS;

public:
    Game() : window(nullptr), renderer(nullptr), running(true) {
        // SDLの初期化
        SDL_Init(SDL_INIT_EVERYTHING);

        window = SDL_CreateWindow("My Game", 
            SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
            800, 600, SDL_WINDOW_SHOWN);

        renderer = SDL_CreateRenderer(window, -1, 
            SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
    }

    void gameLoop() {
        while (running) {
            auto frameStart = SDL_GetTicks();

            handleInput();    // 入力処理
            update();        // 更新処理
            render();        // 描画処理

            // フレームレート制御
            int frameTime = SDL_GetTicks() - frameStart;
            if (FRAME_DELAY > frameTime) {
                SDL_Delay(FRAME_DELAY - frameTime);
            }
        }
    }

    void cleanup() {
        SDL_DestroyRenderer(renderer);
        SDL_DestroyWindow(window);
        SDL_Quit();
    }

private:
    void handleInput() {
        SDL_Event event;
        while (SDL_PollEvent(&event)) {
            if (event.type == SDL_QUIT) {
                running = false;
            }
        }
    }

    void update() {
        // ゲームの状態更新処理
    }

    void render() {
        SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
        SDL_RenderClear(renderer);

        // ゲームオブジェクトの描画処理

        SDL_RenderPresent(renderer);
    }
};

画面描画の基本テクニック

2Dゲームの描画には、主に以下の要素があります:

  1. スプライトの描画
class Sprite {
private:
    SDL_Texture* texture;
    SDL_Rect srcRect;  // 元画像の範囲
    SDL_Rect destRect; // 描画先の範囲

public:
    Sprite(SDL_Renderer* renderer, const char* path) {
        // 画像の読み込み
        SDL_Surface* tempSurface = IMG_Load(path);
        texture = SDL_CreateTextureFromSurface(renderer, tempSurface);
        SDL_FreeSurface(tempSurface);

        srcRect = {0, 0, 32, 32};  // スプライトのサイズ
        destRect = {100, 100, 32, 32}; // 描画位置とサイズ
    }

    void render(SDL_Renderer* renderer) {
        SDL_RenderCopy(renderer, texture, &srcRect, &destRect);
    }

    void setPosition(int x, int y) {
        destRect.x = x;
        destRect.y = y;
    }
};
  1. アニメーション処理
class Animation {
private:
    int currentFrame;
    int frameCount;
    int frameDelay;
    int frameTimer;
    SDL_Rect srcRect;

public:
    Animation(int frames, int delay) 
        : currentFrame(0), frameCount(frames), 
          frameDelay(delay), frameTimer(0) {
        srcRect = {0, 0, 32, 32};
    }

    void update() {
        frameTimer++;
        if (frameTimer >= frameDelay) {
            currentFrame = (currentFrame + 1) % frameCount;
            srcRect.x = currentFrame * 32; // フレーム位置の更新
            frameTimer = 0;
        }
    }

    SDL_Rect* getCurrentFrame() {
        return &srcRect;
    }
};

キー入力の処理と実装例

効率的なキー入力処理の実装方法:

  1. キーボード入力の処理
class InputHandler {
private:
    const Uint8* keyState;

public:
    InputHandler() {
        keyState = SDL_GetKeyboardState(nullptr);
    }

    bool isKeyPressed(SDL_Scancode key) {
        return keyState[key];
    }

    void update() {
        // キー状態の更新
        SDL_PumpEvents();
    }
};

// 使用例
void Player::handleInput(InputHandler& input) {
    if (input.isKeyPressed(SDL_SCANCODE_RIGHT)) {
        x += speed;  // 右移動
    }
    if (input.isKeyPressed(SDL_SCANCODE_LEFT)) {
        x -= speed;  // 左移動
    }
    if (input.isKeyPressed(SDL_SCANCODE_SPACE)) {
        jump();     // ジャンプ
    }
}
  1. マウス入力の処理
class MouseHandler {
private:
    int mouseX, mouseY;
    bool leftButton, rightButton;

public:
    void update() {
        Uint32 buttons = SDL_GetMouseState(&mouseX, &mouseY);
        leftButton = buttons & SDL_BUTTON(SDL_BUTTON_LEFT);
        rightButton = buttons & SDL_BUTTON(SDL_BUTTON_RIGHT);
    }

    bool isLeftButtonPressed() const { return leftButton; }
    bool isRightButtonPressed() const { return rightButton; }
    void getMousePosition(int& x, int& y) const {
        x = mouseX;
        y = mouseY;
    }
};

これらの基本要素を組み合わせることで、シンプルな2Dゲームの土台を作ることができます。次のセクションでは、これらの要素を使って実際にゲームを作っていく方法を説明します。

実践!簡単な2Dゲームを作ってみよう

シンプルな短期間で完成するゲーム企画のコツ

初めてのゲーム制作では、以下の点に注意して企画を立てましょう:

  1. ゲームの基本要素を絞り込む
  • 核となる1つのゲームメカニクス
  • 明確なゲームの目標
  • シンプルな操作方法
  • 最小限の画面遷移
  1. 開発スコープの設定
  • 制作期間:2週間程度
  • 機能:3-4個の主要機能に限定
  • アセット:必要最小限の画像と音声
  • 目標:動くプロトタイプの完成

例として、単純な横スクロールアクションゲームを作ってみましょう。

プレイヤーキャラクターの移動制御を実装する

まず、プレイヤーキャラクターの基本クラスを実装します:

class Player {
private:
    float x, y;          // 位置
    float velocityX, velocityY; // 速度
    bool isJumping;      // ジャンプ状態
    SDL_Texture* texture; // プレイヤーのテクスチャ
    SDL_Rect srcRect;     // スプライトの範囲
    SDL_Rect destRect;    // 描画位置と大きさ

    const float MOVE_SPEED = 5.0f;
    const float JUMP_FORCE = -15.0f;
    const float GRAVITY = 0.8f;

public:
    Player(SDL_Renderer* renderer, const char* spritePath) {
        x = 100.0f;
        y = 500.0f;
        velocityX = 0.0f;
        velocityY = 0.0f;
        isJumping = false;

        // テクスチャの読み込み
        SDL_Surface* tempSurface = IMG_Load(spritePath);
        texture = SDL_CreateTextureFromSurface(renderer, tempSurface);
        SDL_FreeSurface(tempSurface);

        srcRect = {0, 0, 32, 32};
        destRect = {(int)x, (int)y, 32, 32};
    }

    void handleInput(const Uint8* keyState) {
        // 左右移動
        if (keyState[SDL_SCANCODE_LEFT]) {
            velocityX = -MOVE_SPEED;
        }
        else if (keyState[SDL_SCANCODE_RIGHT]) {
            velocityX = MOVE_SPEED;
        }
        else {
            velocityX = 0.0f;
        }

        // ジャンプ
        if (keyState[SDL_SCANCODE_SPACE] && !isJumping) {
            velocityY = JUMP_FORCE;
            isJumping = true;
        }
    }

    void update() {
        // 重力の適用
        velocityY += GRAVITY;

        // 位置の更新
        x += velocityX;
        y += velocityY;

        // 地面との衝突判定
        if (y >= 500.0f) {
            y = 500.0f;
            velocityY = 0.0f;
            isJumping = false;
        }

        // 描画位置の更新
        destRect.x = (int)x;
        destRect.y = (int)y;
    }

    void render(SDL_Renderer* renderer) {
        SDL_RenderCopy(renderer, texture, &srcRect, &destRect);
    }

    SDL_Rect getCollider() const {
        return destRect;
    }
};

当たり判定システムの作り方

効率的な当たり判定システムを実装しましょう:

class CollisionManager {
public:
    // 矩形同士の衝突判定
    static bool checkCollision(const SDL_Rect& rectA, const SDL_Rect& rectB) {
        return (rectA.x < rectB.x + rectB.w &&
                rectA.x + rectA.w > rectB.x &&
                rectA.y < rectB.y + rectB.h &&
                rectA.y + rectA.h > rectB.y);
    }

    // 円同士の衝突判定
    static bool checkCircleCollision(float x1, float y1, float r1,
                                   float x2, float y2, float r2) {
        float dx = x2 - x1;
        float dy = y2 - y1;
        float distance = std::sqrt(dx * dx + dy * dy);
        return distance < (r1 + r2);
    }
};

// 障害物クラス
class Obstacle {
private:
    SDL_Rect rect;
    bool active;

public:
    Obstacle(int x, int y, int w, int h) : active(true) {
        rect = {x, y, w, h};
    }

    bool isActive() const { return active; }
    void deactivate() { active = false; }
    SDL_Rect getCollider() const { return rect; }

    void render(SDL_Renderer* renderer) {
        if (active) {
            SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255);
            SDL_RenderFillRect(renderer, &rect);
        }
    }
};

// ゲーム内での衝突判定の使用例
void Game::checkCollisions() {
    SDL_Rect playerCollider = player.getCollider();

    for (auto& obstacle : obstacles) {
        if (obstacle.isActive() && 
            CollisionManager::checkCollision(playerCollider, obstacle.getCollider())) {
            // 衝突時の処理
            handleCollision(player, obstacle);
        }
    }
}

void Game::handleCollision(Player& player, Obstacle& obstacle) {
    // ゲームオーバー処理や点数減算など
    gameOver = true;
}

実装のポイント:

  1. 効率的な衝突判定
  • 単純な矩形判定から始める
  • 必要に応じて詳細な判定を追加
  • 空間分割などの最適化は後から実装
  1. デバッグ支援
// デバッグ表示用の関数
void renderDebugColliders(SDL_Renderer* renderer, const std::vector<Obstacle>& obstacles) {
    SDL_SetRenderDrawColor(renderer, 0, 255, 0, 255);
    for (const auto& obstacle : obstacles) {
        if (obstacle.isActive()) {
            SDL_Rect collider = obstacle.getCollider();
            SDL_RenderDrawRect(renderer, &collider);
        }
    }
}
  1. パフォーマンス考慮
  • 不要な衝突チェックを避ける
  • オブジェクトの状態管理を適切に行う
  • メモリ効率の良いデータ構造を使用

これらのコードを組み合わせることで、基本的な2Dアクションゲームの土台が完成します。次のセクションでは、このゲームをより面白くするための機能追加について説明します。

ゲームを面白くする実装テクニック

効果音と音楽の追加方法

サウンドは、ゲームの臨場感を高める重要な要素です。SDL_Mixerを使用して実装していきましょう:

class AudioManager {
private:
    std::unordered_map<std::string, Mix_Chunk*> soundEffects;
    std::unordered_map<std::string, Mix_Music*> music;

public:
    AudioManager() {
        // SDL_Mixerの初期化
        Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 2048);
    }

    ~AudioManager() {
        // リソースの解放
        for (auto& sound : soundEffects) {
            Mix_FreeChunk(sound.second);
        }
        for (auto& bgm : music) {
            Mix_FreeMusic(bgm.second);
        }
        Mix_CloseAudio();
    }

    void loadSound(const std::string& name, const char* path) {
        Mix_Chunk* sound = Mix_LoadWAV(path);
        if (sound) {
            soundEffects[name] = sound;
        }
    }

    void loadMusic(const std::string& name, const char* path) {
        Mix_Music* bgm = Mix_LoadMUS(path);
        if (bgm) {
            music[name] = bgm;
        }
    }

    void playSound(const std::string& name, int volume = 128) {
        if (soundEffects.count(name) > 0) {
            Mix_VolumeChunk(soundEffects[name], volume);
            Mix_PlayChannel(-1, soundEffects[name], 0);
        }
    }

    void playMusic(const std::string& name, int loops = -1) {
        if (music.count(name) > 0) {
            Mix_PlayMusic(music[name], loops);
        }
    }

    void stopMusic() {
        Mix_HaltMusic();
    }
};

// 使用例
AudioManager audio;
audio.loadSound("jump", "assets/sounds/jump.wav");
audio.loadSound("coin", "assets/sounds/coin.wav");
audio.loadMusic("bgm", "assets/music/background.mp3");

// ゲーム内での使用
audio.playMusic("bgm");  // BGM再生
audio.playSound("jump"); // ジャンプ音再生

スコアシステムの実装手順

スコアシステムは、ゲームの進行状況を管理する重要な要素です:

class ScoreManager {
private:
    int currentScore;
    int highScore;
    std::vector<ScoreMultiplier> multipliers;

    struct ScoreMultiplier {
        float multiplier;
        int remainingTime;
    };

public:
    ScoreManager() : currentScore(0), highScore(0) {}

    void addPoints(int points) {
        float totalMultiplier = calculateMultiplier();
        int finalPoints = static_cast<int>(points * totalMultiplier);
        currentScore += finalPoints;

        if (currentScore > highScore) {
            highScore = currentScore;
            saveHighScore();
        }
    }

    void addMultiplier(float multiplier, int duration) {
        multipliers.push_back({multiplier, duration});
    }

    void update() {
        // マルチプライヤーの時間更新
        for (auto it = multipliers.begin(); it != multipliers.end();) {
            it->remainingTime--;
            if (it->remainingTime <= 0) {
                it = multipliers.erase(it);
            } else {
                ++it;
            }
        }
    }

    void saveHighScore() {
        std::ofstream file("highscore.dat");
        if (file.is_open()) {
            file << highScore;
            file.close();
        }
    }

    void loadHighScore() {
        std::ifstream file("highscore.dat");
        if (file.is_open()) {
            file >> highScore;
            file.close();
        }
    }

private:
    float calculateMultiplier() {
        float total = 1.0f;
        for (const auto& mult : multipliers) {
            total *= mult.multiplier;
        }
        return total;
    }
};

ゲームの状態管理と画面遷移の実装

ゲームの状態管理には、State Patternを使用します:

// ゲーム状態の基底クラス
class GameState {
public:
    virtual ~GameState() {}
    virtual void enter() = 0;
    virtual void exit() = 0;
    virtual void handleInput(SDL_Event& event) = 0;
    virtual void update() = 0;
    virtual void render(SDL_Renderer* renderer) = 0;
};

// メニュー状態
class MenuState : public GameState {
private:
    std::vector<Button> buttons;

public:
    void enter() override {
        // メニュー画面の初期化
        buttons.push_back(Button("Start Game", 300, 200));
        buttons.push_back(Button("Options", 300, 300));
        buttons.push_back(Button("Exit", 300, 400));
    }

    void handleInput(SDL_Event& event) override {
        if (event.type == SDL_MOUSEBUTTONDOWN) {
            int mouseX, mouseY;
            SDL_GetMouseState(&mouseX, &mouseY);

            for (auto& button : buttons) {
                if (button.isClicked(mouseX, mouseY)) {
                    button.onClick();
                }
            }
        }
    }

    // その他のオーバーライドメソッド...
};

// ゲームプレイ状態
class PlayState : public GameState {
private:
    Player player;
    std::vector<Enemy> enemies;
    ScoreManager scoreManager;

public:
    void enter() override {
        // ゲーム開始時の初期化
        player.reset();
        enemies.clear();
        scoreManager.resetScore();
    }

    void update() override {
        player.update();

        for (auto& enemy : enemies) {
            enemy.update();
        }

        checkCollisions();
        scoreManager.update();
    }

    // その他のオーバーライドメソッド...
};

// 状態管理クラス
class GameStateManager {
private:
    std::stack<std::unique_ptr<GameState>> states;

public:
    void pushState(std::unique_ptr<GameState> state) {
        if (!states.empty()) {
            states.top()->exit();
        }
        states.push(std::move(state));
        states.top()->enter();
    }

    void popState() {
        if (!states.empty()) {
            states.top()->exit();
            states.pop();
            if (!states.empty()) {
                states.top()->enter();
            }
        }
    }

    void handleInput(SDL_Event& event) {
        if (!states.empty()) {
            states.top()->handleInput(event);
        }
    }

    void update() {
        if (!states.empty()) {
            states.top()->update();
        }
    }

    void render(SDL_Renderer* renderer) {
        if (!states.empty()) {
            states.top()->render(renderer);
        }
    }
};

// 使用例
class Game {
private:
    GameStateManager stateManager;

public:
    void init() {
        stateManager.pushState(std::make_unique<MenuState>());
    }

    void run() {
        SDL_Event event;
        while (running) {
            while (SDL_PollEvent(&event)) {
                stateManager.handleInput(event);
            }

            stateManager.update();
            stateManager.render(renderer);
        }
    }
};

これらの機能を組み合わせることで、ゲームにより深い没入感と楽しさを追加できます。次のセクションでは、これらの実装を最適化する方法について説明します。

完成したゲームの改良とパフォーマンス最適化

メモリリークを防ぐデバッグ方法

メモリリークは、特にゲーム開発において重要な問題です。以下の方法で効果的に対処できます:

  1. スマートポインタの活用
class ResourceManager {
private:
    // 生ポインタの代わりにスマートポインタを使用
    std::unordered_map<std::string, std::shared_ptr<SDL_Texture>> textures;
    std::unordered_map<std::string, std::unique_ptr<Sound>> sounds;

public:
    void loadTexture(const std::string& name, SDL_Renderer* renderer, const char* path) {
        SDL_Surface* surface = IMG_Load(path);
        if (surface) {
            SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface);
            SDL_FreeSurface(surface);
            if (texture) {
                textures[name] = std::shared_ptr<SDL_Texture>(texture, SDL_DestroyTexture);
            }
        }
    }

    std::shared_ptr<SDL_Texture> getTexture(const std::string& name) {
        if (textures.count(name) > 0) {
            return textures[name];
        }
        return nullptr;
    }
};
  1. メモリ使用量の監視
class MemoryTracker {
private:
    size_t allocatedMemory;
    std::unordered_map<void*, size_t> allocations;

public:
    static MemoryTracker& getInstance() {
        static MemoryTracker instance;
        return instance;
    }

    void* trackAllocation(size_t size) {
        void* ptr = std::malloc(size);
        allocatedMemory += size;
        allocations[ptr] = size;
        return ptr;
    }

    void trackDeallocation(void* ptr) {
        if (allocations.count(ptr) > 0) {
            allocatedMemory -= allocations[ptr];
            allocations.erase(ptr);
            std::free(ptr);
        }
    }

    size_t getCurrentMemoryUsage() const {
        return allocatedMemory;
    }

    void printMemoryReport() {
        std::cout << "Current memory usage: " << allocatedMemory << " bytes\n";
        std::cout << "Number of active allocations: " << allocations.size() << "\n";
    }
};

// カスタムアロケータの実装例
template<typename T>
class TrackedAllocator {
public:
    T* allocate(size_t n) {
        return static_cast<T*>(MemoryTracker::getInstance().trackAllocation(n * sizeof(T)));
    }

    void deallocate(T* p, size_t n) {
        MemoryTracker::getInstance().trackDeallocation(p);
    }
};

フレームレート安定化のためのコード改善

パフォーマンスを最適化し、安定したフレームレートを維持するためのテクニック:

  1. オブジェクトプーリング
template<typename T>
class ObjectPool {
private:
    std::vector<std::unique_ptr<T>> objects;
    std::queue<T*> available;
    size_t maxSize;

public:
    ObjectPool(size_t size) : maxSize(size) {
        for (size_t i = 0; i < size; ++i) {
            auto obj = std::make_unique<T>();
            available.push(obj.get());
            objects.push_back(std::move(obj));
        }
    }

    T* acquire() {
        if (available.empty()) {
            return nullptr;
        }
        T* obj = available.front();
        available.pop();
        return obj;
    }

    void release(T* obj) {
        available.push(obj);
    }
};

// パーティクルシステムでの使用例
class ParticleSystem {
private:
    ObjectPool<Particle> particlePool;
    std::vector<Particle*> activeParticles;

public:
    ParticleSystem(size_t maxParticles) : particlePool(maxParticles) {}

    void emitParticle(float x, float y) {
        if (Particle* p = particlePool.acquire()) {
            p->init(x, y);
            activeParticles.push_back(p);
        }
    }

    void update() {
        for (auto it = activeParticles.begin(); it != activeParticles.end();) {
            if ((*it)->isExpired()) {
                particlePool.release(*it);
                it = activeParticles.erase(it);
            } else {
                (*it)->update();
                ++it;
            }
        }
    }
};
  1. 空間分割による衝突検出の最適化
class QuadTree {
private:
    struct Node {
        SDL_Rect bounds;
        std::vector<GameObject*> objects;
        std::unique_ptr<Node> children[4];
        bool isLeaf;
    };

    std::unique_ptr<Node> root;
    const int MAX_OBJECTS = 10;
    const int MAX_LEVELS = 5;

public:
    QuadTree(const SDL_Rect& bounds) {
        root = std::make_unique<Node>();
        root->bounds = bounds;
        root->isLeaf = true;
    }

    void insert(GameObject* obj) {
        insertObject(root.get(), obj, 0);
    }

    std::vector<GameObject*> query(const SDL_Rect& area) {
        std::vector<GameObject*> result;
        queryNode(root.get(), area, result);
        return result;
    }

private:
    void insertObject(Node* node, GameObject* obj, int level) {
        if (!node->isLeaf && level < MAX_LEVELS) {
            // 適切な子ノードに挿入
            int index = getQuadrant(node->bounds, obj->getBounds());
            if (index != -1) {
                insertObject(node->children[index].get(), obj, level + 1);
                return;
            }
        }

        node->objects.push_back(obj);

        if (node->objects.size() > MAX_OBJECTS && level < MAX_LEVELS) {
            split(node);
        }
    }

    void split(Node* node) {
        // ノードを4分割
        int subWidth = node->bounds.w / 2;
        int subHeight = node->bounds.h / 2;

        for (int i = 0; i < 4; ++i) {
            SDL_Rect childBounds = {
                node->bounds.x + (i % 2) * subWidth,
                node->bounds.y + (i / 2) * subHeight,
                subWidth,
                subHeight
            };
            node->children[i] = std::make_unique<Node>();
            node->children[i]->bounds = childBounds;
            node->children[i]->isLeaf = true;
        }

        node->isLeaf = false;
    }
};

ゲーム制作の次のステップと学習リソース

ゲーム開発のスキルをさらに向上させるためのステップ:

  1. 設計パターンの学習
  • Observer パターン(イベント処理)
  • Command パターン(入力処理)
  • Factory パターン(オブジェクト生成)
  • Component パターン(ゲームオブジェクト構造)
  1. パフォーマンス最適化の深掘り
  • プロファイリングツールの使用
  • データ構造の最適化
  • マルチスレッド処理の導入
  1. 推奨学習リソース
  • 書籍
    • “Game Programming Patterns” by Robert Nystrom
    • “Effective Modern C++” by Scott Meyers
  • オンラインリソース
    • SDL Wiki (https://wiki.libsdl.org/)
    • C++ Reference (https://en.cppreference.com/)
  • コミュニティ
    • GameDev.net
    • r/gamedev (Reddit)
  1. 次のステップ
  • 3Dグラフィックスの学習(OpenGL/DirectX)
  • 物理エンジンの実装
  • ネットワーク対戦の実装
  • アセット管理システムの改良

これらの最適化と改良を適用することで、より安定した高性能なゲームを作成できます。常に新しい技術とベストプラクティスを学び続けることが、ゲーム開発者としての成長につながります。