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

4.4 Node与Node层级架构

Cocos2d-x采用层级(树形)结构管理场景、层、精灵、菜单、文本、地图和粒子系统等节点(Node)对象。一个场景包含了多个层,一个层又包含多个精灵、菜单、文本、地图和粒子系统等对象。层级结构中的节点可以是场景、层、精灵、菜单、文本、地图和粒子系统等任何对象。

节点的层级结构如图4-15所示。

图4-15 节点的层级结构

Cocos2d-x JS API中的节点类是cc.Node,cc.Node类图如图4-16所示。cc.Node类是最为重要的根类,它是场景、层、精灵、菜单、文本、地图和粒子系统等类的根类。

图4-16 cc.Node类图

4.4.1 Node中重要的操作

作为根类,cc.Node有很多重要的方法,下面分别介绍:


□创建节点:var childNode = new cc.Node()。

□增加新的子节点:node.addChild(childNode,0,123),第二个参数Z轴绘制顺序,第三个参数是标签。

□查找子节点:var childNode = node.getChildByTag(123),通过标签查找子节点。

□node.removeChildByTag(123,true):通过标签删除子节点,并停止所有该子节点上的一切动作。

□node.removeChild(childNode,true):删除childNode节点,并停止所有该子节点上的一切动作。

□node.removeAllChildrenWithCleanup(true):删除node节点的所有子节点,并停止这些子节点上的一切动作。

□node.removeFromParentAndCleanup(true):从父节点删除node节点,并停止所有该节点上的一切动作。


4.4.2 Node中重要的属性

Node还有两个非常重要的属性:position和anchorPoint。

position(位置)属性是Node对象的实际位置,它往往需要配合anchorPoint属性使用。为了将一个Node对象(标准矩形)精准地放在屏幕某一个位置上,需要设置该矩形的anchorPoint(锚点)。anchorPoint属性是相对于position的比例,anchorPoint的计算公式是(w1/w2,h1/h2)。图4-17所示的锚点位于节点对象矩形内,w1是锚点到节点对象左下角的水平距离,w2是节点对象宽度;h1是锚点到节点对象左下角的垂直距离,h2是节点对象的高度。(w1/w2,h1/h2)计算结果为(0.5,0.5),所以anchorPoint为(0.5,0.5),anchorPoint的默认值就是(0.5,0.5)。

图4-17 anchorPoint为(0.5,0.5)

图4-18是anchorPoint为(0.66,0.5)的情况。

图4-18 anchorPoint为(0.66,0.5)

anchorPoint还有两个极端值:一个是锚点在节点对象矩形右上角,如图4-19所示,此时anchorPoint为(1,1);另一个是锚点在节点对象矩形左下角,如果图4-20所示,此时anchorPoint为(0,0)。

图4-19 anchorPoint为(1,1)

图4-20 anchorPoint为(0,0)

为了进一步了解anchorPoint的使用,我们修改HelloJS实例,修改app.js的ctor方法中的helloLabel代码:

    var helloLabel = new cc.LabelTTF("Hello World", "Arial", 38);
    
    helloLabel.setPosition(size.width / 2, 0);        ①
    // helloLabel.x = size.width / 2;                 ②
    // helloLabel.y = 0;                              ③
    
    helloLabel.setAnchorPoint(cc.p(1.0, 1.0));        ④
    // helloLabel.anchorX = 1.0;                      ⑤
    // helloLabel.anchorY = 1.0;                      ⑥
    
    this.addChild(helloLabel, 5);

上述代码第①行调用setPosition(x,y)方法设置position属性,也可以直接通过属性helloLabel.x和helloLabel.y设置(见第②行和第③行代码)。

第④行代码调用setAnchorPoint(x,y)方法设置anchorPoint属性,也可以直接通过属性helloLabel.anchorX和helloLabel.anchorY设置(见第⑤行和第⑥行代码)。

此外,由于有多个属性需要设置,我们可以通过helloLabel.attr({…})语句进行设置,代码如下:

    helloLabel.attr({
          x: size.width / 2,
          y: 0,
          anchorX: 1.0,
          anchorY: 1.0
    });

