freeeyes 发表于 2014-2-20 16:00:02

如何设计游戏中的道具功能(二)

下面来看看具体的实现代码吧。
#ifndef _OBJECT_H
#define _OBJECT_H

//所有物品的基类
//add by freeeyes

#include "ObjectDefine.h"

//此宏用于memcopy边界检查
#define CHECK_SCOPE(a,b) if(a < b){ return false; }

//所有对象的总类
class CBaseObject
{
public:
      CBaseObject()
      {
                m_u8GUID      = 0;
                m_emObjectType= OBJECT_UNKNOW;
                m_emObjectState = OBJECT_UNKNOW_STATE;
                m_u2Count       = 1;
                m_u2MaxCount    = 1;

      };

      CBaseObject(ENUM_OBJECT_TYPE emObjectType, ENUM_OBJECT_STATE emObjectState)
      {
                m_emObjectType= emObjectType;
                m_emObjectState = emObjectState;
      }

      virtual ~CBaseObject() {};

      /*
      *    @Description: 设置对象的全局ID
      */
      void SetGUID(uint64 u8GUID) { m_u8GUID = u8GUID; };

      /*
      *    @Description: 获得对象的全局ID
      */
      uint64 GetGUID() { return m_u8GUID; };

      /*
      *    @Description: 设置对象类型
      */
      void SetObjectType(ENUM_OBJECT_TYPE emObjectType)
      {
                m_emObjectType = emObjectType;
      }

      /*
      *    @Description: 得到对象类型
      */
      ENUM_OBJECT_TYPE GetObjectType()
      {
                return m_emObjectType;
      }

      /*
      *    @Description: 设置对象状态
      */
      void SetObjectState(ENUM_OBJECT_STATE emObjectState)
      {
                m_emObjectState = emObjectState;
      }

      /*
      *    @Description: 得到对象状态
      */
      ENUM_OBJECT_STATE GetObjectState()
      {
                return m_emObjectState;
      }

      /*
      *    @Description: 设置对象叠加个数
      */
      void SetCount(uint16 u2Count)
      {
                m_u2Count = u2Count;
      }

      /*
      *    @Description: 得到对象叠加个数
      */
      uint16 GetCount()
      {
                return m_u2Count;
      }

      /*
      *    @Description: 设置最大个数
      */
      void SetMaxCount(uint16 u2MaxCount)
      {
                m_u2MaxCount = u2MaxCount;
      }

      /*
      *    @Description: 获得最大个数
      */
      uint16 GetMaxCount()
      {
                return m_u2MaxCount;
      }

      /*
      *    @Description: 将对象写入数据流
      */
      virtual bool WriteToBuffer(char* pBuffer, int& nSize)
      {
                int nPos = 0;

                CHECK_SCOPE(nSize, (int)(nPos + sizeof(uint64)));
                uint64 u8NetGUID = 0;
                HTON64(m_u8GUID, u8NetGUID);
                MEMCOPY_SAFE((char* )&pBuffer, (char* )&u8NetGUID, sizeof(uint64));
                nPos += sizeof(uint64);

                CHECK_SCOPE(nSize, (int)(nPos + sizeof(uint16)));
                uint16 u2NetObjectType = 0;
                HTONS(m_emObjectType, u2NetObjectType);
                MEMCOPY_SAFE((char* )&pBuffer, (char* )&u2NetObjectType, sizeof(uint16));
                nPos += sizeof(uint16);

                CHECK_SCOPE(nSize, (int)(nPos + sizeof(uint8)));
                MEMCOPY_SAFE((char* )&pBuffer, (char* )&m_emObjectState, sizeof(uint8));
                nPos += sizeof(uint8);

                CHECK_SCOPE(nSize, (int)(nPos + sizeof(uint16)));
                uint16 u2NetCount = 0;
                HTONS(m_u2Count, u2NetCount);
                MEMCOPY_SAFE((char* )&pBuffer, (char* )&u2NetCount, sizeof(uint16));
                nPos += sizeof(uint16);

                CHECK_SCOPE(nSize, (int)(nPos + sizeof(uint16)));
                uint16 u2NetMaxCount = 0;
                HTONS(m_u2MaxCount, u2NetMaxCount);
                MEMCOPY_SAFE((char* )&pBuffer, (char* )&u2NetMaxCount, sizeof(uint16));
                nPos += sizeof(uint16);

                nSize = nPos;
                return true;
      }

