作者:慧科集团华东校区-朱家聪老师,转载请注明出处及本链接。
导演,场景,层,精灵,菜单
在Cocos2d-x中,有很多的概念,这些概念很多都来源于动画,动漫和电影等行业,如导演,场景和层等概念,当然也有传统的游戏概念。
导演
场景
层
节点
精灵
菜单
动作
效果
粒子运动
地图
物理引擎
首先要来学习的是最主要的几个概念,导演,场景,层,精灵,菜单。前四个概念之间的关系如下图所示:
导演
导演类 Director(v3.0之前是 CCDirector)是用于管理场景的对象,采用了单例设计模式,在一个游戏程序中只有一个导演对象。由于是单例模式,能够保存当前程序中的很多配置信息,并保证这些信息的一致性。获取当前程序的导演对象的代码如下:
auto director = Director::getInstance();
导演对象的职责如下:
- 访问和改变场景
- 访问Cocos2d-x的配置信息
- 暂停,继续和停止游戏
- 转换坐标
场景
场景类Scene(v3.0之前是CCScene)是构成游戏的界面,场景大致可以分为以下几类:
- 展示类场景:播放视频或简单的在图像上输出文字,如游戏的开始界面,帮助界面,胜利提示界面等。
- 选项类场景:包括主菜单,设置菜单等。
- 游戏场景:游戏的主要内容。
导演类管理着不同场景之间的转换,而场景则是层Layer的承载者。
层
层是Cocos2d-x中的一个重点,我们所有的游戏内容都要放在层上进行实现。类似于PhotoShop中的图层的概念,每一个游戏场景都是有一个个的图层构成的。与场景不同,层通常包含的是直接在屏幕上呈现的内容,并且可以接受用户的输入事件,包括触摸,加速度计和键盘输入等。我们需要在层中加入精灵,文本标签或者其他游戏元素,并设置游戏元素的属性,比如位置,方向和大小;设置游戏元素的动作等。
auto layer = LayerColor::create(Color4B(0, 128, 128, 255));
layer->setContentSize(Size(120, 80));
layer->setPosition(Point(50, 50));
this->addChild(layer, 10);
auto layer1 = LayerColor::create(Color4B(128, 0, 128, 255));
layer1->setContentSize(Size(120, 80));
layer1->setPosition(Point(100, 80));
this->addChild(layer1, 20);
auto layer2 = LayerColor::create(Color4B(128, 128, 0, 255));
layer2->setContentSize(Size(120, 80));
layer2->setPosition(Point(150, 110));
this->addChild(layer2, 30);
/*
LayerColor是Layer的一个派生类,用于创建带有背景颜色的层对象
Color4B(r,g,b,a)用于创建一个颜色数据
*/
我们可以在一个场景中添加多个层对象,这些层按照添加到场景中的顺序先后来排列。先添加到场景中的层会放置在最底层,后添加的层会叠加到之前的层上面。而层也是事件响应机制中的一个响应者,当用户触发一个触摸事件时,在上层的层会优先接收到系统事件。在事件的传递过程中,如果有一个层响应了这个事件,则后续的层就不会再接受到该事件。所以层的先后顺序决定了层的上下关系以及事件的传递和响应。
精灵
精灵类Sprite是可以理解为游戏场景中的每一个元素,背景/玩家/敌人/静态或动态的物体都是由精灵对象来实现的。精灵可以移动,旋转,缩放,执行动画,并接受其他转换。在Cocos2d-x中提供了两个精灵类,分别是负责2D游戏的Sprite和负责3D游戏的Sprite3D,他们都继承于Node类。
auto sprite = Sprite::create("HelloWorld.png");
layer2->addChild(sprite,0);
使用HelloWorld.png
来创建一个sprite对象,然后将这个精灵对象加入到前面所创建的layer3中去。结果如下图所示。可以发现这个精灵被添加到了之前所创建的Layer3上面了,但是我们并没有设置这个精灵的位置。因为在Cocos2d-x中,坐标系的原点在左下角,而精灵定锚点在最中心。所以呈现出了精灵的中心点和层的左下角点重合的现象。
菜单
菜单Menu在游戏中是非常重要的概念,它提供了操作的集合。菜单类派生于Layer类,其中包含了菜单项MenuItem类。MenuItem又派生出了多种形式的子类,MenuItemLabel,MenuItemSprite,MenuItemToggle。每一个菜单项又有三种基本状态,正常/选中/禁止。
节点 Node
Cocos2d-x使用的是层级(树形)结构来管理场景,层,精灵,菜单,文本等节点对象。在前文中所说的场景,层,精灵,菜单都是Cocos2d-x结构树中的节点。这些类都有一个共同的基类Node,想要能够管理Cocos2d-x的结构树,我们需要来学习节点相关的各种操作。
节点的常用操作
新建节点
使用Node类的create()
方法可能新建一个空的节点。不同类型的节点有不同的创建方法,比如Sprite在创建时可以添加一张图片。创建完毕后,可以将这个节点通过addChild(node)
添加到节点树中去。
//创建一个新节点
Node *node = Node::create();
//将这个节点添加到当前场景中
this->addChild(node);
对于addChild函数有多种不同参数的定义,可以使用的参数有 child,localZOrder,tag,name。其中localZOrder表示节点在Z轴方向上的显示层次。localZOrder的值越大,显示在越上层。
virtual void addChild(Node * child);
virtual void addChild(Node * child, int localZOrder);
virtual void addChild(Node* child, int localZOrder, int tag);
virtual void addChild(Node* child, int localZOrder, const std::string &name);
查找子节点
当程序运行过程中,我们需要从一个场景的多个节点中,查找到一个我们需要的节点。有两种方式来进行这个操作,分别是使用name和tag。需要注意的是,不论是哪一种方法,都只能查找某一个节点的直属子节点。
//使用tag来查找子节点
node1->setTag(100);
auto nodeForTag = this->getChildByTag(100);
//使用名字来查找子节点
node1->setName("node01");
auto nodeForName = this->getChildByName("node01");
删除子节点
删除某一个节点,可以将这个节点从它所在的节点树中移除。被移除的节点会从屏幕上消失,并且停止一切动作。常用的删除方法有多种,根据实际开发需求来选择使用。
//从父节点通过tag/name来删除node1
this->removeChildByTag(100);
this->removeChildByName("node01");
//从父节点删除指定节点
this->removeChild(node1);
//从父节点删除所有子节点
this->removeAllChildren();
//将子节点从父节点下移除
node1->removeFromParent();
节点的定位
要在当前的场景中,定位一个节点的位置,需要使用到两个属性,position和anchorPoint。为了更直观的来演示节点的Position和anchorPoint属性,我们需要新建一共项目场景来做测试。
auto layer1 = LayerColor::create(Color4B(255, 0, 0, 255));
layer1->setContentSize(Size(100, 100));
layer1->setPosition(Point(0, 30));
layer1->setAnchorPoint(Point(0,0));
this->addChild(layer1);
用一个Layer来举例。此处用到了三个属性的设置,分别是contentSize,position,anchorPoint。
首先设置了这个节点的大小,这边用到了一个类Size。使用Size类的构造函数创建一个了一个Size对象,用于表示当前节点的大小。两个参数分别表示宽和高。
contentSize:节点的大小,Size(float width, float height);
使用position属性可以来设定节点在父节点中的相对位置。在Cocos2d-x中,使用了笛卡尔坐标系,而非标准的屏幕坐标系。坐标系的原点在父节点的左下角。而在普通的iOS程序中使用的屏幕坐标系的原点在左上角。
position:节点的位置,Vec2(float xx, float yy);
一个节点一般被看作是一个标准的矩形区域。对于一个区域的定位,只有一个position值是远远不够的。所以每一个节点都还有一个锚点的概念。这个锚点是节点内部一个虚拟的点,在定位节点时可以理解为将锚点位置对准父节点坐标系中position点所在的位置。而锚点在当前节点中的相对位置则以相对比例来表示。例如(0,0)点在左下角,(1,1)点在右上角,(0,1)点在左上角,(1,0)点在右下角。
anchorPoint:节点的锚点位置,Vec2(float xx, float yy);
游戏的循环和调度
Cocos2d-x调度器为游戏提供定时事件和定时调用服务。所有Node对象都可以调度和取消调度事件,使用调度器有几个好处:
- 每当Node不再可见或已从场景中移除时,调度器会停止。
- Cocos2d-x暂停时,调度器也会停止。当Cocos2d-x重新开始时,调度器也会自动继续启动。
- Cocos2d-x封装了一个供各种不同平台使用的调度器,使用此调度器你不用关心和跟踪你所设定的定时对象的销毁和停止,以及崩溃的风险。
默认调度器
在节点运行过程中,可以随时开启默认调度器。默认调度器能够自动的调用节点对象的update函数,该方法会在每帧绘制之前都会被调用一次。因为游戏能够被用户做观察到的最短时间间隔为1帧,所以在update函数中就能过执行大多数的游戏运行逻辑。使用scheduleUpdate()
函数可以开启默认调度器,或者使用unscheduleUpdate()
来关闭默认调度器。
class HelloWorld : public cocos2d::Scene
{
public:
static cocos2d::Scene* createScene();
virtual bool init();
// a selector callback
void menuCloseCallback(cocos2d::Ref* pSender);
// implement the "static create()" method manually
CREATE_FUNC(HelloWorld);
// 定义update函数,用于循环调用
virtual void update(float dt);
};
// 实现update函数
void HelloWorld::update(float dt){
cout<<"update:"<scheduleUpdate();
return true;
}
程序运行结果:
执行上述代码后系统会不停的去调用update函数,这个函数一般都用于执行一些时时刻刻需要做的事。比如说一些射击类游戏中,子弹的碰撞计算与胜负的判定。
自定义调度器
对于一些执行频率不需要这么高的操作,使用默认调度器就不太合适了。这里我们还可以创建一个自定义的调度器。
class HelloWorld : public cocos2d::Scene
{
public:
static cocos2d::Scene* createScene();
virtual bool init();
// a selector callback
void menuCloseCallback(cocos2d::Ref* pSender);
// implement the "static create()" method manually
CREATE_FUNC(HelloWorld);
//定义自定义的调度函数
virtual void updateCustom(float dt);
};
void HelloWorld::updateCustom(float dt){
cout<<"update:"<
然后同样也需要开启这个自定义的调度器。使用schedule函数可以开启自定义的调度器,这里可以根据自己的需求来填写参数。
this->schedule(schedule_selector(HelloWorld::updateCustom), 1.0f, kRepeatForever, 0);
//取消某一个调度器的执行
//this->unschedule(schedule_selector(HelloWorld::updateCustom));
//取消所有调度器的执行
//this->unscheduleAllCallbacks();
//schedule函数的定义:
void Node::schedule(SEL_SCHEDULE selector);
void Node::schedule(SEL_SCHEDULE selector, float interval);
void Node::schedule(SEL_SCHEDULE selector, float interval, unsigned int repeat, float delay);
参数:
SEL_SCHEDULE selector:所要调用的函数,使用schedule_selector()宏定义来声明一个函数
interval:调度的时间间隔,单位是秒
repeat:重复调度的次数,kRepeatForever表示一直调用直到手动取消
delay: 延迟调用的时间
单次调度器
顾名思义就是值调用一次的调度器。
class HelloWorld : public cocos2d::Scene
{
public:
static cocos2d::Scene* createScene();
virtual bool init();
// a selector callback
void menuCloseCallback(cocos2d::Ref* pSender);
// implement the "static create()" method manually
CREATE_FUNC(HelloWorld);
// 单次调度函数
void updateOnce(float dt);
};
bool HelloWorld::init()
{
scheduleOnce(schedule_selector(HelloWorld::updateOnce), 0.1f);
return true;
}
void HelloWorld::updateOnce(float dt)
{
log("Once");
}