如何在cocos2d-x中使用ECS(實體-組件-系統)架構方法開發一個游戲? - 博客頻道
在我的博客中,我曾經翻譯了幾篇關於ECS的文章。這些文章都是來自於Game Development網站。如果你對這個架構方式還不是很了解的話,歡迎閱讀理解 組件-實體-系統和實現 組件-實體-系統。
我發現這個架構方式,是在瀏覽GameDev上的文章的時候了解到的。很久以前,就知道了有這么個架構方法,只是一直沒有機會自己實踐下。這一次,我就抽空,根據網上對ECS系統的討論,采用了一種實現方法,來實現一個。
我很喜歡做游戲,所以同樣的,還是用游戲實例來實踐這個架構方法。我將會采用cocos2d-x來作為游戲開發的主要工具,這是一款十分強大的跨平台游戲引擎,感興趣的讀者,可以自行搜索了解。
我一直覺得,作為游戲程序員,能夠自己獨立的繪制游戲中的圖片資源是一件非常好玩的事情。所以,沒有美術功底的我,就選擇了一種復古風格的藝術——像素藝 術來學習。經過一段時間的學習,發現做像素畫還是很有趣的,所以我就將我以前做的簡單的像素圖片,來融合成現在的這個游戲實例——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值而已,所以,我並沒有專門為這個概念定義什么,它在我開發的游 戲中,僅僅是一個下標值而已。但是,我們需要知道,游戲中那么多的實體,需要進行統一的管理。所以為此,我創建了如下的一個類,用來對游戲中所有的實體進 行管理。
- #pragma once
- #include<vector>
- #include"Component.h"
- using namespace std ;
- namespace ShapeWar
- {
- class EntityManager
- {
- private:
- EntityManager();
- ~EntityManager();
- public:
- static EntityManager* getEntityManager();
- public:
- _int64 createEntity() ;
- void removeEntity(_int64 entity);
- void registComponents(_int64 component_size);
- void addComponent(Component* component, _int64 component_type, _int64 entity);
- void removeComponent(_int64 component_type, _int64 entity);
- std::vector<Component*>* getComponentList(_int64 component_type) const ;
- Component* getComponent(_int64 component_type, _int64 entity);
- _int64 getEntityFlag(_int64 entity) const ;
- void setEntityFlag(_int64 entity, _int64 entity_type);
- unsigned int getEntitySize() const ;
- typedef std::vector<Component*> Component_List;
- private:
- void _destroy();
- private:
- std::vector<_int64> m_EntityFlagArray ;
- <pre name="code" class="cpp"> std::vector<Component_List> m_ComponentContainer ;
//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 ;
std::vector<Component_List> m_ComponentContainer ; //Contain all the entity
而Component_List定義為如下:
- typedef std::vector<Component*> 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 ;
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采用分治算法,設計了這個方法來獲取位數。
好了,通過上面的描述,讀者應該明白我是以怎么樣的方式來維護游戲中所有的實體的了!!!
在實現了上面的組件,實體之后,接下來就應該實現系統了。我這里實現系統的方式,是根據這篇博客中提出的方法來實現的。
首先,抽象一個系統的類,如下所示:
- 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 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 ; };
在這個抽象的系統中,我定義了一個優先級,這樣,我們就可以定義哪一些系統需要在另外一些系統之前進行運行。有了系統之后,我們就需要一個管理的方式,所以,在定義了一個系統管理器,如下所示:
- class SystemManager
- {
- private:
- SystemManager();
- ~SystemManager();
- public:
- static SystemManager* getSystemManager() ;
- public:
- void addSystem(System * system);
- void update(float dt);
- void pause();
- void resume();
- private:
- void _destroy();
- private:
- std::vector<System*> system_list ;
- bool bPaused ;
- };
/** * 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 ;
- for(int i = 0 ; i < system_list.size() ; i ++)
- {
- system_list[i]->excute(dt);
- }
- }
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()
- {
- }
- 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);
- }
- else
- scene->addChild(pRender->sprite, 10);
- }
- pRender->sprite->setPosition(ccp(pPos->x, pPos->y));
- }
- }
- }
- 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);
- }
- }
#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()
- {
- }
- 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 ;
- }
- }
- }
- void MovementSystem::exit()
- {
- }
#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()
- {
- }
- void HealthSystem::excute(float dt)
- {
- 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 ;
- }
- EntityManager::getEntityManager()->removeEntity(entity);
- }
- else
- entity ++ ;
- }
- else
- entity ++ ;
- }
- else
- entity ++ ;
- }
- }
- void HealthSystem::exit()
- {
- }
#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()
- {
- }
- 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);
- }
- }
- }
- void CreatorSystem::exit()
- {
- }
#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()
- {
- }
- void BoundaryCheckSystem::excute(float dt)
- {
- EntityManager::Component_List* pPos = EntityManager::getEntityManager()->getComponentList(COMPONENT_POSITION);
- EntityManager::Component_List* pType = EntityManager::getEntityManager()->getComponentList(COMPONENT_ENTITY_TYPE);
- unsigned int size = EntityManager::getEntityManager()->getEntitySize();
- unsigned int player_entity = -1 ;
- for(int i = 0 ; i < size ; i ++)
- {
- if(((EntityTypeComponent*)(*pType)[i])->type == EntityTypeComponent::PLAYER)
- {
- player_entity = i ;
- break ;
- }
- }
- HealthComponent * health = (HealthComponent*)EntityManager::getEntityManager()->getComponent(COMPONENT_HEALTH, player_entity);
- 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 ;
- }
- }
- else
- {
- if(((PositionComponent*)(*pPos)[i])->y < 0)
- {
- EntityManager::getEntityManager()->removeEntity(i);
- size -= 1 ;
- health->health-- ;
- continue ;
- }
- }
- i ++ ;
- }
- }
- void BoundaryCheckSystem::exit()
- {
- }
#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(部分資源來至於網絡,請大家不要用於商業用途哦!!!)