      /*
      *    @Description: 从数据流还原对象
      */
      virtual bool ReadFromBuffer(char* pBuffer, int& nSize)
      {
                int nPos = 0;

                CHECK_SCOPE(nSize, (int)(nPos + sizeof(uint64)));
                MEMCOPY_SAFE((char* )&m_u8GUID, &pBuffer, sizeof(uint64));
                NTOH64(m_u8GUID);
                nPos += sizeof(uint64);

                CHECK_SCOPE(nSize, (int)(nPos + sizeof(uint16)));
                uint16 u2objectType = 0;
                MEMCOPY_SAFE((char* )&u2objectType, &pBuffer, sizeof(uint16));
                NTOHS(u2objectType);
                m_emObjectType = (ENUM_OBJECT_TYPE)u2objectType;
                nPos += sizeof(uint16);

                CHECK_SCOPE(nSize, (int)(nPos + sizeof(uint8)));
                MEMCOPY_SAFE((char* )&m_emObjectState, &pBuffer, sizeof(uint8));
                nPos += sizeof(uint8);

                CHECK_SCOPE(nSize, (int)(nPos + sizeof(uint16)));
                MEMCOPY_SAFE((char* )&m_u2Count, &pBuffer, sizeof(uint16));
                NTOHS(m_u2Count);
                nPos += sizeof(uint16);

                CHECK_SCOPE(nSize, (int)(nPos + sizeof(uint16)));
                MEMCOPY_SAFE((char* )&m_u2MaxCount, &pBuffer, sizeof(uint16));
                NTOHS(m_u2MaxCount);
                nPos += sizeof(uint16);

                nSize = nPos;
                return true;
      }

      /*
      *    @Description: 重载等于运算符
      */
      CBaseObject& operator = (CBaseObject& ar)
      {
                this->SetGUID(ar.GetGUID());
                this->SetObjectType(ar.GetObjectType());
                this->SetObjectState(ar.GetObjectState());
                this->SetCount(ar.GetCount());
                return *this;
      }

private:
      uint64            m_u8GUID;         //唯一编号
      ENUM_OBJECT_TYPEm_emObjectType;   //对象类型
      ENUM_OBJECT_STATE m_emObjectState;//对象状态
      uint16            m_u2Count;      //当前个数
      uint16            m_u2MaxCount;   //最大个数
};

#endif
首先我构造了一个obj对象类,所有的物品都从此派生。
当然,你也可以选择从此派生出一些别的有意思的东东。
这个类我只提供4个参数,唯一编号,对象类型,对象状态,当前个数和最大个数。
唯一编号是当这个物品被创造到这个世界的时候必胜生成的唯一ID,这个算法有很多,64位长度,为了保持物品的唯一性并考虑合区的问题,建议在这个算法里面可以添加一个初始值。不同的服务器初始值不同,这样就可以方便的实现和服而不用在费力去合并那些重复的ID。
我在这个算法里面只提供了一个基本的计算方法。如果你愿意,你可以组织你的算法。
#ifndef _ITEM_H
#define _ITEM_H

//道具对象,继承自CBaseObject
//add by freeeyes

#include <stdio.h>
#include <string>
#include "Object.h"

#define ITEM_BUFFER_SIZE10 * 1024

class CItem : public CBaseObject
{
public:
      CItem();
      CItem(uint32 u4Version);
      virtual ~CItem();

//以下物品设计相关方法
public:
      /*
      *    @Description: 添加相关物品属性
      */
      void AddItemAttribute(ENUM_ITEM_ATTRIBUTE emAttrName, uint16 u2Value);

      /*
      *    @Description: 显示所有该物品属性
      */
      void DisPlay(char* pAttr, uint16& u2Size);

      /*
      *    @Description: 创建物品相关唯一ID,物品名称,物品描述
      */
      void Create(const char* pItemName, int nItemNameSize, const char* pItemDesc, int nItemDesc);

      /*
      *    @Description: 得到物品属性数组
      */
      uint32* GetItemAttributeList();

      /*
      *    @Description: 设置物品属性数组
      */
      void SetItemAttributeList(uint32* ayAttribute);

//物品使用相关方法
public:
      /*
      *    @Description: 得到指定的物品属性
      */
      uint32 GetItemAttribute(ENUM_ITEM_ATTRIBUTE emAttrName);

      /*
      *    @Description: 得到物品名称
      */
      const char* GetItemName();

