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

从 C++ 基础到掌握 — 第 12 集

C++ 组织成游戏结构

INTERACTIVE READING MODE →

该博客上撰写的文章是出于教学目的而创建的。 另外,文章中的信息可能不正确,请谨慎。

让我们将迄今为止学到的语法与实际的游戏结构联系起来。 决定哪些类和数据结构具有哪些职责比记住语法更困难。

分担责任

角色可以负责生命值和攻击力,怪物可以负责怪物状态。 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。

生命值不应低于 0,并且已经死亡的对象不应再次处理。 保护措施并不是一个华而不实的功能,但它们可以显着减少调试时间。

与虚幻相对应的概念

一般 C++ 和 Unreal 类型中的数据结构通常具有相似的作用。

  • std::vector和TArray
  • std::unordered_map班级TMap
  • std::unique_ptr班级TUniquePtr
  • std::shared_ptr班级TSharedPtr
  • std::weak_ptr班级TWeakPtr

但是,std::shared_ptr 不应该仅仅因为名称相似而用于 UObject。 常规 C++ 对象的生命周期由 RAII 和智能指针管理,而 UObject 则由 Unreal 的 GC 管理。

AActor 并不像普通对象那样直接删除。Destroy(),终止过程为EndPlay()加工于 需要注意的是,一般F类型和UObject类型有不同的归属方式。

此外,如果 TArray 和 std::vector 都发生重新分配,则指向该元素的指针可能会变得无效。

最后,C++ 语法不是一个单独的函数。 类捆绑职责,容器存储状态,智能指针代表生命周期,算法执行操作。

一旦你开始看到这个结构,你将能够理解为什么代码看起来像这样,而不仅仅是记住语法。