← SEARCHABLE ARCHIVE
C / C++ · 2026-07-12

C++ 基礎からマスターまで — 第12回

ゲーム構造でまとめるC++

INTERACTIVE READING MODE →

本ブログ作成文は、講義を目指して製作されました。 また、作成された文章の情報も間違っている可能性がありますので、ご注意ください。

これまでに学んだ文法を実際のゲーム構造に結びつけよう。 文法を覚えるよりも、どの責任をどのクラスと資料構造に置くかを決めるのが難しい。

責任を分ける

Characterは体力と攻撃を担当し、Monsterはモンスターの状態を担当させることができる。 DungeonGameはパーティーやアクティブモンスター、スポーンキュー、アイテムデータベースを管理する。

class DungeonGame
{
public:
    void AddCharacter(std::unique_ptr<Character> character)
    {
        if (character)
        {
            party.push_back(std::move(character));
        }
    }

    void QueueMonster(std::shared_ptr<Monster> monster)
    {
        if (monster)
        {
            spawnQueue.push(std::move(monster));
        }
    }

private:
    std::vector<std::unique_ptr<Character>> party;
    std::vector<std::shared_ptr<Monster>> activeMonsters;
    std::queue<std::shared_ptr<Monster>> spawnQueue;
    std::stack<int> killHistory;
    std::unordered_map<int, Item> itemDatabase;
    std::weak_ptr<Monster> currentTarget;
};

パーティーキャラクターはDungeonGameが単独所有しているので uniqueptr を使用した。 モンスターはキューとアクティブリストの間で共有できるため、sharedptrを使用した。現在ターゲットは所有していないのでweak_ptrです。

タイプだけを見ても誰がオブジェクトを生かしているのか、ある程度分かる。

境界値処理

ゲームコードは通常の入力だけが入ると仮定するとすぐ壊れる。

bool AttackMonster(std::size_t characterIndex, int monsterId)
{
    if (characterIndex >= party.size())
    {
        return false;
    }

    auto iter = std::find_if(
        activeMonsters.begin(),
        activeMonsters.end(),
        [monsterId](const auto& monster)
        {
            return monster && monster->GetId() == monsterId;
        }
    );

    if (iter == activeMonsters.end())
    {
        return false;
    }

    (*iter)->TakeDamage(party[characterIndex]->Attack());
    return true;
}

無効なインデックス、存在しないID、nullptrの可能性を最初に調べて早期に返します。 空のキューでfrontを呼び出すか、空のスタックでtopを呼び出すことはできません。

体力はゼロより小さくならず、すでに死んだオブジェクトを再処理しないようにする必要があります。 安全装置は派手な機能ではないが、デバッグ時間を大幅に短縮する。

アンリアルと対応する概念

一般C++のデータ構造とアンリアルタイプは同様の役割を持つことが多い。

  • std::vectorとTArray
  • std::unordered_mapとTMap
  • std::unique_ptrとTUniquePtr
  • std::shared_ptrとTSharedPtr
  • std::weak_ptrとTWeakPtr

しかし、名前が似ているため、UObjectにもstd :: shared_ptrを使用しないでください。 一般的なC ++オブジェクトはRAIIとスマートポインタで寿命を管理しますが、UObjectはUnrealのGCによって管理されます。

AActorは通常のオブジェクトのように直接削除せずにDestroy()を呼び出し、終了プロセスはEndPlay()で処理する。 一般的なF型とUObject型は所有方法が異なることを区別する必要があります。

さらに、TArrayとstd :: vectorの両方が再割り当てが発生すると、要素を指していたポインタが無効になる可能性があります。

結局のところ、C++文法はそれぞれ離れた機能ではありません。 クラスが責任を結び、コンテナが状態を保存し、スマートポインタが寿命を表し、アルゴリズムが動作を実行します。

この構造が目に入り始めると、文法を覚えるよりもコードがなぜこんな感じになるのかを理解できるようになる。