阅读: 4 评论: 0 作者: Maxice 发表于 2010-02-24 10:47 原文链接
介绍
很多游戏都允许玩家捡取、携带、使用、买卖、丢弃、饮用,穿戴各种物品。这绝对是个庞大的系统,里面有太多需要关联的东西。在这篇文章中,会展现一个基础的物件管理系统给大家。
系统构架
面对的第一个问题是如何将物品信息对应到各自的身上:比如血瓶有它的名字、外观以及属性;某些物品拥有特定属性,如数量、魔法属性、个性名字,磨损度等等,而且这些属性有可能是在稍后被添加的。
那么,我们用类cItem来表示某一个物品。这个类包含了一个可以表示物品类型的标示。如果我们需要获得某个物品的属性,可通过cItemDatabase来获得,这是个物品数据库,它中包含所有物品的信息。
因为物品会出现在不同的地方,,比如在地上,在交易中,或是玩家的背包里,我们要展现这些物品,就需要一个集合来统一表现,类cItemPack就是物品的集合,我把它翻译成物品包,你可以遍历这包中的物品,添加或移除他们。
物品
第一步,我们来实现物品类,这是我们所用功夫最多的地方。首先考虑物品属性,他应该拥有如下属性:
可以设置到另一个物品上
和另外一个物品对比(看他们是否一样)
物品的指针
可以返回其唯一的ID
可以创建一个拥有唯一ID的物品
根据ID排序
现在我们得出了如下类定义:
class cItem {
unsigned long type;
public:
cItem( unsigned long );
cItem & operator= ( const cItem & );
bool operator== ( const cItem & ) const;
bool operator< ( const cItem & ) const;
unsigned long getID( ) const;
};
上面的类有一个构造函数(参数ID),指针,比较操作,获取ID的函数。私有成员变量是物品的ID,通过这个ID可以从数据库中得到该物品的属性。
这里要注意的是,const关键字出现在很多地方,在编程中要养成这种好习惯(具体作用大家都知道,呵呵)。通过const修饰,当cItem变得很大的时候,可以增加更多的安全性。
下面是实现:
cItem::cItem( unsigned long t ) : type( t ) { }
cItem &cItem::operator =( const cItem & o) { type = copy.type; return( *this ); }
unsigned long cItem::getID( void ) const { return( type ); }
bool cItem::operator ==( const cItem & i ) const { return( type == i.type ); }
bool cItem::operator <( const cItem &i ) const { return( type < i.type ); }
这个类的基础工作就做完了,可以在上面添加自己所需要的东西。接下来我们来谈谈cItemPack
物品包
用户希望物品包拥有如下属性:
将一个物品放入一个包中
创建一个空包
从一个包中将所有物品移除
添加某一物品到包中
从包中移除某一类物品
计算物品的某种类型
将两个包合并成为一个包
包中有列表存放物品的类型。以后我们就可以通过列表容器来访问集合中的物品。不过这里的时间消耗为O(n)。
也可以使用vector来实现,只需要O(1)的访问时间。但如果包中只存在一个物品,那就有点浪费。另外,如果物品很复杂,包含很多属性,或是两个物品有相同类型但不同属性,那么,依靠类型来获得物品,就会出问题,所以我们用map,下面是类定义:
cItemPack类:
class cItemPack {
std::map< cItem, unsigned long > contents;
public:
cItemPack( cItem & , unsigned long );
void clear( );
unsigned long add( const cItem & , const unsigned long );
unsigned long remove( const cItem & , const unsigned long );
unsigned long getAmount( const cItem & ) const;
const std::map< cItem, unsigned long > & getItems( ) const;
cItemPack & operator= ( const cItemPack & );
cItemPack & operator+= ( const cItemPack & );
cItemPack operator+ ( const cItemPack & ) const;
};
通过getAmount()接口获取物品的数量,可以很方便的展现商品清单等功能。通常cItemPack会表示一个实体,所以获取物品数量的接口是非常重要的。下面是cItemPack实现部分:
cItemPack::cItemPack( cItem & i, unsigned long a ) { contents[i] = a; }
void cItemPack::clear( void ) { contents.clear( ); }
cItemPack & cItemPack::operator=( const cItemPack & o ) {
contents = o.contents;
return( *this );
}
两个构造函数,一个清除函数,一个=操作:这里没有具体的初始化,构造是依靠传进来的物品。接下来的函数可以向集合中添加某种物品。
unsigned long cItemPack::add( const cItem & i, const unsigned long a )
{
return( contents[i] += a );
}
下面的函数会返回某一种类型物品的数量,注意,因为map中的[]操作符并不是const,而这个函数的参数却是const,所以必须用map 的const iterator。
unsigned long cItemPack::getAmount( const cItem & i ) const
{
std::map< cItem,unsigned long >::const_iterator j;
j = contents.find( i );
if( j == contents.end( ) ) { return( 0 ); }
else { return( j->second ); }
}
这里我们还需要一个移除函数,代码如下:
unsigned long cItemPack::remove( const cItem & i, const unsigned long a )
{
unsigned long t = contents[i];
if( a > t ) { contents[i] = 0; return( a-t ); }
else { contents[i] = t-a; return( 0 ); }
}
接下来是两个联合集合的函数:
cItemPack & cItemPack::operator+=( const cItemPack & o )
{
std::map< cItem,unsigned long >::const_iterator i;
for( i = o.contents.begin( ); i != o.contents.end( ); ++i )
{
add( i->first, i->second );
}
return( *this );
}
cItemPack cItemPack::operator+( const cItemPack & o ) const
{
return( cItemPack(*this) += o );
}
最后,将map的接口提供出来:
const std::map< cItem,unsigned long > & cItemPack::getItems( void ) { return( contents ); }
物品数据库
我们在这里制定的物品都有名字,有简短的描述,以及值和重量,他们保存在下面的结构中:
struct sItemData {
std::string name, description;
unsigned long value, weight;
};
我们使用monostate来表现这个数据库,这个对象类似单件,只存在一份,不过不能直接进行全局访问。
下面是该对象的定义:
namespace cItemDatabase {
const sItemData & getData( const cItem & );
cItem create( const std::string & );
void initialize( const std::string & );
void unload( void );
};
第一个函数是获得物品数据
第二个是创建一个物品,参数是该物品的名字
不过这里需要注意的是,用这些函数操作物品的时候,都必要要保证被操作的物品是已经被初始化的。不过要注意的是,如果操作失败的话,这里并没有一个返回错误的机制。第二,创建函数在创建物品的时候,并没有判断穿过来的物品名是否有效;第三,用户在获取某一个物品时,如果该物品并不存在于数据库中,同样没有一个返回错误的机制来通知用户。那么我们现在来创建一个返回错误的机制:
enum eDatabaseError {
IDBERR_NOT_INITIALIZED,
IDBERR_INVALID_NAME,
IDBERR_INVALID_ITEM
};
这是数据库的定义:
namespace cItemDatabase {
std::deque< sItemData > item_database_entries;
bool item_database_initialized = false;
};
Deque用在这里的好处就用不着再讲了。呵呵,看老外一大段都是废话我就懒得翻译了。接下来是实现:
void cItemDatabase::initialize( const std::string & s ) {
item_database_entries.clear( );
//FILE LOADING SEQUENCE
item_database_initialized = true;
}
void cItemDatabase::unload( void ) {
item_database_entries.clear( );
item_database_initialized = false;
}
这里并没有具体的调用数据文件的代码,大家都会有自己的一套机制。本文提供的源代码里有作者的一套调用系统,大家可以参考。
getData函数的实现:
const sItemData & cItemDatabase::getData( const cItem & i ) {
if( item_database_initialized ) {
unsigned long type = i.getID( );
if( type >= item_database_entries.size( ) )
{ throw IDBERR_INVALID_ITEM; }
else { return( item_database_entries[type] ); }
}
else { throw IDBERR_NOT_INITIALIZED; }
}
创建函数的实现:
cItem cItemDatabase::create( const std::string & s ) {
if( !item_database_initialized ) { throw IDBERR_NOT_INITIALIZED; }
long i;
for( i = item_database_entries.size( )-1; i >= 0; --i ) {
if( item_database_entries[i].name == s ) { return( cItem(i) ); }
}
throw IDBERR_INVALID_NAME;
}
评论: 0 查看评论 发表评论
找优秀程序员,就在博客园
最新新闻:
· 诺基亚两款概念手机Stealth/Dragonfly曝光(2010-03-09 09:45)
· IE6必须死 却没人做得到(2010-03-09 09:41)
· 南方日报:QQ的平台化价值是如何创造的?(2010-03-09 09:29)
· 报告称SNS网站功能趋于同质化(2010-03-09 09:26)
· 迫于压力 微软将修改浏览器选择界面算法(2010-03-09 09:09)
编辑推荐:史上最强女游戏程序员
网站导航:博客园首页 个人主页 新闻 闪存 小组 博问 社区 知识库