      /*
      *    @Description: 得到物品描述
      */
      const char* GetItemDesc();

      /*
      *    @Description: 清除所有属性
      */
      void ClearAttribute();

//物品存取相关方法
public:
      /*
      *    @Description: 从数据流还原Item对象
      */
      bool ReadFromBuffer(char* pBuffer, int& nSize);

      /*
      *    @Description: 序列化入数据流
      */
      bool WriteToBuffer(char* pBuffer, int& nSize);

      /*
      *    @Description: 重载等于运算符
      */
      CItem& operator = (CItem& ar)
      {
                //这里由于创建新道具的时候涉及拷贝操作,所以这里不负责拷贝GUID
                //this->SetGUID(ar.GetGUID());
                this->SetObjectType(ar.GetObjectType());
                this->SetObjectState(ar.GetObjectState());
                this->SetCount(ar.GetCount());
                this->SetMaxCount(ar.GetMaxCount());

                this->Create(ar.GetItemName(), (int)strlen(ar.GetItemName()), ar.GetItemDesc(), (int)strlen(ar.GetItemDesc()));

                //复制属性
                for(uint16 i = 0; i < MAX_ITEM_ATTRIBUTE; i++)
                {
                        uint32 u4Value = ar.GetItemAttribute((ENUM_ITEM_ATTRIBUTE)i);
                        if(u4Value > 0)
                        {
                              this->AddItemAttribute((ENUM_ITEM_ATTRIBUTE)i, u4Value);
                        }
                }
               
                return *this;
      }

private:
      uint32 m_ayAttribute;
      char   m_szItemName;
      char   m_szItemDesc;
};

#endif

这里是道具的具体结构了,继承了CBaseObject
这里首先我会根据上一章所说的宏,也就是MAX_ITEM_ATTRIBUTE创建一个数组,这个数组就是标识各个属性的。
这样做,有两种好处,因为在实际运行过程中,所有数据都已数组形式提供,快而且高效。不用遍历,根据数组下表定位具体属性的位置,方便查询。
在这里,实现了对指定对象的串行化操作。由于考虑到了不同的主机字序,所以写入文件的一律是网络字序,到本地打开文件流的时候是主机字序。
这里提供了一系列的方法,用于道具的功能。
考虑到了一些道具复制操作,于是在这里重载了=,如果你需要对这个类有添加,在这里也需要提供相应的代码。

道具的基础属性并没有什么问题,这些代码基本都是固定的。
那么,如果我的道具需要合并,分解,使用等等其他操作,应该怎么办呢?
这里首先提出一个概念,那就是,道具的合成,分解,使用,销毁或者其它功能,都离不开数据源。也就是说,我必须要有一个道具的容器。


这里,我们把这些道具的集合称为一种容器。
背包,仓库,银行。除了货币。只要出现的道具,都必须存在在一种容器中,哪怕是掉落了,也会在一个容器中。
如果有存在于没有所在容器指针的道具,说明程序出现了BUG,需要找出来。
这样做,可以最大程序的避免程序设计时以及使用时候的道具追踪。
好了,让我们看看道具容器(背包)是怎么实现的。
#ifndef _BAG_H
#define _BAG_H

//背包容器
//add by freeeyes

#include "Item.h"
#include "Contraner.h"
#include "ItemManager.h"

#define MAX_BAG_COUNT 100
#define BAG_FILE_NAME "Bag.pmf"

class CBag : public CContainer<CItem>
{
public:
        CBag(uint16 u2Count = MAX_BAG_COUNT);
    virtual ~CBag();

        /*
        *    @Description: 从数据流还原容器对象,需要从子类去实现
        */
        bool ReadFromBuffer(const char* pFileName);

        /*
        *    @Description: 从数据流还原容器对象,需要从子类去实现
        */
        bool WriteToBuffer(const char* pFileName);

        /*
        *    @Description: 设置对象管理器
        */
        void SetItemManager(CItemManager* pItemManager);

        /*
        *    @Description: 背包清空事件
        */
        void Close();

        /*
        *    @Description: 添加一个背包道具
        */
        uint16 AddObject(CItem* pItem);

        /*
        *    @Description: 添加一个背包道具
        */
        bool AddObject(uint16 u2Index, CItem* pItem);

        /*
        *    @Description: 删除指定类型的道具以及数量
        */
        bool DelObject(uint32 u4ItemBaseID, uint16 u2Count);

