如何设计游戏中的道具功能(二)
下面来看看具体的实现代码吧。#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]