MMO游戲對象屬性設計
Author: |
Kevin Lynx |
Date: |
5.2.2011 |
一般的MMORPG中,游戲對象主要包括怪物和玩家。這兩類對象在經過游戲性方面的不斷“進化”后,其屬性數量及與之相關的邏輯往往會變得很巨大。如何將這一塊做得既不損失效率,又能保證結構的靈活、清晰、可維護?本文將提供一種簡單的結構。
原始結構
最原始的結構,極有可能為這樣:
Player: +---------------+
| property-1 |
+---------------+
| property-2 |
+---------------+
| ... |
+---------------+
| operator-1 |
+---------------+
| operator-2 |
+---------------+
| ... |
+---------------+
也就是,一個對象為一個C++類,然后里面直接塞滿了各種屬性名,然后是針對這個屬性的邏輯操作(函數)。其結果就是Player成為巨類。針對這個情況,一直以來我覺得可以使用一種簡單的方法來拆分這個類。冠以官腔,稱之為Entity-Component-based Desgin。產生這種想法和我的個人技術積累有一定關系,見下文。
Policy-based Design
Policy-based Design,基于決策的設計。這個概念來源于<Modern C++ Design>。雖然這本書講述的是針對C++模板的使用及設計技巧。但這種思想依然被我潛意識般地用在其他地方。Policy大致來說就是一個小的組件(Component)。它努力不依賴于其他東西,它可能就是個簡單的類,它擁有極少的數據結構,及針對這些數據的極少操作接口。舉例而言,玩家MP的自動回復功能,就可封裝為一個Policy。將許多Policy組合起來,就可完成一個復雜的功能。
這種思想還可指導很多程序結構方面的設計。例如在做功能的接口拆分時,就將每個函數設計得足夠小,小到單純地完成一個功能。一個功能的入口函數,就將之前實現的小函數全部組合起來,然后共同完成功能點。
當然,<Modern C++ Design>里的Policy在表現形式上有所不同。但其核心思想相同,主要體現在 組合 特點上。
Entity-Component-based Design
Entity-Component-based Design按照google到的文章,嚴格來說算是與OOP完全不同的軟件設計方法。不過在這里它將按照我的意思重新被解釋。
如果說Policy-based Design極大可能地影響著我們平時的細節編碼,那么Entity-Component則是直接對游戲對象的結構設計做直接的說明。 一個游戲對象就是一個Entity。 Entity擁有很少的屬性,也許僅包含一個全局標示的ID。 一個Component則是Entity的某個行為、或者說某個組成部分。 其實說白了,以玩家為例,一個玩家對象就是一個Entity,而一個MP的自動回復功能就可被包裝為一個Component。這個Component可能包含若干與該功能相關的數據,例如回復時間間隔,每次的回復量等。我們往玩家對象這個Entity添加各種Component,也就是給玩家添加各種邏輯功能。
但是,Component之間可能會涉及到交互,玩家對象之外的模塊可能也會與玩家內的某個Component交互。子功能點的拆分,不得不涉及到更多的膠水代碼,這也算一種代價。
游戲對象屬性設計
這份屬性結構設計,基本就是參考了上面提到的設計思想。整個系統有如下組件:
Entity: +-------------------+
| property-table |
+-------------------+
| component-table |
+-------------------+
Property: +-------------------+
| observer-list |
+-------------------+
Component: +--------------------+
| logic-related data |
+--------------------+
| logic-related func |
+--------------------+
意即,所有Entity都包含一個屬性表和組件表。這里的屬性表并非硬編碼的屬性數據成員集合,而是一個key-value形式的表。Property包含一個觀察者列表,其實就是一系列回調函數,但是這些觀察者本質上也是組件,后面會提到。Component正如上文描述,僅包含Component本身實現的功能所需要的數據和函數。整個結構大致的代碼如下:
class Entity {
private:
GUID id;
std::map<std::string, IComponent*> components;
std::map<std::string, Property*> properties;
};
class Property {
private:
std::string name;
Value val;
std::vector<IComponent*> observers;
};
class IComponent {
public:
virtual bool Operate (const Args &args) { return false; }
virtual void OnNotify (const Property &property, const Args &args) {}
protected:
std::string name;
Entity *entity;
};
屬性本身是抽象的,這完全是因為我們將屬性統一地放在了一個表里。從而又導致屬性的值也需要繼續閱讀