        /*
        *    @Description: 删除指定单元格的物品
        */
        bool DelObject(uint16 u2Index);

        /*
        *    @Description: 得到指定道具类型的总数
        */
        uint16 GetBagCurrCount(uint32 u4ItemBaseID);

        /*
        *    @Description: 得到指定位置的道具
        */
        CItem* GetContainObject(uint16 u2Index);

        /*
        *    @Description: 得到指定类型的道具,如果有多个返回第一个
        */
        CItem* GetContainObject(uint32 u4ItemBaseID);

        /*
        *    @Description: 从背包里移出一个物品
        */
        CItem* MoveOutItem(uint32 u2Index);

        /*
        *    @Description:分解一个叠加的道具格子
        */
        bool SplitItem(uint32 u2Index, uint16 u2ItemCount);

public:
        CItemManager* m_pItemManager;
};

#endif
看这些函数的注释,你应该会大概理解其中的意思。
一般背包的基本功能都会实现。
实现了道具的容器,就要实现道具的各种使用花样了。
这里我单独独立出来了一个类,用于道具的使用,比如合成和分解。
#ifndef _ITEMSYNTHETIC_H
#define _ITEMSYNTHETIC_H

#include "Bag.h"

//合成专用类
//赫拉迪克方块
//add by freeeyes

//最大合成道具总数不能超过30个
#define MAX_NEWITEM_SYNTHETIC_COUNT 30

enum ENUM_SYNTHETIC_ERRROR:uint8
{
        SYNTHETIC_ERRROR_NONE = 0,   //无措
        SYNTHETIC_ERRROR_NOSYN,      //无法合成,公式错误
        SYNTHETIC_ERRROR_NOENOUGTH,//不够
        SYNTHETIC_ERRROR_NOEXIST,    //不存在
        SYNTHETIC_ERRROR_DELETE,   //扣减道具失败
        SYNTHETIC_ERRROR_NOITEM,   //不是合成物品
        SYNTHETIC_ERRROR_NULLITEM,   //道具池已没有空余
        SYNTHETIC_ERRROR_ADDBAG,   //添加到背包错误
        SYNTHETIC_ERRROR_BAGFULL,    //背包已满
        SYNTHETIC_ERRROR_GUID,       //GUID已存在
};


class CItemSynTheTic
{
public:
        CItemSynTheTic();
        ~CItemSynTheTic();

        ENUM_SYNTHETIC_ERRROR SynTheTic(uint32 u4BaseID, CBag* pBag, CItemDictionary* pItemDictionary, CItemManager* pItemManager);

private:
        ENUM_SYNTHETIC_ERRROR Check_NewItem(CItem* pDrawItem, CItemDictionary* pItemDictionary);
        ENUM_SYNTHETIC_ERRROR Check_Item_Enougth(CItem* pDrawItem, CBag* pBag);
        ENUM_SYNTHETIC_ERRROR Check_Bag_Enougth(CItem* pDrawItem, CBag* pBag);
        ENUM_SYNTHETIC_ERRROR Check_Item_Pool_Enougth(CItem* pDrawItem, CItemManager* pItemManager);
        ENUM_SYNTHETIC_ERRROR Reduce_Bag_Item(CItem* pDrawItem, CBag* pBag, CItemManager* pItemManager);
        ENUM_SYNTHETIC_ERRROR Create_New_Bag_Item(CBag* pBag, CItemManager* pItemManager, CItemDictionary* pItemDictionary);

        void Get_New_Item_Count(CItem* pDrawItem);

private:
        uint32 m_u4ItemNewBaseID;
        uint32 m_u4ItemClassCount;
};


#endif
合成而言,方法就是SynTheTic,分解同理,用一个函数。
仔细想想,合成和分解无外乎,组合成一个或者多个新道具,分解岂不是一样?
这里需要几个参数。
BaseID:道具的基本类型,这个属性在道具设计的时候,就会被指定。
pBag: 背包的指针,我会根据BaseID去背包寻找是否存在这个物品,因为很可能存在的这个物品,是叠加的,或者是多个,我只需要找到其中一个去做这件事就行了。
CItemDictionary: 道具字典,我会根据这个字典去创建指定的物品。一个或者多个。
CItemManager: 这个就是整个世界的道具管理器,记录所有生成的道具,在这个类里面都有对应关系。
这里,其他的大家都容易理解,我要重点说一下CItemManager


CItemManager的类结构如下
#ifndef _ITEMMANAGER_H
#define _ITEMMANAGER_H

