引言
在我的博客中,我曾经翻译了几篇关于ECS的文章。这些文章都是来自于Game Development网站。如果你对这个架构方式还不是很了解的话,欢迎阅读理解 组件-实体-系统和实现 组件-实体-系统。
我发现这个架构方式,是在浏览GameDev上的文章的时候了解到的。很久以前,就知道了有这么个架构方法,只是一直没有机会自己实践下。这一次,我就抽空,根据网上对ECS系统的讨论,采用了一种实现方法,来实现一个。
我很喜欢做游戏,所以同样的,还是用游戏实例来实践这个架构方法。我将会采用cocos2d-x来作为游戏开发的主要工具,这是一款十分强大的跨平台游戏引擎,感兴趣的读者,可以自行搜索了解。
ShapeWar
我一直觉得,作为游戏程序员,能够自己独立的绘制游戏中的图片资源是一件非常好玩的事情。所以,没有美术功底的我,就选择了一种复古风格的艺术——像素艺术来学习。经过一段时间的学习,发现做像素画还是很有趣的,所以我就将我以前做的简单的像素图片,来融合成现在的这个游戏实例——ShapeWar 。
这个游戏很简单,玩家通过键盘上的左右键来移动发射器,通过按下space键,来进行攻击,将所有掉落下来的立方体全都打掉。如果有立方体遗漏掉,那么将会丢掉一颗血,直到玩家死亡为止。这个游戏,开始的时候,可能会非常容易,但是,立方体下落的速度是逐渐增加的,到了后面,如果玩家还能够坚持住的话,那非常了不起!!!
好了,游戏规则很简单,来看看游戏的截图吧!
好了,这个游戏很简单,有兴趣的同学,可以到这里来下载,试玩一下,并且在留言中,告诉我,你最高得了多少分哦!!!
架构设计
从上面的截图,大家也能够明白,游戏只有两个场景,分别是开始场景,和游戏进行场景。需要完成的功能如下:
- 能够产生立方体,控制立方体产生
- 能够控制发射器,发射出球体
- 能够检测出球体和立方体之间的碰撞
- 对不同的立方体,需要碰撞不同的次数才能消灭
- 立方体消灭之后,要播放动画
- 玩家拥有血量和积分
这个游戏大致就有这些功能。
在ECS系统中,我们没有像传统的面向对象方法那样,为游戏中每一个实体定义一个类。比如,对于这里的玩家(Player)定义一个类,然后为绿色的立方体(GreenCube),红色的立方体(RedCube),橙色的立方体(OrangeCube)和紫色的立方体(PurpleCube)都定义一个类。对于这样的小游戏来说,你可以这么做,但是对于大一点的游戏来说,里面的实体有非常的多,如果每一个都定义一个类的话,那么系统将难以维护。所以,在ECS系统中,它将“多使用组合,少使用继承”的概念发挥到极致。
组件
在系统中,并没有为每一个实体都定义一个类,而是为构成这些实体的基本单元,也就是前面两篇博文中讲述的Component(组件),一个一个的定义。下面是我游戏中,需要用到的所有的组件类型:
- //File:Component.h
- //------------------------------------------------------------------
- //declaration:Copyright(c),byXJ,2014.Allrightreserved.
- //brief:ThisfilewilldefinetheComponentbaseclassofthe
- //Entity-Component-System.
- //author:XJ
- //date:2014/6/8
- //version:1.0
- //-------------------------------------------------------------------
- #pragmaonce
- #include<cocos2d.h>
- usingnamespacecocos2d;
- namespaceShapeWar
- {
- #defineCOMPONENT_NONE0x0
- classComponent
- {
- public:
- Component(){}
- virtual~Component(){}
- };
- /**
- *DefinetheRenderComponent
- */
- #defineCOMPONENT_RENDER(1<<1)
- classRenderComponent:publicComponent
- {
- public:
- RenderComponent(){}
- ~RenderComponent()
- {
- sprite->removeFromParentAndCleanup(true);
- deletesprite;
- }
- public:
- CCSprite*sprite;
- };
- /**
- *DefinethePositionComponent
- */
- #defineCOMPONENT_POSITION(1<<2)
- classPositionComponent:publicComponent
- {
- public:
- PositionComponent(){}
- ~PositionComponent(){}
- public:
- floatx;
- floaty;
- };
- /**
- *DefinetheVelocityComponent
- */
- #defineCOMPONENT_VELOCITY(1<<3)
- classVelocityComponent:publicComponent
- {
- public:
- VelocityComponent(){}
- ~VelocityComponent(){}
- public:
- floatvx;
- floatvy;
- };
- /**
- *DefinetheHealthComponent
- */
- #defineCOMPONENT_HEALTH(1<<4)
- classHealthComponent:publicComponent
- {
- public:
- HealthComponent(){}
- ~HealthComponent(){}
- public:
- unsignedinthealth;
- };
- /**
- *DefinetheCollidableComponent
- *brief:UsetheAABB'sMin-Maxrepresentation
- */
- #defineCOMPONENT_COLLID(1<<5)
- classCollidableComponent:publicComponent
- {
- public:
- CollidableComponent(){}
- ~CollidableComponent(){}
- public:
- floatmin_x;
- floatmin_y;
- floatmax_x;
- floatmax_y;
- };
- /**
- *DefinetheEntityTypecomponent
- *brief:Thiscomponentwillindicatewhichtypetheentityis.
- */
- #defineCOMPONENT_ENTITY_TYPE(1<<6)
- classEntityTypeComponent:publicComponent
- {
- public:
- EntityTypeComponent(){}
- ~EntityTypeComponent(){}
- public:
- staticconstunsignedintRED_CUBE=(1<<1);
- staticconstunsignedintPURPLE_CUBE=(1<<2);
- staticconstunsignedintORANGE_CUBE=(1<<3);
- staticconstunsignedintGREEN_CUBE=(1<<4);
- staticconstunsignedintSPHERE_BALL=(1<<5);
- staticconstunsignedintPLAYER=(1<<6);
- public:
- unsignedinttype;
- };
- /**
- *DefinetheAnimateComponent
- */
- #defineCOMPONENT_ANIMATE(1<<7)
- classAnimateComponent:publicComponent
- {
- public:
- AnimateComponent(){}
- ~AnimateComponent(){}
- public:
- cocos2d::CCAnimate*animate;
- unsignedframes;
- };
- };
在定义完了基类之后,分别定义了如下的组件类型:
- RenderComponent, 用于支持渲染
- PositionComponent, 用于定义位置属性
- VelocityComponent,用于定义速度属性
- HealthComponent,用于定义健康属性
- CollidableComponent,用于定义AABB碰撞检测盒
- EntityTypeComponent,用于定义实体类型
- AnimateComponent, 用于定义动画渲染属性
读者可能发现,在每一个组件上方,我都为它定义了一个标示符,如#define COMPONENT_RENDER (1 << 1)。这是因为,我们需要知道一个实体中到底有哪些组件,所以,我们为每一个组件定义一个标示符,然后就可以通过判断这个标示符,来知道,一个实体是否拥有指定的组件了。我们将在后面看到它的用处。
实体
如果读者,你仔细的阅读了我前面介绍的几篇文章,那么你就会知道,实体实际上就是一个ID值而已,所以,我并没有专门为这个概念定义什么,它在我开发的游戏中,仅仅是一个下标值而已。但是,我们需要知道,游戏中那么多的实体,需要进行统一的管理。所以为此,我创建了如下的一个类,用来对游戏中所有的实体进行管理。
- //File:EntityManager
- //------------------------------------------------------------------
- //declaration:Copyright(c),2014.Allrightreserved.
- //brief:ThisfilewilldefinetheEntityoftheEntity-Componet-
- //Systemandtheentitymanager.
- //author:XJ
- //date:2014/6/8
- //version:1.0
- //-------------------------------------------------------------------
- #pragmaonce
- #include<vector>
- #include"Component.h"
- usingnamespacestd;
- namespaceShapeWar
- {
- /**
- *DefinetheEntityManager
- */
- classEntityManager
- {
- private:
- EntityManager();
- ~EntityManager();
- /**Singletongetter*/
- public:
- staticEntityManager*getEntityManager();
- /**Coremethod*/
- public:
- /**
- *Createanemptyentity
- */
- _int64createEntity();
- /**
- *Removeanentity
- */
- voidremoveEntity(_int64entity);
- /**
- *Registercomponent
- *brief:Thismethodwillmaketheentitymanagertoallocthememorytostore
- *theregistedcomponet.IfyouwanttouSEOnecomponetintheECS,you
- *mustregisteditatthestarttime.
- */
- voidregistComponents(_int64component_size);
- /**
- *Addancomponenttotheentity
- */
- voidaddComponent(Component*component,_int64component_type,_int64entity);
- /**
- *Removeancomponentoftheentity
- */
- voidremoveComponent(_int64component_type,_int64entity);
- /**
- *Getcomponentlist
- */
- std::vector<Component*>*getComponentList(_int64component_type)const;
- /**
- *Getthespecificedcomponentoftheentity
- */
- Component*getComponent(_int64component_type,_int64entity);
- /**
- *Getentityflag
- */
- _int64getEntityFlag(_int64entity)const;
- /**
- *Setentityflag
- */
- voidsetEntityFlag(_int64entity,_int64entity_type);
- /**
- *Gettheentitysize
- */
- unsignedintgetEntitySize()const;
- /**
- *DefinetheComponent_List
- */
- typedefstd::vector<Component*>Component_List;
- private:
- /**
- *Destroyallthecomponent
- */
- void_destroy();
- private:
- std::vector<_int64>m_EntityFlagArray;//ContaintheEntityflag
- <prename="code"class="cpp">std::vector<Component_List>m_ComponentContainer;//Containalltheentity
};}; 正如读者看到的那样,这个类是一个单例类,里面提供了很多的方法。要理解这个类,我们先来看看组件是如何在这个类里面进行保存的。
在这个类中,我定义了一个这样的成员:
- std::vector<Component_List>m_ComponentContainer;//Containalltheentity
而Component_List定义为如下:
- /**
- *DefinetheComponent_List
- */
- typedefstd::vector<Component*>Component_List;
从上图中可以看出,这是一个二维的空间盒子,纵向表示了一个组件类型中所有的组件实例,横向表示了一个实体拥有哪些组件。所以,这里的实体,也就是这里的容器中的下标了。
好了,在明白了组件是如何保存了的之后,我们还需要了解在EntityManager中定义的这个数组是什么意思:
- std::vector<_int64>m_EntityFlagArray;//ContaintheEntityflag
COMPONENT_RENDER | COMPONENT_POSITION | COMPONENT_VELOCITY
那么就意味着,这个实体是由RenderComponent,和PositionComponent,VelocityComponent组合而成的。
好了,在明白了这个数组的功能之后,我们来看看上面管理器中各个函数的作用吧。
_int64 CreateEntity
这个函数用来创建一个空的实体,并且返回实体的下标。用户可以通过这个方法来创建一个实体
void removeEntity(_int64 entity)
这个函数,根据传递进来的实体下标,将实体从容器中移除,并且释放相关的资源
void registComponent(int num)
这个函数,用来根据参数,开辟相应的组件类型空间。在开始的时候,我们并不知道有多少个组件需要使用,所以让用户自行决定需要使用多少个组件。
void addComponent(Component* component,_int64 entity)
这个函数,根据传进来的组件,还有组件类型,以及实体下标,将组件加入到相对应的位置去。
void removeComponent(_int64 component_type,_int64 entity);
这个函数,用来将制定类型的组件,从实体中移除。
由于篇幅限制,这里不再一一的讲述。上面的代码能够很好的自我解释出每一个函数的功能。
这里有个问题需要注意,读者可能想知道,我是如何通过组建标示符,来找到那个组建的容器的???并且实体只是定义了横向的坐标,而纵向的坐标是如何获取的了?
这个还要讲解下我定义的容器的组织方式。
对于不同的组件,我分别定义了标示符,而标示符中都有不同的位置标示,如COMPONENT_RENDER为 10,这个标示符中1在第1位(从0计算),那么我们将这个组件的纵向位置定义为1 - 1 = 0 ,也就是0号下标的组件容器中。所以,这就是为什么我要定义不同的组件标示符。为了能够从64位的标示符中获取‘1’在哪一位上,我在前面的博客中算法设计:如何从64位数中获取哪一位数为1采用分治算法,设计了这个方法来获取位数。
好了,通过上面的描述,读者应该明白我是以怎么样的方式来维护游戏中所有的实体的了!!!
系统
在实现了上面的组件,实体之后,接下来就应该实现系统了。我这里实现系统的方式,是根据这篇博客中提出的方法来实现的。
首先,抽象一个系统的类,如下所示:
- /**
- *Definethebasesystemclass.Allsystemwillinheritfromthisbaseclass.
- */
- classSystem
- {
- public:
- System(int_priority);
- virtual~System();
- public:
- virtualvoidenter()=0;
- virtualvoidexcute(floatdt)=0;
- virtualvoidexit()=0;
- public:
- intpriority;
- };
在这个抽象的系统中,我定义了一个优先级,这样,我们就可以定义哪一些系统需要在另外一些系统之前进行运行。有了系统之后,我们就需要一个管理的方式,所以,在定义了一个系统管理器,如下所示:
- /**
- *Definethesystemmanager
- */
- classSystemManager
- {
- private:
- SystemManager();
- ~SystemManager();
- /**Singletongetter*/
- public:
- staticSystemManager*getSystemManager();
- /**Coremethod*/
- public:
- /**
- *Addonesystemtothesystemlist
- */
- voidaddSystem(System*system);
- /**
- *Updateallthesystem
- */
- voidupdate(floatdt);
- /**
- *Pauseallthesystem
- */
- voidpause();
- /**
- *Resumeallthesystem
- */
- voidresume();
- private:
- /**
- *Destroyallthesystems
- */
- void_destroy();
- private:
- std::vector<System*>system_list;
- boolbPaused;
- };
这个类同样也是单例的,用户可以通过调用addSystem来添加系统到系统管理器中。系统管理器,会在每一帧,调用update方法,update方法如下所示:
- voidSystemManager::update(floatdt)
- {
- if(bPaused==true)
- return;
- //Excuteallthesystem
- for(inti=0;i<system_list.size();i++)
- {
- system_list[i]->excute(dt);
- }//endfor
- }//endforupdate
很简单,它调用已经根据优先级排好序的系统中的excute方法,来执行每一个系统的任务。
在我的这个简单的游戏中,我定义了如下的几个系统,根据优先级从低到进行排序:
- RenderSystem,负责进行渲染
- MovementSystem, 负责进行实体的移动
- HealthSystem,负责判断哪些实体已死亡
- CreatorSystem,负责游戏中立方体的创建规则
- InputSystem, 负责处理键盘输入
- CollidDetectionSystem,负责进行碰撞检测
- BoundaryCheckSystem,负责进行边界检查,当立方体和球体出了边界之后,进行相应的操作
下面我们来分别看看这些系统的实现过程:
RenderSystem
- #include"RenderSystem.h"
- #include"EntityMananger.h"
- usingnamespaceShapeWar;
- RenderSystem::RenderSystem(int_priority,CCNode*_scene)
- :System(_priority),
- scene(_scene)
- {
- }
- RenderSystem::~RenderSystem()
- {
- }
- voidRenderSystem::enter()
- {
- }//edforenter
- voidRenderSystem::excute(floatdt)
- {
- unsignedintsize=EntityManager::getEntityManager()->getEntitySize();
- for(unsignedinti=0;i<size;i++)
- {
- _int64flag=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);
- }//endforPLAYER
- else
- scene->addChild(pRender->sprite,10);
- }
- pRender->sprite->setPosition(ccp(pPos->x,pPos->y));
- }
- }//endforsprite
- }//endforexcute
- voidRenderSystem::exit()
- {
- unsignedintsize=EntityManager::getEntityManager()->getEntitySize();
- for(unsignedinti=0;i<size;i++)
- {
- RenderComponent*pRender=(RenderComponent*)EntityManager::getEntityManager()->getComponent(COMPONENT_RENDER,i);
- pRender->sprite->stopAllActions();
- pRender->sprite->removeFromParentAndCleanup(true);
- }//endfor
- }//endforexit
MovementSystem
- #include"MovementSystem.h"
- #include"EntityMananger.h"
- usingnamespaceShapeWar;
- MovementSystem::MovementSystem(int_priority)
- :System(_priority)
- {
- }
- MovementSystem::~MovementSystem()
- {
- }
- voidMovementSystem::enter()
- {
- }//endforenter
- voidMovementSystem::excute(floatdt)
- {
- unsignedintsize=EntityManager::getEntityManager()->getEntitySize();
- for(unsignedinti=0;i<size;i++)
- {
- _int64flag=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;
- }
- }//endfor
- }//endforexcute
- voidMovementSystem::exit()
- {
- }//endforexit
HealthSystem
- #include"HealthSystem.h"
- #include"EntityMananger.h"
- #include"GameInfo.h"
- usingnamespaceShapeWar;
- HealthSystem::HealthSystem(intpriority)
- :System(priority)
- {
- }
- HealthSystem::~HealthSystem()
- {
- }
- voidHealthSystem::enter()
- {
- }//endforenter
- voidHealthSystem::excute(floatdt)
- {
- //GetalltheHealthComponentlist
- EntityManager::Component_List*pHealth=EntityManager::getEntityManager()->getComponentList(COMPONENT_HEALTH);
- for(unsignedintentity=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)
- {
- caseEntityTypeComponent::GREEN_CUBE:
- caseEntityTypeComponent::RED_CUBE:
- GameInfo::getGameInfo()->CUR_score+=1;
- break;
- caseEntityTypeComponent::ORANGE_CUBE:
- GameInfo::getGameInfo()->CUR_score+=2;
- break;
- caseEntityTypeComponent::PURPLE_CUBE:
- GameInfo::getGameInfo()->CUR_score+=3;
- break;
- }//endswitch
- EntityManager::getEntityManager()->removeEntity(entity);
- }
- else
- entity++;
- }//endif
- else
- entity++;
- }//endif
- else
- entity++;
- }//endfor
- }//endforexcute
- voidHealthSystem::exit()
- {
- }//endforexit
CreatorSystem
- #include"CreatorSystem.h"
- #include"EntityCreator.h"
- usingnamespaceShapeWar;
- CreatorSystem::CreatorSystem(int_priority)
- :System(_priority),
- frames(0)
- {
- }
- CreatorSystem::~CreatorSystem()
- {
- }
- voidCreatorSystem::enter()
- {
- }//endforenter
- voidCreatorSystem::excute(floatdt)
- {
- frames++;
- staticintdelta=0;
- delta=frames/1800;
- if(delta>=30)
- delta=30;
- if(frames%(60-delta)==0)
- {
- intvalue=rand()%100;
- floatvy=-60-(frames/300.0)*10;
- if(0<=value&&value<40)
- {
- EntityCreator::createGreenCube(0,vy);
- }
- elseif(40<=value&&value<80)
- {
- EntityCreator::createRedCube(0,vy);
- }
- elseif(80<=value&&value<90)
- {
- EntityCreator::createOrangeCube(0,0.6*vy);
- }
- elseif(90<=value&&value<100)
- {
- EntityCreator::createPurpleCube(0,0.4*vy);
- }
- }//endif
- }//endforexcute
- voidCreatorSystem::exit()
- {
- }//endforexit
InputSystem
- #include"InputSystem.h"
- #include"EntityMananger.h"
- #include"EntityCreator.h"
- #include"AudioSystem.h"
- usingnamespaceShapeWar;
- InputSystem::InputSystem(int_priority)
- :System(_priority)
- {
- }
- InputSystem::~InputSystem()
- {
- }
- voidInputSystem::enter()
- {
- }//endforenter
- voidInputSystem::excute(floatdt)
- {
- //GettheComponentlist
- EntityManager::Component_List*pPos=EntityManager::getEntityManager()->getComponentList(COMPONENT_POSITION);
- EntityManager::Component_List*pType=EntityManager::getEntityManager()->getComponentList(COMPONENT_ENTITY_TYPE);
- //Findtheplayerandtheun-shootedball
- unsignedintsize=EntityManager::getEntityManager()->getEntitySize();
- intplayer=-1,ball=-1;
- for(unsignedinti=0;i<size;i++)
- {
- unsignedinttype=((EntityTypeComponent*)(*pType)[i])->type;
- if(type==EntityTypeComponent::PLAYER)
- {
- player=i;
- }//endif
- if(type==EntityTypeComponent::SPHERE_BALL)
- {
- _int64flag=EntityManager::getEntityManager()->getEntityFlag(i);
- if((flag&COMPONENT_VELOCITY)==0)
- {
- ball=i;
- }//endif
- }//endif
- if(player!=-1&&ball!=-1)
- break;
- }//endfor
- 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;
- }
- }elseif(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;
- }
- }
- staticintnFrame=0;
- if((GetKeyState(VK_SPACE)&0x8000)&&(nFrame>=15))
- {
- VelocityComponent*pVelocity=newVelocityComponent();
- pVelocity->vx=0;
- pVelocity->vy=600;
- EntityManager::getEntityManager()->addComponent(pVelocity,COMPONENT_VELOCITY,ball);
- //Createanotherball
- EntityCreator::createSphereBall(pPlayer_Pos->x,pPlayer_Pos->y);
- //PlayerEffect
- AudioSystem::sharedAudioSystem()->playSound("Shoot.wav");
- nFrame=0;
- }
- nFrame++;
- }//endforexcute
- voidInputSystem::exit()
- {
- }//endforexit
CollidDetectionSystem
- #include"CollidDetectionSystem.h"
- #include"EntityMananger.h"
- #include"AudioSystem.h"
- usingnamespaceShapeWar;
- CollidDetectionSystem::CollidDetectionSystem(int_priority)
- :System(_priority)
- {
- }
- CollidDetectionSystem::~CollidDetectionSystem()
- {
- }
- voidCollidDetectionSystem::enter()
- {
- }//endforenter
- voidCollidDetectionSystem::excute(floatdt)
- {
- //GetallPositionComponentlist
- EntityManager::Component_List*pPos=EntityManager::getEntityManager()->getComponentList(COMPONENT_POSITION);
- //GetalltheCollidableComponentlist
- EntityManager::Component_List*pCollid=EntityManager::getEntityManager()->getComponentList(COMPONENT_COLLID);
- //GetalltheEntityTypeComponentlist
- EntityManager::Component_List*pType=EntityManager::getEntityManager()->getComponentList(COMPONENT_ENTITY_TYPE);
- //GetalltheHealthComponentlist
- EntityManager::Component_List*pHealth=EntityManager::getEntityManager()->getComponentList(COMPONENT_HEALTH);
- unsignedintsize=EntityManager::getEntityManager()->getEntitySize();
- //Findallsphereball
- std::vector<unsignedint>index_array;
- for(unsignedinti=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);
- }//endif
- }//endif
- }//endfor
- for(unsignedinti=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(unsignedintj=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=newAnimateComponent();
- pAnimate->animate=newCCAnimate();
- CCAnimation*pAnimation=CCAnimation::create();
- for(inti=0;i<10;i++)
- {
- charbuffer[32];
- sprintf(buffer,"Explosion000%d.png",i);
- pAnimation->addSpriteFrameWithFileName(buffer);
- }//endfor
- pAnimation->setDelayPerUnit(1.0/10);
- pAnimate->animate->initWithAnimation(pAnimation);
- pAnimate->frames=60;
- //AddtheAnimateComponenttotheentity
- EntityManager::getEntityManager()->addComponent(pAnimate,COMPONENT_ANIMATE,j);
- //RemovetheCollidDetectionComponent
- EntityManager::getEntityManager()->removeComponent(COMPONENT_COLLID,j);
- //RemovetheVelocityComponent
- EntityManager::getEntityManager()->removeComponent(COMPONENT_VELOCITY,j);
- }//endif
- HealthComponent*ball=(HealthComponent*)(*pHealth)[index_array[i]];
- ball->health--;
- //Playhurteffect
- AudioSystem::sharedAudioSystem()->playSound("Hurt.wav");
- break;
- }//endif
- }//endforcube
- }//endforsphereball
- }//endforexcute
- voidCollidDetectionSystem::exit()
- {
- }//endforexit
BoundaryCheckSystem
- #include"BoundaryCheckSystem.h"
- #include"EntityMananger.h"
- usingnamespaceShapeWar;
- BoundaryCheckSystem::BoundaryCheckSystem(intpriority)
- :System(priority)
- {
- }
- BoundaryCheckSystem::~BoundaryCheckSystem()
- {
- }
- voidBoundaryCheckSystem::enter()
- {
- }//endforenter
- voidBoundaryCheckSystem::excute(floatdt)
- {
- //GetallPositionComponentlist
- EntityManager::Component_List*pPos=EntityManager::getEntityManager()->getComponentList(COMPONENT_POSITION);
- //GetalltheEntityTypeComponentlist
- EntityManager::Component_List*pType=EntityManager::getEntityManager()->getComponentList(COMPONENT_ENTITY_TYPE);
- unsignedintsize=EntityManager::getEntityManager()->getEntitySize();
- //FindthePlayer'shealthComponent
- unsignedintplayer_entity=-1;
- for(inti=0;i<size;i++)
- {
- if(((EntityTypeComponent*)(*pType)[i])->type==EntityTypeComponent::PLAYER)
- {
- player_entity=i;
- break;
- }
- }//endfor
- HealthComponent*health=(HealthComponent*)EntityManager::getEntityManager()->getComponent(COMPONENT_HEALTH,player_entity);
- //Checkiftheentityisoutofthescreen
- for(unsignedinti=0;i<size;)
- {
- if(((EntityTypeComponent*)(*pType)[i])->type==EntityTypeComponent::SPHERE_BALL)
- {
- if(((PositionComponent*)(*pPos)[i])->y>480)
- {
- EntityManager::getEntityManager()->removeEntity(i);
- size-=1;
- continue;
- }
- }//endifforsphereball
- else
- {
- if(((PositionComponent*)(*pPos)[i])->y<0)
- {
- EntityManager::getEntityManager()->removeEntity(i);
- size-=1;
- health->health--;
- continue;
- }
- }
- i++;
- }//endfor
- }//endforexcute
- voidBoundaryCheckSystem::exit()
- {
- }//endforexit
系统内部是如何工作的,不是本文章讨论的范畴。这篇文章旨在告诉读者,我们可以通过ECS系统,实现更加弹性的设计。通过使用组合的方法,大大降低系统的耦合性。同时,这里将数据和处理过程,通过组建和系统的方法实现了分离。通过这样的系统,我们很容易的能够实现网络游戏,因为只需要对组件数据进行单独的传输即可,并且很容易的实现诸如关卡保存,这样的内容。
但是,任何事情都是双面的,在带来这些好处的同时,在另外的方面也会带来限制。
系统缺点
通过上面的描述,我们大概可以明确这样的系统有如下的缺点:
- 内存利用较低。我们在容器中为每一个实体都开辟了同样大的空间,如果某个实体并不具有那样的组件的时候,那个空间依然为它保留着,这浪费了大量的空间
- 同一个实体,没有办法拥有同一个组件的两份实例。也就说,对于像动画这样的组件,一个实体,可能不只有一个动画属性。它可能需要在死亡时,同时播放两种动画,那么这个系统就没有办法完成这样的工作。
- 最重要的一个缺点就是性能问题。读者可能发现,系统和实体的交互方式,完全是系统主动的轮询,来进行系统的处理。我们知道,高效的设计方法,应该是让实体在有需要的时候,调用系统来进行工作。如果系统持续的运行,在很多情况下,系统并没有做什么有效的工作。所以,应该将这种主动轮询的方式改成由事件驱动的可能更好一点。但是,博主暂时没有想到如何设计这样的系统,可能在后面的实践中,掌握这样的设计方法的时候,再来向大家讲述。
好了,ECS架构实践的第一篇博客就到此结束了。
如果您有什么不明白的地方,或者发现了文中设计上的缺陷,欢迎大家在评论中指出。毕竟,旁观者清,当局者迷。希望能够和大家互相的学习!互相进步!
这个游戏的源代码和程序以及上传至CSDN,感兴趣的同学可以自行下载来阅读和试玩,不要忘了在评论中给出你获得的最高分哦,大家比比看谁的反应是最好的哦哦!!!
ShapeWar_exe.zip(部分资源来至于网络,请大家不要用于商业用途哦!!!)