Cocos2d-x学习笔记:完全掌握C++ API与游戏项目开发 (未来书库)
上QQ阅读APP看书,第一时间看更新

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停止。