//道具管理器
//add by freeeyes

#include "ItemDictionary.h"
#include "ItemMonitor.h"
#include <time.h>

enum ENUM_ITEM_MANAGER_ERROR:uint8
{
        ITEM_MANAGER_NO_ERROR      = 0x00,   //无措
        ITEM_MANAGER_DICTIONARY_NULL = 0x01,   //字典为空
        ITEM_MANAGER_COUNT_FULL      = 0x02,   //已达到最大上限
        ITEM_MANAGER_BASEID_NULL   = 0x03,   //BaseID不存在
        ITEM_MANAGER_USED_NULL       = 0x04,   //该物品不存在
        ITEM_MANAGER_MAP_EXIST       = 0x05,   //关系已存在
};

//这个类需要单件来处理
class CItemManager
{
public:
        CItemManager();
        ~CItemManager();

        /*
        *    @Description: 创建一个GUID
        */
        uint64 CreateGUID();

        /*
        *    @Description: 初始化道具管理器
        */
        bool Init(CItemDictionary* pItemDic, uint32 u4ItemMaxCount);

        /*
        *    @Description: 清除道具管理器
        */
        void Close();

        /*
        *    @Description: 从道具管理器中获得一个空闲的CItem并返回
        */
        CItem* CreateItem();

        /*
        *    @Description: 从道具管理器中回收一个道具(这个道具还未生效)
        */
        ENUM_ITEM_MANAGER_ERROR DeleteItemUnRegedit(CItem*& pItem);

        /*
        *    @Description: 从道具管理器中回收一个道具
        */
        ENUM_ITEM_MANAGER_ERROR DeleteItem(CItem*& pItem);

        /*
        *    @Description: 从道具管理器中回收一个道具
        */
        ENUM_ITEM_MANAGER_ERROR DeleteItem(uint64 u6GUID);

        /*
        *    @Description: 建立物品和GUID之间的对应关系
        */
        ENUM_ITEM_MANAGER_ERROR RegeditItem(uint64 u8GUID, CItem* pItem);

        /*
        *    @Description: 得到空余的道具数量
        */
        uint32 GetFreeItemCount();

        /*
        *    @Description: 保存监控日志
        */
        void SaveMonitor();

private:
        typedef map<uint64, CItem*> mapItemUsed;
        typedef vector<CItem*> vecItemPool;

        mapItemUsed   m_mapItemUsed;      //记载和GUID的映射关系
        vecItemPool   m_vecItemUnUsed;      //空闲的道具

        CItemDictionary* m_pItemDic;   //物品字典,用于翻阅
        uint32         m_u4MaxCount; //最大生成物品个数(包含目前已经有的)
       
        //生成GUID相关参数
        uint32         m_u4TimeSeed;       //时间种子
        uint32         m_u4TimeSeedCount;//当前种子个数

        //道具监控者
        CItemMonitor   m_objItemMonitor;   //监控道具的类
};

#endif

我如何从这个类里面创建一个道具呢?

        //创建一个道具
        CItem* pItem = NULL;
        pItem = m_objItemManager.CreateItem();


首先,我去道具池里面获取一个空余的道具对象。
如果没有了,那么就会得到一个NULL,说明全世界的道具已经都被生成出来了,已经没有空余的道具可以被生成了。

                CItem* pItemDoc = m_ItemDictionary.GetItem(u4ItemBaseID);
                (*pItem) = (*pItemDoc);


从字典里面获取一个要生成的指定BaseID的道具信息,并赋值给这个空道具对象,让他具象化。

                m_objItemManager.RegeditItem(pItem->GetGUID(), pItem)
告诉CItemManager,我已经具象化完成了,并赋予了它一个GUID(唯一ID),建立道具和GUID的唯一关联关系。
如果这时候这个GUID已经存在,说明代码存在问题。生成的这个道具是不合法的。
于是回收之


                        //如果当前道具的GUID已被使用,则将物品销毁归还道具池
                        m_objItemManager.DeleteItemUnRegedit(pItem);

反之,放入容器。
完整整个操作,呵呵。
挺简单吧,其实,道具说复杂不复杂,但是要想做好可控的道具流,还是要费一些心思的。
下一章,我会讲讲邮件是怎么做的。下一章也是最后一章,在那时候我会贴出所有的代码。

页: [1]
查看完整版本: 如何设计游戏中的道具功能(二)