前面提到我们把行为Action从对象Object中分离了出来,用各种不同的行为组合出对象的功能。大家都知道,面向对象的一个类,就是数据和操作的集合。操作(行为)被分离出来了,数据怎么办呢?操作依赖的数据从哪里取得?《游戏编程精粹5》"基于组件的对象管理"中提供了一个方案,把数据都放到操作(书中称为组件)中,当一个操作需要位于另一个操作的数据时,双方通过消息来通讯。个人不喜欢这种搞法,操作之间的依赖关系太复杂了,是网状的。数据应该仍然放在对象中,所有操作都只依赖于对象本身,这样的依赖关系是星状的,要简明得多。当一个对象中不存在某行为需要的数据时,表明这个行为压根就不应该在该对象上执行,这个时候可以用assert处理,也可以让行为什么都不做。
但是这样就出现了一个问题,怎样查询对象中是否存在某个特定属性(数据)呢?需要一个通用接口来操作对象属性,类似下面这种
R GetProperty(int32 propertyID); void SetProperty(int32 propertyID, const R& property);
propertyID是唯一标识属性的常量,
typedef struct { enum E { NAME, POSX, POSY, POSZ, COUNT }; }Properties;
逻辑层可以接着往下定义更多属性。
那个R怎么办?我首先想到的是boost::variant,但是varient只支持最多20个属性。而且这个时候我发现,属性数据没有必要写死在对象里,可以用SetProperty给对象动态增加属性,可以把数据库读出的字段作为属性增加到对象里,也可以用配置文件配置对象属性。这种情况下variant显得太大太复杂,毕竟是大量频繁使用的东西,尽量还是小一点。最后自己写了个简单的:
class Property { public: Property() : _typeID(0) { _value.pstr = nullptr; } Property(const Property& rhs) { _typeID = rhs._typeID; if (3 == _typeID) { _value.pstr = new std::string; *_value.pstr = *rhs._value.pstr; } else { _value = rhs._value; } } Property::Property(int32 val) { _typeID = 1; _value.i32 = val; } Property::Property(uint32 val) { _typeID = 1; _value.i32 = (int32)val; } Property::Property(float val) { _typeID = 2; _value.f = val; } Property::Property(std::string& val) { _typeID = 3; _value.pstr = new std::string; *_value.pstr = val; } ~Property() { if (3 == _typeID) delete _value.pstr; } public: Property& Property::operator=(int32 val) { SMART_ASSERT(0 == _typeID || 1 == _typeID).Msg("错误的类型转换").Debug(AssertLevels::WARNNING); _typeID = 1; _value.i32 = val; return *this; } Property& Property::operator=(uint32 val) { *this = (int32)val; return *this; } Property& Property::operator=(float val) { SMART_ASSERT(0 == _typeID || 2 == _typeID).Msg("错误的类型转换").Debug(AssertLevels::WARNNING); _typeID = 2; _value.f = val; return *this; } Property& Property::operator=(const char* val) { SMART_ASSERT(0 == _typeID || 3 == _typeID).Msg("错误的类型转换").Debug(AssertLevels::FATAL); if (!_value.pstr) _value.pstr = new std::string; _typeID = 3; *_value.pstr = val; return *this; } operator int32() const { SMART_ASSERT(1 == _typeID).Msg("错误的类型转换").Debug(AssertLevels::WARNNING); return _value.i32; } operator uint32() const { SMART_ASSERT(1 == _typeID).Msg("错误的类型转换").Debug(AssertLevels::WARNNING); return (uint32)_value.i32; } operator float() const { SMART_ASSERT(2 == _typeID).Msg("错误的类型转换").Debug(AssertLevels::WARNNING); return _value.f; } operator const std::string&() const { SMART_ASSERT(3 == _typeID).Msg("错误的类型转换").Debug(AssertLevels::FATAL); return *_value.pstr; } bool operator==(Property& rhs) { return _value.i32 == rhs._value.i32; } private: Property& operator=(const Property& rhs) { return *this; } private: union { int32 i32; float f; std::string* pstr; }_value; char _typeID; };
以union为基础,只支持int, float, string3种类型,大多数情况都够用了。如果有必要,可以加上int64和double。
Object类基本上就是管理一个属性数组,如下:
class Object { public: virtual ~Object(); public: virtual bool LoadObject(const char* fileName); virtual void OnPropertyChanged(uint32 propertyID, Property& oldValue) {} enum { OBJECT_TYPE = BaseObjects::OBJECT }; virtual uint32 GetType() { return OBJECT_TYPE; } public: Property& GetProperty(uint32 propertyID) { SMART_ASSERT(_propertys.find(propertyID) != _propertys.end()).Msg("未支持的属性").Debug(AssertLevels::FATAL); return _propertys[propertyID]; } template<typename T> void SetProperty(uint32 propertyID, T value) { if (_propertys.find(propertyID) == _propertys.end()) return; T oldValue = _propertys[propertyID]; if (oldValue == value) return; _propertys[propertyID] = value; Property val = oldValue; OnPropertyChanged(propertyID, val); } template<uint32 PropertyGroupID> void OnPropertyGroupChanged(typename PropertyGroup<PropertyGroupID>::type& oldValues); template<uint32 PropertyGroupID> void SetPropertyGroup(typename PropertyGroup<PropertyGroupID>::type& values) { typename _SetPropertyGroup<PropertyGroupID>::template Set<PropertyGroup<PropertyGroupID>::Count> writer; writer(this, values); }
void RunAction(Action* pAction); void StopAction(uint32 actionID); protected: template<uint32 PropertyGroupID> struct _SetPropertyGroup { template<int count, typename Null = void> struct Get { typename PropertyGroup<PropertyGroupID>::type operator()(Object* pObject) { assert(false); } }; template<typename Null> struct Get<3, Null> { typename PropertyGroup<PropertyGroupID>::type operator()(Object* pObject) { typename PropertyGroup<PropertyGroupID>::type1 p1 = pObject->GetProperty(PropertyGroup<PropertyGroupID>::_1); typename PropertyGroup<PropertyGroupID>::type2 p2 = pObject->GetProperty(PropertyGroup<PropertyGroupID>::_2); typename PropertyGroup<PropertyGroupID>::type3 p3 = pObject->GetProperty(PropertyGroup<PropertyGroupID>::_3); return std::move(std::make_tuple(p1, p2, p3)); } }; template<int count, typename Null = void> struct Set { void operator()(Object* pObject, typename PropertyGroup<PropertyGroupID>::type& values) { assert(false); } }; template<typename Null> struct Set<3, Null> { void operator()(Object* pObject, typename PropertyGroup<PropertyGroupID>::type& values) { typename PropertyGroup<PropertyGroupID>::type oldValues = Get<3>()(pObject); pObject->_SetProperty(PropertyGroup<PropertyGroupID>::_1, std::get<0>(values)); pObject->_SetProperty(PropertyGroup<PropertyGroupID>::_2, std::get<1>(values)); pObject->_SetProperty(PropertyGroup<PropertyGroupID>::_3, std::get<2>(values)); pObject->OnPropertyGroupChanged<PropertyGroupID>(oldValues); } }; }; template<typename T> void _SetProperty(uint32 propertyID, T value) { if (_propertys.find(propertyID) == _propertys.end()) return; _propertys[propertyID] = value; } protected: std::map<uint32, Property> _propertys; std::list<Action**> _runningActions; }; template<> inline void Object::OnPropertyGroupChanged<Propertys::POSX>(typename PropertyGroup<Propertys::POSX>::type& oldValues) { Property val(std::get<0>(oldValues)); OnPropertyChanged(Propertys::POSX, val); }
有两个比较奇怪的接口:
template<uint32 PropertyGroupID> void OnPropertyGroupChanged(typename PropertyGroup<PropertyGroupID>::type& oldValues); template<uint32 PropertyGroupID> void SetPropertyGroup(typename PropertyGroup<PropertyGroupID>::type& values) { typename _SetPropertyGroup<PropertyGroupID>::template Set<PropertyGroup<PropertyGroupID>::Count> writer; writer(this, values); }
因为属性都被分拆成int, float, string三个类型,原来可能存在于对象中的结构都被打散了。如果希望把某些属性作为一个整体来操作,以避免反复不必要地调用OnPropertyChange,就需要用到SetPropertyGroup。逻辑层需要特化PropertyGroup类,象下面这样:
template<uint32 PropertyID> struct PropertyGroup {}; template<>
struct PropertyGroup<Properties::POSX> { enum Content { _1 = Properties::POSX, _2 = Properties::POSY, _3 = Properties::POSZ, Count = 3 }; typedef float type1; typedef float type2; typedef float type3; typedef std::tuple<float, float, float> type; };