精通Cocos2d-x游戏开发(基础卷)
上QQ阅读APP看书,第一时间看更新

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。