首页 > 技术文章 > 一个简单的游戏开发框架(五.对象Object)

bnudc 2013-11-14 18:46 原文

前面提到我们把行为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; };

 

推荐阅读