一个简单的游戏开发框架(五.对象Object)
前面提到我们把行为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() { _value.pstr = nullptr; }
Property(const Property& rhs)
{
_typeID = rhs._typeID;
if ( == _typeID)
{
_value.pstr = new std::string;
*_value.pstr = *rhs._value.pstr;
}
else
{
_value = rhs._value;
}
}
Property::Property(int32 val)
{
_typeID = ;
_value.i32 = val;
}
Property::Property(uint32 val)
{
_typeID = ;
_value.i32 = (int32)val;
}
Property::Property(float val)
{
_typeID = ;
_value.f = val;
}
Property::Property(std::string& val)
{
_typeID = ;
_value.pstr = new std::string;
*_value.pstr = val;
} ~Property()
{
if ( == _typeID) delete _value.pstr;
} public:
Property& Property::operator=(int32 val)
{
SMART_ASSERT( == _typeID || == _typeID).Msg("错误的类型转换").Debug(AssertLevels::WARNNING);
_typeID = ;
_value.i32 = val;
return *this;
}
Property& Property::operator=(uint32 val)
{
*this = (int32)val;
return *this;
}
Property& Property::operator=(float val)
{
SMART_ASSERT( == _typeID || == _typeID).Msg("错误的类型转换").Debug(AssertLevels::WARNNING);
_typeID = ;
_value.f = val;
return *this;
}
Property& Property::operator=(const char* val)
{
SMART_ASSERT( == _typeID || == _typeID).Msg("错误的类型转换").Debug(AssertLevels::FATAL);
if (!_value.pstr)
_value.pstr = new std::string;
_typeID = ;
*_value.pstr = val;
return *this;
} operator int32() const
{
SMART_ASSERT( == _typeID).Msg("错误的类型转换").Debug(AssertLevels::WARNNING);
return _value.i32;
}
operator uint32() const
{
SMART_ASSERT( == _typeID).Msg("错误的类型转换").Debug(AssertLevels::WARNNING);
return (uint32)_value.i32;
}
operator float() const
{
SMART_ASSERT( == _typeID).Msg("错误的类型转换").Debug(AssertLevels::WARNNING);
return _value.f;
}
operator const std::string&() const
{
SMART_ASSERT( == _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<, 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<, Null>
{
void operator()(Object* pObject, typename PropertyGroup<PropertyGroupID>::type& values)
{
typename PropertyGroup<PropertyGroupID>::type oldValues = Get<>()(pObject); pObject->_SetProperty(PropertyGroup<PropertyGroupID>::_1, std::get<>(values));
pObject->_SetProperty(PropertyGroup<PropertyGroupID>::_2, std::get<>(values));
pObject->_SetProperty(PropertyGroup<PropertyGroupID>::_3, std::get<>(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<>(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 =
};
typedef float type1;
typedef float type2;
typedef float type3;
typedef std::tuple<float, float, float> type;
};
一个简单的游戏开发框架(五.对象Object)的更多相关文章
- 一个简单的游戏开发框架(四.舞台Stage)
首先是StageManager类: class StageManager : public Singleton<StageManager> { friend class Singleton ...
- 一个简单的游戏开发框架(六.行为Action)
Action是cocos2d-x中比较重要的概念,有一个庞大的类族.参见老G写的cocos2d-x学习笔记09:动作2:持续动作 除了各种包装器,剩下的主要是一些持续动作: CCMoveTo:移动到. ...
- 一个简单的游戏开发框架(七.动作Motion)
发现还没谈到最基本也是最重要的问题,怎么画图,画动画? 在原版cocos2d-x里画动画比较麻烦,见cocos2d-x学习笔记04:简单动画 cocostudio扩展出CCArmature类,就比较简 ...
- Prism for WPF 搭建一个简单的模块化开发框架(五)添加聊天、消息模块
原文:Prism for WPF 搭建一个简单的模块化开发框架(五)添加聊天.消息模块 中秋节假期没事继续搞了搞 做了各聊天的模块,需要继续优化 第一步画页面 页面参考https://github.c ...
- Prism for WPF 搭建一个简单的模块化开发框架(六)隐藏菜单、导航
原文:Prism for WPF 搭建一个简单的模块化开发框架(六)隐藏菜单.导航 这个实际上是在聊天之前做的,一起写了,也不分先后了 看一下效果图,上面是模块主导航,左侧是模块内菜单,现在加一下隐藏 ...
- Prism for WPF 搭建一个简单的模块化开发框架(四)异步调用WCF服务、WCF消息头添加安全验证Token
原文:Prism for WPF 搭建一个简单的模块化开发框架(四)异步调用WCF服务.WCF消息头添加安全验证Token 为什么选择wcf? 因为好像wcf和wpf就是哥俩,,, 为什么选择异步 ...
- Prism for WPF 搭建一个简单的模块化开发框架(三) 给TreeView加样式做成菜单
原文:Prism for WPF 搭建一个简单的模块化开发框架(三) 给TreeView加样式做成菜单 昨天晚上把TreeView的样式做了一下,今天给TreeView绑了数据,实现了切换页面功能 上 ...
- Cocos2d-x-Lua 开发一个简单的游戏(记数字步进白色块状)
Cocos2d-x-Lua 开发一个简单的游戏(记数字步进白色块状) 本篇博客来给大家介绍怎样使用Lua这门语言来开发一个简单的小游戏-记数字踩白块. 游戏的流程是这种:在界面上生成5个数1~5字并显 ...
- 5、使用Libgdx设计一个简单的游戏------雨滴
(原文:http://www.libgdx.cn/topic/49/5-%E4%BD%BF%E7%94%A8libgdx%E8%AE%BE%E8%AE%A1%E4%B8%80%E4%B8%AA%E7% ...
随机推荐
- Java GUI编程-(项目代码_扫雷_弹钢琴)
--扫雷 package com;import java.awt.*;import java.awt.event.ActionEvent;import java.awt.event.ActionLis ...
- 由Excel表格导出Latex代码
Latex提供了不少绘制表格的宏包(参见:http://tug.org/pracjourn/2007-1/mori/),但在latex里画表并不直观,特别是在表格比较大的时候,有时候也需要先用Exce ...
- [转]抓取安卓APP内接口的方法--Charles
http://blog.csdn.net/yyh352091626/article/details/52759294
- Android高级控件--AdapterView与Adapter
在J2EE中提供过一种非常好的框架--MVC框架,实现原理:数据模型M(Model)存放数据,利用控制器C(Controller)将数据显示在视图V(View)上.在Android中有这样一种高级控件 ...
- 妙方之解决matplotlib的图例里的中文呈现小方形
妙方之解决matplotlib的图例里的中文呈现小方形 分析思路: 每个中文都对应地呈现一个小方形, 不多也不少. 不能说是乱码. 应该是matplotlib的默认字库不支持中文造成的. 应对办法: ...
- 数位dp总结
由简单到稍微难点. 从网上搜了10到数位dp的题目,有几道还是很难想到的,前几道基本都是模板题,供入门用. 点开即可看题解. hdu3555 Bomb hdu3652 B-number hdu2089 ...
- C++ 11学习和掌握 ——《深入理解C++ 11:C++11新特性解析和应用》读书笔记(一)
因为偶然的机会,在图书馆看到<深入理解C++ 11:C++11新特性解析和应用>这本书,大致扫下,受益匪浅,就果断借出来,对于其中的部分内容进行详读并亲自编程测试相关代码,也就有了整理写出 ...
- MFC编程入门之二十二(常用控件:按钮控件Button、Radio Button和Check Box)
本节继续讲解常用控件--按钮控件的使用. 按钮控件简介 按钮控件包括命令按钮(Button).单选按钮(Radio Button)和复选框(Check Box)等.命令按钮就是我们前面多次提到的侠义的 ...
- 【cpp】Vector
这vector 很有用 // compile with: /EHsc #include <vector> #include <iostream> int main() { us ...
- java String 的+操作导致的问题
不说别的先看代码截图: 结果如下: 很好奇为什么String对象的null加上了""就等于"null"字符串了,先给点资料看看: 这个是我找的一个人博客上的截图 ...