6.2 节点的使用
节点有3种使用方法,直接使用Node,使用具备某种功能的Node(如Label和Sprite等),以及继承Node。
❏直接使用Node,往往用来作为容器使用,添加一个空的Node来梳理节点树的结构。
❏使用具备某种功能的Node,主要是为了使用其提供的功能,如显示文字和图片这样的功能(如在HelloCpp中创建的背景图片Sprite),Node作为一个void类型的节点,并没有提供显示的功能。
❏继承Node是非常实用的方法,用于扩展各种Node的功能,以及编写游戏逻辑。
使用节点的思路也是使用Cocos2d-x的思路(如HelloCpp中的HelloWorldScene场景类),首先需要编写逻辑代码,我们的代码都是在直接或间接继承于Node的子类对应的回调方法中编写的,编辑好代码,然后将节点添加到场景中,使节点的逻辑被执行。
在游戏运行时,会有一个场景节点来管理整个场景,只需要将节点添加到这个场景节点下即可,通过添加、删除及修改节点来改变场景的显示内容。在HelloCpp例子中,HelloWorldScene就是间接继承了Node,在init和onEnter编写初始化的相关代码来构建场景。
Node常用于编写逻辑的函数分别是init、onEnter、onExit和update。
❏init用于初始化节点,根据初始化成功与否返回true和false。
❏onEnter是节点被添加到场景中,激活时的回调,往往与onExit对应。
❏onExit是节点从场景中删除时的回调,往往与onEnter对应。
❏update是需要手动注册的一个时间相关的回调,常用于每一帧需要执行的逻辑。
在重写init、onEnter和onExit时,务必记得回调父类的方法,在onEnter和onExit回调中,慎重对父节点进行添加和移除节点的操作,当节点树在遍历的过程中被动态修改时,很容易出现崩溃。在onEnter创建和添加的东西,最好在onExit中收尾。在update中执行与距离相关逻辑时,乘以参数delta会使运行更加平滑。
6.2.1 使用Action
动作系统作为Cocos2dx非常有特色的一个系统,功能强大、使用简单是动作系统最大的特点。Action大大方便了游戏开发,为Node提供了非常丰富的运动方式,深受程序员喜爱。Node调用runAction来执行Action,在游戏中是常用的做法。Cocos2dx提供了大量内置Action,可以使用各种动作来灵活地控制Node以及让动作与逻辑结合。使用Action,只需要很简单的几行代码,就可以实现一些复杂的效果。在第10章中会介绍完整的动作系统,这里重点介绍Action和Node的关系。Node提供了一系列函数来操作Action:
//执行Action Action* runAction(Action* action); //停止所有Action void stopAllActions(); //停止指定的action void stopAction(Action* action); //停止指定Tag的一个Action,Tag可以在create Action后对Action进行设置 void stopActionByTag(int tag); //停止指定Tag的所有Action void stopAllActionsByTag(int tag); //根据Tag来获取正在执行的Action Action* getActionByTag(int tag);
Node通过调用runAction可以执行指定的Action,Action会操作Node来实现各种效果,如淡入、淡出、移动、弹跳和播放帧动画等。
一个Action只能被一个Node执行,如果多个Node希望执行一个Action,可以通过多次创建Action,也可以通过动作的clone方法来复制这个Action。
一个Node可以同时运行多个Action,通过Node提供的方法可以管理它们,但同时运行多个操作同一属性的Action会导致冲突(如同时执行一个向左和向右的Action)。
Node身上的Action是通过ActionManager进行管理的,使用ActionManager可以对Action进行更底层的管理。
6.2.2 使用Schedule
调度器Schedule是一个专门用于时间调度的模块,游戏中所有根据时间执行的逻辑都由其来驱动。节点的update更新,Action的运行更新都是基于调度器来驱动的,可以将Schedule简单理解为一个定时器。使用Action可更多地执行一些特定的、已实现行为,而Schedule则更加底层。可以使用Schedule来控制代码执行的时机,每一帧,每一秒,或者多长时间后执行。更多的细节在第17章会对调度器进行详细的介绍。
Node提供了一系列函数来操作Schedule。
(1)Schedule注册检查。
//检查selector是否已经注册到调度器 bool isScheduled(SEL_SCHEDULE selector); //检查是否有指定key的lambda函数注册到调度器 bool isScheduled(const std::string &key);
(2)默认的updateSchedule。
//将update函数以0的优先级注册到调度器中,并且每帧执行一次,不得重复注册update void scheduleUpdate(void); //和scheduleUpdate相比,增加了可以自定义优先级的功能 void scheduleUpdateWithPriority(int priority); //如果update已经注册到调度器中,注销它 void unscheduleUpdate(void); //注销所有的schedule void unscheduleAllCallbacks();
(3)注册自定义selector。
//注册一个selector成员函数,每帧执行一次 void schedule(SEL_SCHEDULE selector); //注册一个selector成员函数,每interval秒执行一次 void schedule(SEL_SCHEDULE selector, float interval); //注册一个selector成员函数,delay秒后执行一次 void scheduleOnce(SEL_SCHEDULE selector, float delay); //注册一个selector成员函数,delay秒后以每interval秒的间隔执行repeat次 void schedule(SEL_SCHEDULE selector, float interval, unsigned int repeat, float delay); //从调度器中注销selector函数 void unschedule(SEL_SCHEDULE selector);
(4)注册lambda回调selector,功能同自定义回调,但注册的不是成员函数而是lambda回调函数,用字符串key来管理。
void schedule(const std::function<void(float)>& callback, const std:: string &key); void schedule(const std::function<void(float)>& callback, float interval, const std::string &key); void scheduleOnce(const std::function<void(float)>& callback, float delay, const std::string &key); void schedule(const std::function<void(float)>& callback, float interval, unsigned int repeat, float delay, const std::string &key); void unschedule(const std::string &key);
(5)update是一个被Schedule系统优化过的方法,如果重复注册,只会生效一个。
(6)SEL_SCHEDULE 是一个原型为void Ref::fun(float)的函数,通过schedule_selector或CC_SCHEDULE_SELECTOR宏可以传入Ref对象的成员函数,如schedule_selector(MyNode::update)。schedule_selector将会被弃用。
(7)带Key的Schedule方法是为了方便使用lambda表达式,key是用于方便管理,不允许有重复的key。