Cocos2dx学习笔记(11)——定时器

有了上一章的游戏主循环的基础,那么便让我们进一步学习每一帧的处理。

本文仅供个人记录和复习,不用于其他用途

基本概念

定时器又称调度器,Cocos2d-x调度器为游戏提供定时事件和定时调用服务。所有Node对象都知道如何调度和取消调度事件,使用调度器有几个好处:

  • 每当Node不再可见或已从场景中移除时,调度器会停止
  • Cocos2d-x暂停时,调度器也会停止。当Cocos2d-x重新开始时,调度器也会自动继续启动
  • Cocos2d-x封装了一个供各种不同平台使用的调度器,使用此调度器你不用关心和跟踪你所设定的定时对象的销毁和停止,以及崩溃的风险

简单点来说,假如游戏中发生了碰撞事件,那么为了检测到这个事件,你需要根据时间来做出相应的判断。那么使用定时器,便能够让游戏更好地处理动态事件。在开发中我们经常会使用到如下3个定时器(这里我并没有使用官方的名词):

  • 帧循环定时器
  • 自定义定时器
  • 一次性定时器

帧循环定时器

帧循环定时器又称默认调度器,该调度器是使用Node的刷新事件update()方法,该方法在每帧绘制之前都会被调用一次。由于每帧之间时间间隔较短,所以每帧刷新一次已足够完成大部分游戏过程中需要的逻辑判断。Cocos2d-x中Node默认是没有启用update()的,因此你需要重载update()方法来执行自己的逻辑代码。通过执行schedulerUpdate()调度器每帧执行update(),如果需要停止这个调度器,可以使用unschedulerUpdate()

我们需要先在头文件中重写update()

1
void update(float dt) override;

然后再在.cpp文件中实现并且调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
bool T07Schedule::init()
{
// 填写自己的初始化代码
scheduleUpdate();
return true;
}
void T07Schedule::update(float dt)
{
CCLOG("dt = %g", dt);
static int i = 0;
i++;
if (i == 100)
{
unscheduleUpdate();
CCLOG("schedule is over");
}
}

从上述代码可以看出,我们在init()方法中调用了scheduleUpdate(),然后对update()方法进行了实现。update()方法对dt的值打印了100次,然后结束了定时器,另外,这里的CCLOG用于输出控制台信息。可能有人会问dt是什么,其实dt就是每帧的时间,我们这里用的是默认帧率,也就是60帧每秒,换算一下就是1帧0.0166秒。

自定义定时器

游戏开发中,在某些情况下我们可能不需要频繁的进行逻辑检测,这样可以提高游戏性能。所以Cocos2d-x还提供了自定义调度器,可以实现以一定的时间间隔连续调用某个函数。由于引擎的调度机制,自定义时间间隔必须大于两帧的间隔,否则两帧内的多次调用会被合并成一次调用。所以自定义时间间隔应在0.1秒以上。

另外,取消该调度器可以用这个方法:

1
unschedule(SEL_SCHEDULE selector, float delay);

至于具体的实现,我们首先要在头文件中声明update()方法(与默认调度器不同,名字可以随意取):

1
void myschedule(float dt);

然后在.cpp文件中实现这个方法:

1
2
3
4
5
6
7
8
9
10
11
bool T07Schedule::init()
{
// 填写自己的初始化代码
schedule(schedule_selector(T07Schedule::myschedule), 1, 10, 4);
return true;
}
void T07Schedule::myschedule(float dt)
{
...
}

事实上自定义定时器有四个参数,分别是回调函数、间隔时间、执行次数、延时。上面代码中的定时器,就是在4s以后,每隔1s执行一次回调函数,一共执行10次。查看CCNode源文件,会发现自定义定时器进行了重载:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void Node::schedule(SEL_SCHEDULE selector)
{
this->schedule(selector, 0.0f, CC_REPEAT_FOREVER, 0.0f);
}
void Node::schedule(SEL_SCHEDULE selector, float interval)
{
this->schedule(selector, interval, CC_REPEAT_FOREVER, 0.0f);
}
void Node::schedule(SEL_SCHEDULE selector, float interval, unsigned int repeat, float delay)
{
CCASSERT( selector, "Argument must be non-nil");
CCASSERT( interval >=0, "Argument must be positive");
_scheduler->schedule(selector, this, interval , repeat, delay, !_running);
}

仔细看看会发现,其实无论你是用哪一个,它们都会调用四个参数的重载方法。另外,解释一下这个CC_REPEAT_FOREVER,意思就是无限循环。

一次性定时器

游戏中某些场合,你只想进行一次逻辑检测,Cocos2d-x同样提供了单次调度器。这个一次性的定时器只会触发一次,所以一般也用不到取消方法。不过呢,如果非要使用,那么这个取消方法和自定义定时器的一样,但是这个用的很少,因为本来就只触发一次,没必要取消。

1
scheduleOnce(schedule_selector(T07Schedule::updateOnce), 0.1f);

上面这个就是一次性定时器方法,与自定义定时器相比只不过把名字换成了scheduleOnce,而且只执行一次,因此代码实现也和自定义定时器类似。

选择器

小伙伴们可能对schedule_selector()这个宏产生了疑问,查看CCRef文件可以看到如下的定义:

1
#define schedule_selector(_SELECTOR) CC_SCHEDULE_SELECTOR(_SELECTOR)

好吧,那么CC_SCHEDULE_SELECTOR()又是什么呢,接着往上找:

1
#define CC_SCHEDULE_SELECTOR(_SELECTOR) static_cast<cocos2d::SEL_SCHEDULE>(&_SELECTOR)

我们可以看到,CC_SCHEDULE_SELECTOR其实就是SEL_SCHEDULE,这里使用了C++的static_cast强制转换。一般来说,static_cast用于指针的强制转换,那么接着往上找:

1
typedef void (Ref::*SEL_SCHEDULE)(float);

看到这里,就需要比较良好的C++基础。关于typedef有很多陷阱,详情请参考我的C++分类博客。SEL_SCHEDULE代替的是一个类成员函数指针,更多的细节这里不再多说,以后再进行补充。