让每个实体拥有唯一ID——Entity和EntityManager类的封装

为什么要唯一的ID?
拥有一个唯一的ID是一件很好的事,特别是在网络中传输数据时,需要指定某个玩家,或者需要操作某个战斗单位,要做的只是传输一个int类型的ID即可。对游戏中的每个实体进行唯一标示,这就是拥有一个Entity的意义。

Entity类
我们创建一个Entity类,它拥有一个m_nID成员,并且在构造函数中用一个static的计数器来为m_nID创建唯一的ID值。
/////////////////////////////////////////////////////////////// // Copyright(c) Tiaotiao // // FileName : Entity.h // Creator : Tiaotiao // Date : 2010-1-14 // Comment : 为子类提供唯一的ID,所有需要ID的实体继承该类 // /////////////////////////////////////////////////////////////// class CEntity { public: CEntity(void) { static int s_nCount = 1; m_nID = s_nCount++; } virtual ~CEntity(void) {} public: int ID() { return m_nID; } protected: int m_nID; };

不必担心ID值会超过int的上限,如果你确实有所顾虑,那么在快达到2*10^9的时候将它重新置1即可。虽然说这样仍然不算严谨,因为有可能在新的一轮计数时,之前ID较小的实体并没有被销毁,这时就可能存在冲突。我们这里认为这种情况不会发生,如果你需要做一个宏伟的项目,那么需要另行考虑。

有了唯一ID,如何找到对应的实体?
服务端从网络接收到一个命令,ID为某个值的战斗单位要向前移动一步。这时需要通过ID找到对应的实例。这个需求很容易用STL里的map来实现。

EntityManager类
这里我们将它封装为EntityManager类,EntityManager类保存一个map,并向外提供GetEntityByID的方法。这里,我将Entity实例的new和delete的职责也交付给EntityManager,原因是EntityManager可以在Entity创建和销毁时,同时移除map中的映射,这样就不用担心被delete的实例指针仍然能在EntityManager中找到。

/////////////////////////////////////////////////////////////// // Copyright(c) Tiaotiao // // FileName : EntityManager.h // Creator : Tiaotiao // Date : 2010-1-14 // Comment : 管理Entity实例,对外提供创建销毁和查找Entity的方法 // /////////////////////////////////////////////////////////////// #include <map> #include "Entity.h" class CEntityManager { public: CEntityManager(void); virtual ~CEntityManager(void); public: // 创建一个Entity实例 virtual CEntity* New() = 0; // 销毁一个Entity实例 void Delete(CEntity* pkEntity); // 通过ID查找Entity指针, 不存在返回NULL CEntity* GetEntityByID(int nID); protected: // 子类New实体后,需将实体加入到map中 CEntity* AddEntity(CEntity* pkEntity); protected: std::map<int, CEntity*> m_mapEntitys; };

// EntityManager.cpp #include "EntityManager.h" using namespace std; CEntityManager::CEntityManager(void) :m_mapEntitys() { } CEntityManager::~CEntityManager(void) { } void CEntityManager::Delete( CEntity* pkEntity ) { int id = pkEntity->ID(); m_mapEntitys.erase(m_mapEntitys.find(id)); delete pkEntity; } CEntity* CEntityManager::GetEntityByID( int nID ) { if (m_mapEntitys.find(nID) != m_mapEntitys.end()) { return m_mapEntitys[nID]; } return NULL; } CEntity* CEntityManager::AddEntity( CEntity* pkEntity ) { int id = pkEntity->ID(); m_mapEntitys[id] = pkEntity; return pkEntity; }

配套使用
下面给出一个例子,如何配套使用这两个类。

这里创建一个简化的战斗单位的类Unit,Unit类继承于Entity,并且UnitManager继承于EntityManager。

// Unit.h #include "entity.h" class CUnit : public CEntity { public: CUnit(void); CUnit(int nUnitType); virtual ~CUnit(void); public: int UnitType() const { return m_nUnitType; } void UnitType(int val) { m_nUnitType = val; } public: int x; int y; private: int m_nUnitType; };

// Unit.cpp CUnit::CUnit(void) : x(0),y(0),m_nUnitType(0) { } CUnit::CUnit( int nUnitType ) : x(0),y(0),m_nUnitType(0) { } CUnit::~CUnit(void) { }

注意这个Unit类有一个重载的构造函数。在编写Unit类时父类Entity不会对它有任何的制约,只是继承了父类的一个ID而已,需要做的只是专注于Unit本身的逻辑而已。Unit类的cpp文件不做任何事情,只是简单的在构造函数中初始化变量。

#include "entitymanager.h" #include "Unit.h" class CUnitManager : public CEntityManager { public: CUnitManager(void); virtual ~CUnitManager(void); public: virtual CUnit* New(); CUnit* New(int nUnitType); CUnit* GetUnitByID(int nID); };

// UnitManager.cpp #include "UnitManager.h" CUnitManager::CUnitManager(void) { } CUnitManager::~CUnitManager(void) { } CUnit* CUnitManager::New() { CUnit* pUnit = new CUnit(); AddEntity(pUnit); return pUnit; } CUnit* CUnitManager::New( int nUnitType ) { CUnit* pUnit = new CUnit(nUnitType); AddEntity(pUnit); return pUnit; } CUnit* CUnitManager::GetUnitByID( int nID ) { return (CUnit*)GetEntityByID(nID); }

重点是UnitManager需要做一些事情。
第一是实现父类的纯虚函数CEntity* New(),并且将返回值修改为CUnit*。别忘了在new的语句之后调用AddEntity将其加入到map的映射中。
第二件事情是提供一个GetUnitByID方法。虽然父类已经提供了GetEntityByID方法能满足需求,这样做的目的只是实现类型转换,在语义上也更明确一些。
除此之外最后一件事情就是,如果Unit类提供了其他的重载构造函数,那么在UnitManager中也要有对应的New方法。

后面两点虽然不是强制的,但是这样做会使得这个UnitManager类更好用。 编写EntityManager子类时,应该要记住做这三件事。

小结
就是这样,很简单的一个封装但实用。在我最近写的项目中大量用到这种结构,使得查找和管理实例相当容易。

最后注意几个原则:
1.只能通过UnitManager的New方法来创建Unit实例。
2.谁调用New方法获取了Unit实例,谁就要负责调用UnitManager的Delete方法销毁它。

你可能感兴趣的:(游戏,Date,网络,null,delete,2010)