运行结果如图4-21所示,helloLabel设置anchorPoint为(1.0,1.0)。

图4-21 helloLabel的anchorPoint为(1.0,1.0)

4.4.3 游戏循环与调度

每一个游戏程序都有一个循环在不断运行,它是由导演对象来管理和维护。如果需要场景中的精灵运动起来,可以在游戏循环中使用定时器(cc.Scheduler)对精灵等对象的运行进行调度。因为cc.Node类封装了cc.Scheduler类,所以也可以直接使用cc.Node中定时器的相关方法。

cc.Node中定时器的相关方法主要有:


□scheduleUpdate():每个Node对象只要调用该方法,那么这个Node对象就会定时地每帧回调一次自己的update(dt)方法。

□schedule(callback_fn,interval,repeat,delay):与scheduleUpdate方法功能一样,不同的是可以指定回调方法(通过callback_fn指定)。interval是时间间隔;repeat是执行的次数;delay延迟执行的时间。

□unscheduleUpdate():停止update(dt)方法调度。

□unschedule(callback_fn):指定具体方法停止调度。

□unscheduleAllCallbacks():停止所有的调度。


为了进一步了解游戏循环与调度的使用,我们修改HelloJS实例。修改app.js代码,添加update(dt)声明,代码如下:

    var HelloWorldLayer = cc.Layer.extend({
        sprite: null,
        ctor: function () {
            ……
            var closeItem = new cc.MenuItemImage(
                res.CloseNormal_png,
                res.CloseSelected_png,
                function () {
                    cc.log("Menu is clicked!");
                    this.unscheduleUpdate();
                }, this);
    
            ……
            var helloLabel = new cc.LabelTTF("Hello World", "Arial", 38);
    
            helloLabel.attr({
                x: size.width / 2,
                y: 0,
                anchorX: 1.0,
                anchorY: 1.0
            });
    
            helloLabel.setTag(123);                                            ①
            //更新方法
            this.scheduleUpdate();                                             ②
            //this.schedule(this.update, 1.0/60, cc.REPEAT_FOREVER, 0.1);                ③
    
            this.addChild(helloLabel, 5);
    
            // add "HelloWorld" splash screen"
            this.sprite = new cc.Sprite(res.HelloWorld_png);
            this.sprite.attr({
                x: size.width / 2,
                y: size.height / 2,
                scale: 0.5,
                rotation: 180
            });
            this.addChild(this.sprite, 0);
    
            this.sprite.runAction(
                cc.sequence(
                    cc.rotateTo(2, 0),
                    cc.scaleTo(2, 1, 1)
                )
            );
            helloLabel.runAction(
                cc.spawn(
                    cc.moveBy(2.5, cc.p(0, size.height - 40)),
                    cc.tintTo(2.5, 255, 125, 0)
                )
            );
            return true;
        },
        update: function (dt) {                                                ④
            var label = this.getChildByTag(123);                               ⑤
            label.x = label.x + 0.2;                                           ⑥
            label.y = label.y + 0.2;                                           ⑦
        }
    });

为了能够在ctor方法之外访问标签对象helloLabel,需要为标签对象设置Tag属性,其中第①行代码就是设置Tag属性为123。第⑤行代码是通过Tag属性重新获得这个标签对象。

为了能够开始调度,还需要在ctor方法中调用scheduleUpdate(见第②行代码)或schedule(见第③行代码)。

第④行代码的update(dt)方法是调度方法。精灵等对象的变化逻辑都是在这个方法中编写的。这个例子很简单,只是让标签对象动起来,第⑥行代码就是改变它的位置。

为了省电等目的,如果不再使用调度,一定不要忘记停止调度。可以在Close菜单项的点击事件中停止调度,代码如下:

    var closeItem = new cc.MenuItemImage(
                res.CloseNormal_png,
                res.CloseSelected_png,
                function () {
                    this.unscheduleUpdate();
                }, this);

代码this.unscheduleUpdate()就是停止调度update,如果是其他的调度方法可以采用unschedule或unscheduleAllSelectors停止。