C++ 從基礎到精通 — 第 12 講
C++ 組織成遊戲結構
該部落格上撰寫的文章是出於教學目的而創建的。 另外,文章中的資訊可能不正確,請謹慎。
讓我們將迄今為止學到的語法與實際的遊戲結構聯繫起來。 決定哪些類別和資料結構具有哪些職責比記住文法更困難。
分擔責任
角色可以負責生命值和攻擊力,怪物可以負責怪物狀態。 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和TArraystd::unordered_map班級TMapstd::unique_ptr班級TUniquePtrstd::shared_ptr班級TSharedPtrstd::weak_ptr班級TWeakPtr
但是,std::shared_ptr 不應該僅僅因為名稱相似而用於 UObject。 常規 C++ 物件的生命週期由 RAII 和智慧指標管理,而 UObject 則由 Unreal 的 GC 管理。
AActor 並不像普通物件那樣直接刪除。Destroy(),終止過程為EndPlay()加工於 需要注意的是,一般F類型和UObject類型有不同的歸屬方式。
此外,如果 TArray 和 std::vector 都發生重新分配,則指向該元素的指標可能會變得無效。
最後,C++ 語法不是一個單獨的函數。 類別綁定職責,容器儲存狀態,智慧指標代表生命週期,演算法執行操作。
一旦你開始看到這個結構,你將能夠理解為什麼程式碼看起來像這樣,而不僅僅是記住語法。