3.5 Node与Node层级架构
Cocos2d-x采用层级(树形)结构管理场景、层、精灵、菜单、文本、地图和粒子系统等节点(Node)对象。一个场景可以包含多个层,一个层又可以包含多个精灵、菜单、文本、地图和粒子系统等对象。层级结构中的节点可以是场景、层、精灵、菜单、文本、地图和粒子系统等任何对象。
节点的层级结构如图3-16所示。
图3-16 节点的层级结构
这些节点有一个共同的父类Node,Node类图如图3-17所示。Node类是Cocos2d-x最为重要的根类,它是场景、层、精灵、菜单、文本、地图和粒子系统等类的根类。
图3-17 Node类图
3.5.1 Node中的重要操作
Node作为根类,它有很多重要的函数。下面分别介绍一下:
●创建节点:Node* childNode = Node::create()。
●增加新的子节点:node->addChild (childNode, 0, 123),第二个参数Z轴绘制顺序,第三个参数是标签。
●查找子节点:Node* node = node->getChildByTag(123),通过标签查找子节点。
●node->removeChildByTag(123, true)通过标签删除子节点,并停止所有该节点上的一切动作。
●node->removeChild(childNode, true)删除childNode节点,并停止所有该子节点上的一切动作。
●node->removeAllChildrenWithCleanup(true)删除node节点的所有子节点,并停止这些子节点上的一切动作。
●node->removeFromParentAndCleanup(true)从父节点删除node节点,并停止所有该节点上的一切动作。
3.5.2 Node中的重要属性
此外,Node还有两个非常重要的属性:position和anchorPoint。
position(位置)属性是Node对象的实际位置。position属性往往还要配合使用anchorPoint属性,为了将一个Node对象(标准矩形图形)精准地放置在屏幕某一个位置上,需要设置该矩形的anchorPoint(锚点),anchorPoint属性是相对于position的比例,anchorPoint计算公式是(w1/w2,h1/h2),图3-18所示锚点位于节点对象矩形内,w1是锚点到节点对象左下角的水平距离,w2是节点对象宽度;h1是锚点到节点对象左下角的垂直距离,h2是节点对象高度。(w1/w2 , h1/h2)计算结果为(0.5,0.5),所以anchorPoint为(0.5,0.5),anchorPoint的默认值就是(0.5,0.5)。
图3-18 anchorPoint为(0.5,0.5)
图3-19是anchorPoint为(0.66, 0.5)的情况。
图3-19 anchorPoint为(0.66,0.5)
anchorPoint还有两个极端值,一个是锚点在节点对象矩形右上角,如果图3-20所示,此时anchorPoint为(1,1);另一个是锚点在节点对象矩形左下角,如果图3-21所示,此时anchorPoint为(0,0)。
图3-20 anchorPoint为(1,1)
图3-21 anchorPoint为(0,0)
为了进一步了解anchorPoint的使用,我们修改HelloWorld实例,修改HelloWorldScene.cpp的HelloWorld::init()函数如下,其中加粗字体显示的是我们添加的代码。
bool HelloWorld::init()
{
…
auto label = Label::createWithTTF("Hello World", "fonts/Marker Felt.ttf", 24);
label->setPosition(Point(origin.x + visibleSize.width/2,
origin.y + visibleSize.height - label->getContentSize().height));
label->setAnchorPoint( Vec2(1.0, 1.0));
this->addChild(label, 1);
auto sprite = Sprite::create("HelloWorld.png");
sprite->setPosition(Vec2(visibleSize.width/2 + origin.x, visibleSize.height/2 + origin.y));
this->addChild(sprite, 0);
return true;
}
运行结果如图3-22所示,Hello World设置了anchorPoint为(1.0,1.0)。
图3-22 Hello World的anchorPoint为(1.0,1.0)
3.5.3 游戏循环与调度
每一个游戏程序都有一个循环在不断运行,它由导演对象来管理和维护。如果需要场景中的精灵运动起来,我们可以在游戏循环中使用定时器(Scheduler)对精灵等对象的运行进行调度。因为Node类封装了Scheduler类,所以我们也可以直接使用Node中的定时器相关函数。
Node中的定时器相关函数主要有:
●void scheduleUpdate(void):每个Node对象只要调用该函数,那么这个Node对象就会定时地每帧回调一次自己的update(float dt)函数。
●void schedule(SEL_SCHEDULE selector, float interval):与scheduleUpdate函数功能一样,不同的是我们可以指定回调函数(通过selector指定),也可以更加需要指定回调时间间隔。
●void unscheduleUpdate(void):停止update(float dt)函数调度。
●void unschedule(SEL_SCHEDULE selector):可以指定具体函数停止调度。
●void unscheduleAllSelectors(void):可以停止调度。
为了进一步了解游戏循环与调度的使用,我们修改HelloWorld实例。
修改HelloWorldScene.h代码,添加update(float dt)声明,代码如下:
class HelloWorld : public cocos2d::Layer
{
public:
…
virtual void update(float dt);
CREATE_FUNC(HelloWorld);
};
修改HelloWorldScene.cpp代码如下:
bool HelloWorld::init() { … auto label = LabelTTF::create("Hello World", "Arial", 24); label->setTag(123); ① … //更新函数 this->scheduleUpdate(); ② //this->schedule(schedule_selector(HelloWorld::update),1.0f/60); ③ return true; } void HelloWorld::update(float dt) ④ { auto label = this->getChildByTag(123); ⑤ label->setPosition(label->getPosition() + Vec2(2,-2)); ⑥ } void HelloWorld::menuCloseCallback(Ref* pSender) { //停止更新 unscheduleUpdate(); ⑦ Director::getInstance()->end(); … }
为了能够在init函数之外访问标签对象label,我们需要为标签对象设置Tag属性,其中第①行代码就是设置Tag属性为123。第⑤行代码是通过Tag属性重新获得这个标签对象。
为了能够开始调度,还需要在init函数中调用scheduleUpdate(见第②行代码)或schedule(见第③行代码)。
代码第④行的HelloWorld::update(float dt)函数是调度函数,精灵等对象的变化逻辑都是在这个函数中编写的。这个例子很简单,只是让标签对象动起来,第⑥行代码就是改变它的位置。
为了省电等目的,如果不再使用调度,一定不要忘记停止调度。第⑦行代码unscheduleUpdate()就是停止调度update,如果是其他的调度函数可以采用unschedule或unscheduleAllSelectors停止。