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