引言
在我的博客中,我曾经翻译了几篇关于ECS的文章。这些文章都是来自于Game Development网站。如果你对这个架构方式还不是很了解的话,欢迎阅读理解 组件-实体-系统和实现
组件-实体-系统。
我发现这个架构方式,是在浏览GameDev上的文章的时候了解到的。很久以前,就知道了有这么个架构方法,只是一直没有机会自己实践下。这一次,我就抽空,根据网上对ECS系统的讨论,采用了一种实现方法,来实现一个。
我很喜欢做游戏,所以同样的,还是用游戏实例来实践这个架构方法。我将会采用cocos2d-x来作为游戏开发的主要工具,这是一款十分强大的跨平台游戏引擎,感兴趣的读者,可以自行搜索了解。
ShapeWar
我一直觉得,作为游戏程序员,能够自己独立的绘制游戏中的图片资源是一件非常好玩的事情。所以,没有美术功底的我,就选择了一种复古风格的艺术——像素艺术来学习。经过一段时间的学习,发现做像素画还是很有趣的,所以我就将我以前做的简单的像素图片,来融合成现在的这个游戏实例——ShapeWar 。
这个游戏很简单,玩家通过键盘上的左右键来移动发射器,通过按下space键,来进行攻击,将所有掉落下来的立方体全都打掉。如果有立方体遗漏掉,那么将会丢掉一颗血,直到玩家死亡为止。这个游戏,开始的时候,可能会非常容易,但是,立方体下落的速度是逐渐增加的,到了后面,如果玩家还能够坚持住的话,那非常了不起!!!
好了,游戏规则很简单,来看看游戏的截图吧!
好了,这个游戏很简单,有兴趣的同学,可以到这里来下载,试玩一下,并且在留言中,告诉我,你最高得了多少分哦!!!
架构设计
从上面的截图,大家也能够明白,游戏只有两个场景,分别是开始场景,和游戏进行场景。需要完成的功能如下:
- 能够产生立方体,控制立方体产生
- 能够控制发射器,发射出球体
- 能够检测出球体和立方体之间的碰撞
- 对不同的立方体,需要碰撞不同的次数才能消灭
- 立方体消灭之后,要播放动画
- 玩家拥有血量和积分
这个游戏大致就有这些功能。
在ECS系统中,我们没有像传统的面向对象方法那样,为游戏中每一个实体定义一个类。比如,对于这里的玩家(Player)定义一个类,然后为绿色的立方体(GreenCube),红色的立方体(RedCube),橙色的立方体(OrangeCube)和紫色的立方体(PurpleCube)都定义一个类。对于这样的小游戏来说,你可以这么做,但是对于大一点的游戏来说,里面的实体有非常的多,如果每一个都定义一个类的话,那么系统将难以维护。所以,在ECS系统中,它将“多使用组合,少使用继承”的概念发挥到极致。
组件
在系统中,并没有为每一个实体都定义一个类,而是为构成这些实体的基本单元,也就是前面两篇博文中讲述的Component(组件),一个一个的定义。下面是我游戏中,需要用到的所有的组件类型:
// File: Component.h //------------------------------------------------------------------ // declaration : Copyright (c), by XJ , 2014 . All right reserved . // brief : This file will define the Component base class of the // Entity-Component-System. // author : XJ // date : 2014 / 6 / 8 // version : 1.0 //------------------------------------------------------------------- #pragma once #include<cocos2d.h> using namespace cocos2d ; namespace ShapeWar { #define COMPONENT_NONE 0x0 class Component { public: Component(){} virtual ~Component(){} }; /** * Define the Render Component */ #define COMPONENT_RENDER (1 << 1) class RenderComponent: public Component { public: RenderComponent(){} ~RenderComponent() { sprite->removeFromParentAndCleanup(true); delete sprite ; } public: CCSprite* sprite ; }; /** * Define the Position Component */ #define COMPONENT_POSITION (1 << 2 ) class PositionComponent: public Component { public: PositionComponent(){} ~PositionComponent(){} public: float x ; float y ; }; /** * Define the Velocity Component */ #define COMPONENT_VELOCITY (1 << 3) class VelocityComponent: public Component { public: VelocityComponent(){} ~VelocityComponent(){} public: float vx ; float vy ; }; /** * Define the Health Component */ #define COMPONENT_HEALTH (1 << 4) class HealthComponent: public Component { public: HealthComponent(){} ~HealthComponent(){} public: unsigned int health ; }; /** * Define the Collidable Component * brief : Use the AABB's Min-Max representation */ #define COMPONENT_COLLID (1 << 5) class CollidableComponent:public Component { public: CollidableComponent(){} ~CollidableComponent(){} public: float min_x ; float min_y ; float max_x ; float max_y ; }; /** * Define the EntityType component * brief : This component will indicate which type the entity is. */ #define COMPONENT_ENTITY_TYPE (1 << 6) class EntityTypeComponent: public Component { public: EntityTypeComponent(){} ~EntityTypeComponent(){} public: static const unsigned int RED_CUBE = (1 << 1) ; static const unsigned int PURPLE_CUBE = (1 << 2) ; static const unsigned int ORANGE_CUBE = (1 << 3) ; static const unsigned int GREEN_CUBE = (1 << 4) ; static const unsigned int SPHERE_BALL = (1 << 5) ; static const unsigned int PLAYER = (1 << 6) ; public: unsigned int type ; }; /** * Define the AnimateComponent */ #define COMPONENT_ANIMATE (1 << 7) class AnimateComponent:public Component { public: AnimateComponent(){} ~AnimateComponent(){} public: cocos2d::CCAnimate* animate ; unsigned frames ; }; };
从上面的代码中,大家可以看到,我首先定义了一个基类Component,然后让所有的组件都继承于这个基类。这里,我并没有用到继承,读者可以发现Component中什么内容也没有。 我将其他组件继承于Component的组要原因是能够将他们统一的进行处理,仅此而已。
在定义完了基类之后,分别定义了如下的组件类型:
- RenderComponent, 用于支持渲染
- PositionComponent, 用于定义位置属性
- VelocityComponent,用于定义速度属性
- HealthComponent,用于定义健康属性
- CollidableComponent,用于定义AABB碰撞检测盒
- EntityTypeComponent,用于定义实体类型
- AnimateComponent, 用于定义动画渲染属性
读者可能发现,在每一个组件上方,我都为它定义了一个标示符,如#define COMPONENT_RENDER (1 << 1)。这是因为,我们需要知道一个实体中到底有哪些组件,所以,我们为每一个组件定义一个标示符,然后就可以通过判断这个标示符,来知道,一个实体是否拥有指定的组件了。我们将在后面看到它的用处。
实体
如果读者,你仔细的阅读了我前面介绍的几篇文章,那么你就会知道,实体实际上就是一个ID值而已,所以,我并没有专门为这个概念定义什么,它在我开发的游戏中,仅仅是一个下标值而已。但是,我们需要知道,游戏中那么多的实体,需要进行统一的管理。所以为此,我创建了如下的一个类,用来对游戏中所有的实体进行管理。
//File:EntityManager //------------------------------------------------------------------ // declaration : Copyright (c), by XJ , 2014 . All right reserved . // brief : This file will define the Entity of the Entity-Componet- // System and the entity manager. // author : XJ // date : 2014 / 6 / 8 // version : 1.0 //------------------------------------------------------------------- #pragma once #include<vector> #include"Component.h" using namespace std ; namespace ShapeWar { /** * Define the EntityManager */ class EntityManager { private: EntityManager(); ~EntityManager(); /** Singleton getter*/ public: static EntityManager* getEntityManager(); /** Core method */ public: /** * Create an empty entity */ _int64 createEntity() ; /** * Remove an entity */ void removeEntity(_int64 entity); /** * Register component * brief : This method will make the entity manager to alloc the memory to store * the registed componet.If you want to use one componet in the ECS , you * must registed it at the start time. */ void registComponents(_int64 component_size); /** * Add an component to the entity */ void addComponent(Component* component, _int64 component_type, _int64 entity); /** * Remove an component of the entity */ void removeComponent(_int64 component_type, _int64 entity); /** * Get component list */ std::vector<Component*>* getComponentList(_int64 component_type) const ; /** * Get the specificed component of the entity */ Component* getComponent(_int64 component_type, _int64 entity); /** * Get entity flag */ _int64 getEntityFlag(_int64 entity) const ; /** * Set entity flag */ void setEntityFlag(_int64 entity, _int64 entity_type); /** * Get the entity size */ unsigned int getEntitySize() const ; /** * Define the Component_List */ typedef std::vector<Component*> Component_List; private: /** * Destroy all the component */ void _destroy(); private: std::vector<_int64> m_EntityFlagArray ; //Contain the Entity flag <pre name="code" class="cpp"> std::vector<Component_List> m_ComponentContainer ; //Contain all the entity
};};
正如读者看到的那样,这个类是一个单例类,里面提供了很多的方法。要理解这个类,我们先来看看组件是如何在这个类里面进行保存的。
在这个类中,我定义了一个这样的成员:
std::vector<Component_List> m_ComponentContainer ; //Contain all the entity
而Component_List定义为如下:
/** * Define the Component_List */ typedef std::vector<Component*> Component_List;
也就是说,这个ComponentContainer,包含了所有的在游戏中使用的组件实例。同一种组件实例,放在同一个Component_List中,然后不同的Component_List,放在Component_Container中。如果读者对这个不是很清楚的话,可以看下面的图片:
从上图中可以看出,这是一个二维的空间盒子,纵向表示了一个组件类型中所有的组件实例,横向表示了一个实体拥有哪些组件。所以,这里的实体,也就是这里的容器中的下标了。
好了,在明白了组件是如何保存了的之后,我们还需要了解在EntityManager中定义的这个数组是什么意思:
std::vector<_int64> m_EntityFlagArray ; //Contain the Entity flag
这个数组,保存了相对应的实体中组件的标示符。还记得我们在组件那一节讲述的组件表示符吗?通过这个数组,我们保存了每个实体对应的组件中有哪些组件。比如说,在这个数组下标为1的单元中,也就是实体1中,有如下的组件标示符保存:
COMPONENT_RENDER | COMPONENT_POSITION | COMPONENT_VELOCITY
那么就意味着,这个实体是由RenderComponent,和PositionComponent,VelocityComponent组合而成的。
好了,在明白了这个数组的功能之后,我们来看看上面管理器中各个函数的作用吧。
_int64 CreateEntity
这个函数用来创建一个空的实体,并且返回实体的下标。用户可以通过这个方法来创建一个实体
void removeEntity(_int64 entity)
这个函数,根据传递进来的实体下标,将实体从容器中移除,并且释放相关的资源
void registComponent(int num)
这个函数,用来根据参数,开辟相应的组件类型空间。在开始的时候,我们并不知道有多少个组件需要使用,所以让用户自行决定需要使用多少个组件。
void addComponent(Component* component, _int64 component_type, _int64 entity)
这个函数,根据传进来的组件,还有组件类型,以及实体下标,将组件加入到相对应的位置去。
void removeComponent(_int64 component_type, _int64 entity);
这个函数,用来将制定类型的组件,从实体中移除。
由于篇幅限制,这里不再一一的讲述。上面的代码能够很好的自我解释出每一个函数的功能。
这里有个问题需要注意,读者可能想知道,我是如何通过组建标示符,来找到那个组建的容器的???并且实体只是定义了横向的坐标,而纵向的坐标是如何获取的了?
这个还要讲解下我定义的容器的组织方式。
对于不同的组件,我分别定义了标示符,而标示符中都有不同的位置标示,如COMPONENT_RENDER为 10,这个标示符中1在第1位(从0计算),那么我们将这个组件的纵向位置定义为1 - 1 = 0 ,也就是0号下标的组件容器中。所以,这就是为什么我要定义不同的组件标示符。为了能够从64位的标示符中获取‘1’在哪一位上,我在前面的博客中算法设计:如何从64位数中获取哪一位数为1采用分治算法,设计了这个方法来获取位数。
好了,通过上面的描述,读者应该明白我是以怎么样的方式来维护游戏中所有的实体的了!!!
系统
在实现了上面的组件,实体之后,接下来就应该实现系统了。我这里实现系统的方式,是根据这篇博客中提出的方法来实现的。
首先,抽象一个系统的类,如下所示:
/** * Define the base system class. All system will inherit from this base class. */ class System { public: System(int _priority); virtual ~System(); public: virtual void enter() = 0 ; virtual void excute(float dt) = 0; virtual void exit() = 0 ; public: int priority ; };
在这个抽象的系统中,我定义了一个优先级,这样,我们就可以定义哪一些系统需要在另外一些系统之前进行运行。有了系统之后,我们就需要一个管理的方式,所以,在定义了一个系统管理器,如下所示:
/** * Define the system manager */ class SystemManager { private: SystemManager(); ~SystemManager(); /** Singleton getter*/ public: static SystemManager* getSystemManager() ; /** Core method*/ public: /** * Add one system to the system list */ void addSystem(System * system); /** * Update all the system */ void update(float dt); /** * Pause all the system */ void pause(); /** * Resume all the system */ void resume(); private: /** * Destroy all the systems */ void _destroy(); private: std::vector<System*> system_list ; bool bPaused ; };
这个类同样也是单例的,用户可以通过调用addSystem来添加系统到系统管理器中。系统管理器,会在每一帧,调用update方法,update方法如下所示:
void SystemManager::update(float dt) { if(bPaused == true) return ; //Excute all the system for(int i = 0 ; i < system_list.size() ; i ++) { system_list[i]->excute(dt); }// end for }// end for update
很简单,它调用已经根据优先级排好序的系统中的excute方法,来执行每一个系统的任务。
在我的这个简单的游戏中,我定义了如下的几个系统,根据优先级从低到进行排序:
- RenderSystem,负责进行渲染
- MovementSystem, 负责进行实体的移动
- HealthSystem,负责判断哪些实体已死亡
- CreatorSystem,负责游戏中立方体的创建规则
- InputSystem, 负责处理键盘输入
- CollidDetectionSystem,负责进行碰撞检测
- BoundaryCheckSystem,负责进行边界检查,当立方体和球体出了边界之后,进行相应的操作
下面我们来分别看看这些系统的实现过程:
RenderSystem
#include"RenderSystem.h" #include"EntityMananger.h" using namespace ShapeWar ; RenderSystem::RenderSystem(int _priority, CCNode* _scene) :System(_priority), scene(_scene) { } RenderSystem::~RenderSystem() { } void RenderSystem::enter() { }// ed for enter void RenderSystem::excute(float dt) { unsigned int size = EntityManager::getEntityManager()->getEntitySize(); for(unsigned int i = 0 ; i < size ; i ++) { _int64 flag = EntityManager::getEntityManager()->getEntityFlag(i); if((flag & (COMPONENT_RENDER | COMPONENT_POSITION)) == (COMPONENT_RENDER | COMPONENT_POSITION)) { RenderComponent* pRender = (RenderComponent*)EntityManager::getEntityManager()->getComponent(COMPONENT_RENDER,i); PositionComponent* pPos = (PositionComponent*)EntityManager::getEntityManager()->getComponent(COMPONENT_POSITION,i); if(pRender->sprite->getParent() == NULL) { EntityTypeComponent* pType = (EntityTypeComponent*)EntityManager::getEntityManager()->getComponent(COMPONENT_ENTITY_TYPE,i); if(pType->type != EntityTypeComponent::PLAYER) { pRender->sprite->runAction(CCRepeatForever::create(CCRotateBy::create(1.0/60, 5))); scene->addChild(pRender->sprite); }// end for PLAYER else scene->addChild(pRender->sprite, 10); } pRender->sprite->setPosition(ccp(pPos->x, pPos->y)); } }// end for sprite }// end for excute void RenderSystem::exit() { unsigned int size = EntityManager::getEntityManager()->getEntitySize(); for(unsigned int i = 0 ; i < size ; i ++) { RenderComponent* pRender = (RenderComponent*)EntityManager::getEntityManager()->getComponent(COMPONENT_RENDER,i); pRender->sprite->stopAllActions(); pRender->sprite->removeFromParentAndCleanup(true); }// end for }// end for exit
MovementSystem
#include"MovementSystem.h" #include"EntityMananger.h" using namespace ShapeWar ; MovementSystem::MovementSystem(int _priority) :System(_priority) { } MovementSystem::~MovementSystem() { } void MovementSystem::enter() { }// end for enter void MovementSystem::excute(float dt) { unsigned int size = EntityManager::getEntityManager()->getEntitySize(); for(unsigned int i = 0 ; i < size ; i ++) { _int64 flag = EntityManager::getEntityManager()->getEntityFlag(i); if((flag & (COMPONENT_POSITION | COMPONENT_VELOCITY)) == (COMPONENT_POSITION | COMPONENT_VELOCITY)) { PositionComponent* pPos = (PositionComponent*)EntityManager::getEntityManager()->getComponent(COMPONENT_POSITION, i); VelocityComponent* pVelocity = (VelocityComponent*)EntityManager::getEntityManager()->getComponent(COMPONENT_VELOCITY, i); pPos->x += (1.0 / 60) * pVelocity->vx ; pPos->y += (1.0 / 60) * pVelocity->vy ; } }// end for }// end for excute void MovementSystem::exit() { }// end for exit
HealthSystem
#include"HealthSystem.h" #include"EntityMananger.h" #include"GameInfo.h" using namespace ShapeWar ; HealthSystem::HealthSystem(int priority) :System(priority) { } HealthSystem::~HealthSystem() { } void HealthSystem::enter() { }// end for enter void HealthSystem::excute(float dt) { //Get all the HealthComponent list EntityManager::Component_List* pHealth = EntityManager::getEntityManager()->getComponentList(COMPONENT_HEALTH); for(unsigned int entity = 0 ; entity < EntityManager::getEntityManager()->getEntitySize() ;) { HealthComponent* health = (HealthComponent*)(*pHealth)[entity] ; if(health != NULL) { EntityTypeComponent* pType = (EntityTypeComponent*)EntityManager::getEntityManager()->getComponent(COMPONENT_ENTITY_TYPE, entity); if(pType->type == EntityTypeComponent::PLAYER) { GameInfo::getGameInfo()->CUR_HEALTH_PLAYER = health->health ; } if(health->health == 0) { if((EntityManager::getEntityManager()->getEntityFlag(entity) & COMPONENT_ANIMATE) == 0) { switch(pType->type) { case EntityTypeComponent::GREEN_CUBE: case EntityTypeComponent::RED_CUBE: GameInfo::getGameInfo()->CUR_SCORE += 1 ; break ; case EntityTypeComponent::ORANGE_CUBE: GameInfo::getGameInfo()->CUR_SCORE += 2 ; break ; case EntityTypeComponent::PURPLE_CUBE: GameInfo::getGameInfo()->CUR_SCORE += 3 ; break ; }// end switch EntityManager::getEntityManager()->removeEntity(entity); } else entity ++ ; }// end if else entity ++ ; }// end if else entity ++ ; }// end for }// end for excute void HealthSystem::exit() { }// end for exit
CreatorSystem
#include"CreatorSystem.h" #include"EntityCreator.h" using namespace ShapeWar ; CreatorSystem::CreatorSystem(int _priority) :System(_priority), frames(0) { } CreatorSystem::~CreatorSystem() { } void CreatorSystem::enter() { }// end for enter void CreatorSystem::excute(float dt) { frames ++ ; static int delta = 0 ; delta = frames / 1800 ; if(delta >= 30) delta = 30 ; if(frames % (60 - delta ) == 0) { int value = rand()%100 ; float vy = -60 - (frames / 300.0) * 10 ; if(0 <= value&& value < 40) { EntityCreator::createGreenCube(0, vy); } else if(40 <= value&& value < 80) { EntityCreator::createRedCube(0, vy); } else if(80 <= value && value < 90) { EntityCreator::createOrangeCube(0, 0.6*vy); } else if(90 <= value && value<100) { EntityCreator::createPurpleCube(0,0.4*vy); } }//end if }// end for excute void CreatorSystem::exit() { }// end for exit
InputSystem
#include "InputSystem.h" #include "EntityMananger.h" #include "EntityCreator.h" #include "AudioSystem.h" using namespace ShapeWar ; InputSystem::InputSystem(int _priority) :System(_priority) { } InputSystem::~InputSystem() { } void InputSystem::enter() { }// end for enter void InputSystem::excute(float dt) { //Get the Component list EntityManager::Component_List* pPos = EntityManager::getEntityManager()->getComponentList(COMPONENT_POSITION); EntityManager::Component_List* pType = EntityManager::getEntityManager()->getComponentList(COMPONENT_ENTITY_TYPE); //Find the player and the un-shooted ball unsigned int size = EntityManager::getEntityManager()->getEntitySize(); int player = -1 , ball = -1 ; for(unsigned int i = 0 ; i < size ; i ++) { unsigned int type = ((EntityTypeComponent*)(*pType)[i])->type ; if(type == EntityTypeComponent::PLAYER) { player = i ; }// end if if(type == EntityTypeComponent::SPHERE_BALL) { _int64 flag = EntityManager::getEntityManager()->getEntityFlag(i); if((flag & COMPONENT_VELOCITY) == 0) { ball = i ; } // end if }// end if if(player != -1 && ball != -1) break ; }// end for PositionComponent* pPlayer_Pos = NULL ; PositionComponent* pBall_Pos = NULL ; if(player != -1) pPlayer_Pos = (PositionComponent*)(*pPos)[player] ; if(ball != -1) pBall_Pos = (PositionComponent*)(*pPos)[ball] ; if(GetKeyState(VK_RIGHT) & 0x8000) { if(pPlayer_Pos != NULL) { pPlayer_Pos->x += 5 ; if(pPlayer_Pos->x >= 320 - 22) pPlayer_Pos->x = 320 - 22 ; if(pBall_Pos != NULL) pBall_Pos->x = pPlayer_Pos->x ; } }else if(GetKeyState(VK_LEFT)&0x8000) { if(pPlayer_Pos != NULL) { pPlayer_Pos->x -= 5 ; if(pPlayer_Pos->x <= 22) pPlayer_Pos->x = 22 ; if(pBall_Pos != NULL) pBall_Pos->x = pPlayer_Pos->x ; } } static int nFrame = 0 ; if((GetKeyState(VK_SPACE)& 0x8000) && (nFrame >= 15)) { VelocityComponent* pVelocity = new VelocityComponent(); pVelocity->vx = 0 ; pVelocity->vy = 600 ; EntityManager::getEntityManager()->addComponent(pVelocity, COMPONENT_VELOCITY, ball); //Create another ball EntityCreator::createSphereBall(pPlayer_Pos->x, pPlayer_Pos->y); //Player Effect AudioSystem::sharedAudioSystem()->playSound("Shoot.wav"); nFrame = 0 ; } nFrame ++ ; }// end for excute void InputSystem::exit() { }// end for exit
CollidDetectionSystem
#include"CollidDetectionSystem.h" #include"EntityMananger.h" #include"AudioSystem.h" using namespace ShapeWar ; CollidDetectionSystem::CollidDetectionSystem(int _priority) :System(_priority) { } CollidDetectionSystem::~CollidDetectionSystem() { } void CollidDetectionSystem::enter() { }// end for enter void CollidDetectionSystem::excute(float dt) { //Get all PositionComponent list EntityManager::Component_List* pPos = EntityManager::getEntityManager()->getComponentList(COMPONENT_POSITION); //Get all the CollidableComponent list EntityManager::Component_List* pCollid = EntityManager::getEntityManager()->getComponentList(COMPONENT_COLLID); //Get all the EntityTypeComponent list EntityManager::Component_List* pType = EntityManager::getEntityManager()->getComponentList(COMPONENT_ENTITY_TYPE); //Get all the HealthComponent list EntityManager::Component_List* pHealth = EntityManager::getEntityManager()->getComponentList(COMPONENT_HEALTH); unsigned int size = EntityManager::getEntityManager()->getEntitySize(); //Find all sphere ball std::vector<unsigned int> index_array ; for(unsigned int i = 0 ; i < size ; i ++) { if(((EntityTypeComponent*)(*pType)[i])->type == EntityTypeComponent::SPHERE_BALL) { if((EntityManager::getEntityManager()->getEntityFlag(i) & COMPONENT_VELOCITY) == COMPONENT_VELOCITY) { index_array.push_back(i); }// end if }// end if }// end for for(unsigned int i = 0 ; i < index_array.size() ; i ++) { CollidableComponent* collidAreaA = ((CollidableComponent*)((*pCollid)[index_array[i]])) ; PositionComponent* posA = ((PositionComponent*)((*pPos)[index_array[i]])) ; collidAreaA->min_x = posA->x - 16 ; collidAreaA->min_y = posA->y - 16 ; collidAreaA->max_x = posA->x + 16 ; collidAreaA->max_y = posA->y + 16 ; size = EntityManager::getEntityManager()->getEntitySize(); for(unsigned int j = 0 ; j < size ; j ++) { if((EntityManager::getEntityManager()->getEntityFlag(j) & COMPONENT_COLLID) == COMPONENT_COLLID && ((EntityTypeComponent*)(*pType)[j])->type != EntityTypeComponent::SPHERE_BALL) { CollidableComponent* collidAreaB = ((CollidableComponent*)((*pCollid)[j])) ; PositionComponent* posB = ((PositionComponent*)((*pPos)[j])) ; collidAreaB->min_x = posB->x - 16 ; collidAreaB->min_y = posB->y - 16 ; collidAreaB->max_x = posB->x + 16 ; collidAreaB->max_y = posB->y + 16 ; if(collidAreaA->min_x > collidAreaB->max_x ||collidAreaA->max_x < collidAreaB->min_x) continue ; if(collidAreaA->min_y > collidAreaB->max_y || collidAreaA->max_y < collidAreaB->min_y) continue ; HealthComponent* cube = (HealthComponent*)(*pHealth)[j] ; cube->health -- ; if(cube->health == 0) { AnimateComponent* pAnimate = new AnimateComponent(); pAnimate->animate = new CCAnimate(); CCAnimation* pAnimation = CCAnimation::create(); for(int i = 0 ; i < 10 ; i ++) { char buffer[32] ; sprintf(buffer,"Explosion000%d.png",i); pAnimation->addSpriteFrameWithFileName(buffer); }// end for pAnimation->setDelayPerUnit(1.0/10); pAnimate->animate->initWithAnimation(pAnimation); pAnimate->frames = 60 ; //Add the Animate Component to the entity EntityManager::getEntityManager()->addComponent(pAnimate, COMPONENT_ANIMATE, j); //Remove the CollidDetection Component EntityManager::getEntityManager()->removeComponent(COMPONENT_COLLID, j); //Remove the Velocity Component EntityManager::getEntityManager()->removeComponent(COMPONENT_VELOCITY, j); }// end if HealthComponent* ball = (HealthComponent*)(*pHealth)[index_array[i]] ; ball->health -- ; //Play hurt effect AudioSystem::sharedAudioSystem()->playSound("Hurt.wav"); break ; }// end if }// end for cube }// end for sphere ball }// end for excute void CollidDetectionSystem::exit() { }// end for exit
BoundaryCheckSystem
#include"BoundaryCheckSystem.h" #include"EntityMananger.h" using namespace ShapeWar ; BoundaryCheckSystem::BoundaryCheckSystem(int priority) :System(priority) { } BoundaryCheckSystem::~BoundaryCheckSystem() { } void BoundaryCheckSystem::enter() { }// end for enter void BoundaryCheckSystem::excute(float dt) { //Get all PositionComponent list EntityManager::Component_List* pPos = EntityManager::getEntityManager()->getComponentList(COMPONENT_POSITION); //Get all the EntityTypeComponent list EntityManager::Component_List* pType = EntityManager::getEntityManager()->getComponentList(COMPONENT_ENTITY_TYPE); unsigned int size = EntityManager::getEntityManager()->getEntitySize(); //Find the Player's health Component unsigned int player_entity = -1 ; for(int i = 0 ; i < size ; i ++) { if(((EntityTypeComponent*)(*pType)[i])->type == EntityTypeComponent::PLAYER) { player_entity = i ; break ; } }// end for HealthComponent * health = (HealthComponent*)EntityManager::getEntityManager()->getComponent(COMPONENT_HEALTH, player_entity); //Check if the entity is out of the screen for(unsigned int i = 0 ; i < size ; ) { if(((EntityTypeComponent*)(*pType)[i])->type == EntityTypeComponent::SPHERE_BALL) { if(((PositionComponent*)(*pPos)[i])->y > 480) { EntityManager::getEntityManager()->removeEntity(i); size -= 1 ; continue ; } }// end if for sphere ball else { if(((PositionComponent*)(*pPos)[i])->y < 0) { EntityManager::getEntityManager()->removeEntity(i); size -= 1 ; health->health-- ; continue ; } } i ++ ; }// end for }// end for excute void BoundaryCheckSystem::exit() { }// end for exit
系统内部是如何工作的,不是本文章讨论的范畴。这篇文章旨在告诉读者,我们可以通过ECS系统,实现更加弹性的设计。通过使用组合的方法,大大降低系统的耦合性。同时,这里将数据和处理过程,通过组建和系统的方法实现了分离。通过这样的系统,我们很容易的能够实现网络游戏,因为只需要对组件数据进行单独的传输即可,并且很容易的实现诸如关卡保存,这样的内容。
但是,任何事情都是双面的,在带来这些好处的同时,在另外的方面也会带来限制。
系统缺点
通过上面的描述,我们大概可以明确这样的系统有如下的缺点:
- 内存利用较低。我们在容器中为每一个实体都开辟了同样大的空间,如果某个实体并不具有那样的组件的时候,那个空间依然为它保留着,这浪费了大量的空间
- 同一个实体,没有办法拥有同一个组件的两份实例。也就说,对于像动画这样的组件,一个实体,可能不只有一个动画属性。它可能需要在死亡时,同时播放两种动画,那么这个系统就没有办法完成这样的工作。
- 最重要的一个缺点就是性能问题。读者可能发现,系统和实体的交互方式,完全是系统主动的轮询,来进行系统的处理。我们知道,高效的设计方法,应该是让实体在有需要的时候,调用系统来进行工作。如果系统持续的运行,在很多情况下,系统并没有做什么有效的工作。所以,应该将这种主动轮询的方式改成由事件驱动的可能更好一点。但是,博主暂时没有想到如何设计这样的系统,可能在后面的实践中,掌握这样的设计方法的时候,再来向大家讲述。
好了,ECS架构实践的第一篇博客就到此结束了。
如果您有什么不明白的地方,或者发现了文中设计上的缺陷,欢迎大家在评论中指出。毕竟,旁观者清,当局者迷。希望能够和大家互相的学习!互相进步!
这个游戏的源代码和程序以及上传至CSDN,感兴趣的同学可以自行下载来阅读和试玩,不要忘了在评论中给出你获得的最高分哦,大家比比看谁的反应是最好的哦哦!!!
ShapeWar_exe.zip
如何在cocos2d-x中使用ECS(实体-组件-系统)架构方法开发一个游戏?