引言
在我的博客中,我曾经翻译了几篇关于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的组要原因是能够将他们统一的进行处理,仅此而已。
在定义完了基类之后,