首 页 | 新 闻 | Symbian | Android| Windows Mobile | J2ME | 下载中心 | 游戏策划招聘与求职 | 购书指南 | 视频教程
您现在的位置: 开发视界 >> Symbian >> 语言基础 >> 正文
物品管理系统
作者:杨冰(源…    文章来源:NGame2000    更新时间:2005-10-26 7:53:10

物品管理系统
  
 
作者:Victor Nicollet
翻译:杨冰(源代码之光)
个人主页:http://www.ngame2000.com
发布:GameRes http://www.gameres.com
创建时间:2004-11-18


介绍

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


系统构架

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

  那么,我们用类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;
}
 

相关文章:
Symbian OS之客户 —服务器架构
第11章 其它编程经验
高质量C++/C编程指南 -- 第5章 常量
TDateTime
C++ 语言基础(1)
BCB日常使用小集锦
C++辨析系列谈
CRC-16/CRC-32 程序代码
 

站点地图 | 加入收藏 | 联系站长 | 广告服务 |
QQ:280529124  Tel:0592-8271361 辽ICP备05021703号