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

C++ von den Grundlagen bis zum Profi — Folge 12

C++ anhand einer Spielarchitektur zusammenführen

INTERACTIVE READING MODE →

Die in diesem Blog verfassten Artikel wurden mit dem Ziel erstellt, zu unterrichten. Außerdem können die Informationen im Artikel falsch sein, also seien Sie bitte vorsichtig.

Lassen Sie uns die Grammatik, die wir bisher gelernt haben, mit der tatsächlichen Spielstruktur verbinden. Die Entscheidung, welche Klassen und Datenstrukturen welche Verantwortlichkeiten haben, ist schwieriger als das Auswendiglernen der Grammatik.

Verantwortung teilen

Charaktere können für Gesundheit und Angriff verantwortlich sein, und Monster können für den Monsterstatus verantwortlich sein. DungeonGame verwaltet Ihre Gruppe, aktive Monster, Spawn-Warteschlange und Gegenstandsdatenbank.

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

Gruppencharaktere sind ausschließlich Eigentum von DungeonGame, daher wurde uniqueptr verwendet. Monster können zwischen der Warteschlange und der aktiven Liste geteilt werden, daher haben wir sharedptr verwendet. Das aktuelle Ziel ist schwach_ptr, da es nicht im Besitz ist.

Allein durch die Betrachtung des Typs können Sie einigermaßen erkennen, wer das Objekt am Leben erhält.

Grenzwertverarbeitung

Wenn man davon ausgeht, dass nur normale Eingaben eingehen, bricht der Spielcode schnell zusammen.

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

Es prüft zunächst, ob ungültige Indizes, nicht vorhandene IDs und Nullptr-Möglichkeiten vorhanden sind, und kehrt frühzeitig zurück. Sie sollten weder „front“ in einer leeren Warteschlange noch „top“ in einem leeren Stapel aufrufen.

Der Gesundheitszustand sollte nicht unter 0 fallen und bereits tote Objekte sollten nicht erneut verarbeitet werden. Schutzmaßnahmen sind keine auffällige Funktion, aber sie verkürzen die Debugging-Zeit erheblich.

Konzepte, die Unreal entsprechen

Datenstrukturen in allgemeinen C++- und Unreal-Typen haben oft ähnliche Rollen.

  • std::vectorUndTArray
  • std::unordered_mapKlasseTMap
  • std::unique_ptrKlasseTUniquePtr
  • std::shared_ptrKlasseTSharedPtr
  • std::weak_ptrKlasseTWeakPtr

Allerdings sollte std::shared_ptr nicht für UObject verwendet werden, nur weil der Name ähnlich ist. Während die Lebensdauer regulärer C++-Objekte von RAII und Smart Pointern verwaltet wird, wird die Lebensdauer von UObjects vom GC von Unreal verwaltet.

AActor wird nicht wie ein normales Objekt direkt gelöscht.Destroy(), und der Beendigungsprozess istEndPlay()Verarbeitet in Es ist zu beachten, dass der allgemeine F-Typ und der UObject-Typ unterschiedliche Besitzmethoden haben.

Wenn außerdem eine Neuzuweisung von TArray und std::vector erfolgt, kann der Zeiger, der auf das Element zeigt, ungültig werden.

Letztendlich ist die C++-Syntax keine separate Funktion. Klassen binden Verantwortlichkeiten, Container speichern den Zustand, intelligente Zeiger stellen Lebensdauern dar und Algorithmen führen Operationen aus.

Sobald Sie diese Struktur erkennen, werden Sie verstehen, warum der Code so aussieht, anstatt sich nur die Grammatik zu merken.