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;
};
属性本身是抽象的,这完全是因为我们将属性统一地放在了一个表里。从而又导致属性的值也需要继续阅读