简单的物品管理系统

介绍

  很多游戏都允许玩家捡取、携带、使用、买卖、丢弃、饮用,穿戴各种物品。这绝对是个庞大的系统,里面有太多需要关联的东西。在这篇文章中,会展现一个基础的物件管理系统给大家。


系统构架

  面对的第一个问题是如何将物品信息对应到各自的身上:比如血瓶有它的名字、外观以及属性;某些物品拥有特定属性,如数量、魔法属性、个性名字,磨损度等等,而且这些属性有可能是在稍后被添加的。

  那么,我们用类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;
}
posted @ 2010-02-24 10:47  Maxice  阅读(1514)  评论(0编辑  收藏  举报