在游戏中,经常会周期执行一些检测、操作或更新一些数据等,我们称之为调度。Cocos2D-x中将调度封装为类CCScheduler,方便在游戏开发中使用。我们一起来学习一下,CCScheduler具有哪些调度功能。

   从上图可知,CCScheduler直接从基类CCObject继承而来。CCScheduler的调度模式分为两种:一种是update定时器调度,它是按照优先级来进行调度,在Cocos2D-x中优先级分为三类(大于零、小于零和等于零),值越小优先级越高。另一种是按照自定义定时器调度。这种调度方式需要设定一个或多个回调函数,当定时器计时到达时,调用回调函数。
 
   CCScheduler的内部结构和功能如下:
   ·CCScheduler属性:
     //用于update定时器调度
   float m_fTimeScale:时间刻度(默认1.0f),作用是调整计时器的快慢
   struct _listEntry *m_pUpdatesNegList:优先级小于0的链表,保存调度对象
   struct _listEntry *m_pUpdates0List:优先级等于0的链表,保存调度对象
   struct _listEntry *m_pUpdatesPosList: 优先级大于0的链表,保存调度对象
   struct _hashUpdateEntry *m_pHashForUpdates:该哈希表不用于调度,只用于快速查找调度对象所在的链表
 
     //用于自定义定时器调度
   struct _hashSelectorEntry *m_pHashForSelectors:该链表用于自定义定时器调度对象容器
   struct _hashSelectorEntry *m_pCurrentTarget:该链表用于处理当前调度对象
   bool m_bCurrentTargetSalvaged:当前调度对象状态标志
   bool m_bUpdateHashLocked:调度对象更新标志
   CCArray* m_pScriptHandlerEntries:脚本调度
   ·CCScheduler方法:
     void update(float dt):调度函数
     void scheduleSelector(SEL_SCHEDULE pfnSelector, CCObject *pTarget, float fInterval, bool bPaused, unsigned int repeat, float delay):自定义调度选择器(回调函数)
     void scheduleSelector(SEL_SCHEDULE pfnSelector, CCObject *pTarget, float fInterval, bool bPaused):自定义调度选择器(自定义调度选择器(回调函数))
     void pauseTarget(CCObject *pTarget):暂停调度
     void resumeTarget(CCObject *pTarget):恢复调度
    
   以上就是CCScheduler的主要属性和方法。CCScheduler内部处理机制涉及一些编程技巧(如使用哈希表快速查找调度对象),有兴趣专研的同学也许可以从中提升编程技能。由于我们重点关注CCScheduler的功能和使用,所以不详细讲解它的处理机制。
   下面就具体讲解一下两种调试模式的使用和流程:
   ·update定时器调度:
   如果对前面介绍的概念类比较熟悉的同学,一定发现在CCDirector和CCNode的属性中都有一个CCScheduler指针。
我们看一下CCScheduler对象的创建过程:
   bool CCDirector::init(void) //CCDirector初始化函数
   {
       ...
       m_pScheduler = new CCScheduler();//这里创建CCScheduler实例
       ...
       return true;
   }
 
   CCNode::CCNode(void) //CCNode构造函数
   {
       // set default scheduler and actionManager
       CCDirector *director = CCDirector::sharedDirector();
       m_pActionManager = director->getActionManager();
       m_pActionManager->retain();
       m_pScheduler = director->getScheduler(); //引用在CCDirector中创建的CCScheduler实例
       m_pScheduler->retain(); //CCScheduler引用数加1
   }
   CCScheduler对象原来是在CCDirector的初始化过程中创建的,CCNode引用了该对象。这样,只要是CCNode继承类都能引用到CCScheduler对象。
   update定时器调度其实是调用CCScheduler的update方法实现的。这个方法又是在哪里调用的呢?看代码,如下:
   void CCDirector::drawScene(void)
   {
       // calculate "global" dt
       calculateDeltaTime();
       //tick before glClear: issue #533
       if (! m_bPaused)
       {
         m_pScheduler->update(m_fDeltaTime);
       }
       ...
   }
   它是在CCDirector绘制场景时调用的。有人可能继续追问,drawScene方法又是在哪里调用的。当然是在游戏的主循环里了。
   int CCApplication::run()
   {
    ...
    //主消息循环
    while (1)
    {
        if (! PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
            QueryPerformanceCounter(&nNow);
            if (nNow.QuadPart - nLast.QuadPart > m_nAnimationInterval.QuadPart)
            {
                nLast.QuadPart = nNow.QuadPart;
                CCDirector::sharedDirector()->mainLoop();//游戏主循环
            }
            else
            {
                Sleep(0);
            }
            continue;
        }
 
        ...
    }
    return (int) msg.wParam;
    }
   如果有人还问我CCApplication的run又是在哪里调用,我只能无语了,建议回过头再看一看介绍Cocos2D-x基本框架这一节。
   言归正传,现在CCScheduler对象有了,update也可以周期调用了,万事俱备就等着添加想要调度对象。
   那如何添加呢?不用担心,在CCNode里已经给大家准备好了。如下:
   void scheduleUpdate(void)://调度优先级默认为0
   void scheduleUpdateWithPriority(int priority):设置优先级可设置
   void CCNode::unscheduleUpdate():从调度对象中取消调度自己
   所以,被调度对象想要使用CCScheduler调度就很简单了。首先,它需要继承自CCNode,然后就是调用scheduleUpdate或scheduleUpdateWithPriority添加自己到CCScheduler调度中。
   这样就完了吗?当然不是,还有一个最重要的问题。被调度对象如何调用自己的调度方法呢?因为说白了,调度最终的目的就是周期执行本对象的调度方法,才能达到调度的目的。
   这个问题在CCScheduler的update方法里已经给出了答案,我们还是一起去看一下吧。
   void CCScheduler::update(float dt)
   {
    ...
    // updates with priority < 0
    DL_FOREACH_SAFE(m_pUpdatesNegList, pEntry, pTmp)
    {
        if ((! pEntry->paused) && (! pEntry->markedForDeletion))
        {
            pEntry->target->update(dt); //调用待调度对象的调度方法
        }
    }
    // updates with priority == 0
    DL_FOREACH_SAFE(m_pUpdates0List, pEntry, pTmp)
    {
        if ((! pEntry->paused) && (! pEntry->markedForDeletion))
        {
            pEntry->target->update(dt); //调用待调度对象的调度方法      
        }
    }
    // updates with priority > 0
    DL_FOREACH_SAFE(m_pUpdatesPosList, pEntry, pTmp)
    {
        if ((! pEntry->paused) && (! pEntry->markedForDeletion))
        {
            pEntry->target->update(dt); //调用待调度对象的调度方法          
        }
    }
    ...
   }
   这段代码比较长,读起来有点累,为大家考虑我将次要代码省略了,直奔主题。原来待调度对象的调度方法也是update。追溯一下这个update方法的来源,发现原来在基类CCObject里就已经有定义了。这就怪不得CCScheduler对象和待调度对象都有update方法。想一想,忽然豁然开朗,整个CCScheduler的调度机制其实很简单,它就是利用所有被调度对象和它都有一个共同的基类CCObject以及update方法。所以,我推测这个update方法放在CCObject里定义,可能主要目的就是为了调度。 
 
   ·自定义定时器调度:
   有了update定时器调度,为什么还需要自定义定时器调度呢?我想原因是update定时器调度只支持单一化调度。举个例子,如果在游戏中有一个节点对象,需要每隔2秒检测一下操作,每隔3秒更新一下后台数据,每隔5秒添加一些道具,这个时候使用update定时器调度就只能完成其中一项调度,而自定义定时器调度支持多定时器调度,可以很轻松和优雅的搞定这一切。
   下面看一看它是如何实现的。在介绍CCScheduler的属性和方法时,有如下两个方法:
  void scheduleSelector(SEL_SCHEDULE pfnSelector, CCObject *pTarget, float fInterval, bool bPaused, unsigned int repeat, float delay):自定义调度选择器(回调函数)

  void scheduleSelector(SEL_SCHEDULE pfnSelector, CCObject *pTarget, float fInterval, bool bPaused):自定义调度选择器(自定义调度选择器(回调函数))
  这两个方法专门用于自定义定时器调度注册函数,它的作用就是添加任意个待调度对象和方法。
  可能上面函数中的参数SEL_SCHEDULE pfnSelector看起来有点怪异,这只是一个函数指针类型重定义,如下:
  typedef void (CCObject::*SEL_SCHEDULE)(float); 
  定义这个函数指针用于指向回调函数,而回调函数在调度定时器间隔时间到达时会被调用。
  值得注意的是,使用调度函数每添加一个自定义定时器调度,将会对应产生一个定时器。
 
  自定义定时器调度同样是调用CCScheduler的update方法。现在纵使update方法的另一部分代码给出,如下:
  void CCScheduler::update(float dt)
  {
    ...
    // Interate all over the custom selectors
    for (tHashSelectorEntry *elt = m_pHashForSelectors; elt != NULL; )
    {
        m_pCurrentTarget = elt;
        m_bCurrentTargetSalvaged = false;
        if (! m_pCurrentTarget->paused)
        {
            //遍历所有调度的定时器
            for (elt->timerIndex = 0; elt->timerIndex < elt->timers->num; ++(elt->timerIndex))
            {
                elt->currentTimer = (CCTimer*)(elt->timers->arr[elt->timerIndex]);
                elt->currentTimerSalvaged = false;
                elt->currentTimer->update(dt); //定时器更新
                if (elt->currentTimerSalvaged)
                {
                    elt->currentTimer->release();
                }
                elt->currentTimer = NULL;
            }
        }
    }
    ...
  }
  //定时器更新函数
  void CCTimer::update(float dt)
  {
    if (m_fElapsed == -1)
    {
        m_fElapsed = 0;
        m_nTimesExecuted = 0;
    }
    else
    {
        if (m_bRunForever && !m_bUseDelay)
        {
            m_fElapsed += dt;  //定时器计时
            if (m_fElapsed >= m_fInterval) //判断定时器间隔时间
            {
                if (m_pTarget && m_pfnSelector)
                {
                    (m_pTarget->*m_pfnSelector)(m_fElapsed);//调用回调函数
                }
                m_fElapsed = 0;//定时器计时清零
                ...
            }
        } 
    ... 
 } 
这段代码的关键点已经注释了,应该很容易明白,就不累述。CCScheduler的自定义调度器使用非常方便,如下:
用户自定义UserClass类,继承于CCNode节点类,然后在其初始化函数onEnter中添加:
schedule(schedule_selector(UserClass::callbackSchedulerFunc1), 10.5f);
schedule(schedule_selector(UserClass::callbackSchedulerFunc2), 10.5f);
UserClass类中分别定义callbackSchedulerFunc1和callbackSchedulerFunc2回调函数,然后在函数实现代码中添加调度代码即可。
     Cocos2D-x封装的CCScheduler调度就介绍到这里,还有更为高级的调度方式(如脚本调度)和使用方法各位有兴趣可以自己研究一下。在TestCpp工程中SchedulerTest测试项有调度的各种使用实例代码,可以参考学习一下。
-------------------------------------------------------------------------------------------------------------------

注:本人在本博客的原创文章采用创作共用版权协议http://creativecommons.org/licenses/by-nc-sa/2.5/cn/), 要求署名、非商业用途和保持一致。要求署名包含注明我的网名及文章来源(我的博客地址:http://www.cnblogs.com/binbingg)。

[原创]cocos2d-x研习录-第三阶 特性之调度器的更多相关文章

  1. [原创]cocos2d-x研习录-第三阶 特性之物理引擎

    游戏物理引擎是指在游戏中涉及物理现象的逻辑处理,它用于模拟现实世界的各种物理规律(如赛车碰撞.子弹飞行.物体掉落等),让玩家能够在游戏中有真实的体验. Cocos2D-x中支持Box2D和Chipmu ...

  2. [原创]cocos2d-x研习录-第三阶 特性之粒子系统

    我想接触过游戏引擎的同学,对粒子系统应该不会陌生.它用于解决由大量按一定规则运动(变化)的微小物质在计算机上的生成和显示问题.粒子系统在游戏中有着非常广泛的应用,可以模拟很多现象,如火花.爆炸.烟雾. ...

  3. [原创]cocos2d-x研习录-第三阶 特性之瓦片地图集

    由于一张大的世界地图或背景图片往往可以由屈指可数的几种地形来表示,每种地形对应于一张小的图片,我们称这些小的地形图片为瓦片.把这些瓦片拼接在一起,组合成一个完整的地图,这就是瓦片地图集的基本原理. C ...

  4. [原创]cocos2d-x研习录-第三阶 特性之动作

    在前面的Cocos2D-x的概念类中,我们了解到节点类CCNode.导演类CCDirector.场景类CCScene.布景层类CCLayer和精灵类CCSprite等,这些类都是构成游戏画面的基本元素 ...

  5. [原创]cocos2d-x研习录-第三阶 特性之按键与虚拟键盘

    Cocos2D-x引擎支持按键事件,它能检测设备的键盘输入并处理相应的事件.而基于不同操作系统的移动设备,可供用户操作的按键数量和功能都存在差异.   Cocos2D-x使用CCKeypadDeleg ...

  6. [原创]cocos2d-x研习录-第三阶 特性之加速度传感器

    智能手机的游戏与应用中,也经常会用到加速传感器事件来丰富用户的体验,比如飞翔的企鹅(英文AirPenguin)游戏就是通过加速度传感器来控制角色的移动和跳跃方向.下面学习Cocos2D-x中如何使用加 ...

  7. [原创]cocos2d-x研习录-第三阶 特性之触屏

    游戏跟视频最大的区别就是互动,而手游(基于智能手机)主要靠触摸屏幕.重力传感和虚拟键盘等方式实现互动.这里主要记录Cocos2D-x对玩家触屏操作的处理. 在Cocos2D-x中触屏分为单点触屏和多点 ...

  8. [原创]cocos2d-x研习录-第三阶 多分辨率适配器

    在移动终端(智能手机)平台下开发游戏一般都会涉及到屏幕多分辨率适配问题,原因是手机款式多种多样,不同的款式存在有不同的尺寸,即使尺寸相同又可能存在不同的分辨率. 手机屏幕尺寸:指手机屏幕对角线长度. ...

  9. [原创]cocos2d-x研习录-第三阶 背景音乐和音效

    在游戏中,音效是一个不可或缺的部分,它可以为我们的游戏增加效果.音效在游戏中一般分为长时间的背景音乐和短促的特效音乐.Cocos2D-x支持多种常见音乐格式(mp3.wav等). Cocos2D-x提 ...

随机推荐

  1. JS刷新父窗口的几种方式

    浮层内嵌iframe及frame集合窗口,刷新父页面的多种方法   <script language=JavaScript>       parent.location.reload(); ...

  2. C语言程序设计第六次作业

    同学们,本周我们已经学完了顺序结构.选择结构和循环结构,你都理解并掌握了吗?现在就好好理理思路,做个阶段总结吧.本周的知识点总结要求大家在理解的基础上对结构化程序设计的三种基本结构做一个全面的总结. ...

  3. 单例模式(Java)

    //单例模式 public class Singleton { private static Singleton s; private Singleton(){ } public static Sin ...

  4. 领域设计之模型充血、Repository对象注入

    工作中接触了不少项目组,他们在实际的项目开发中,Domain Object的贫血模型设计,还是主要的应用的范式.原因在于,贫血模型模型设计中,把所有涉及持久化的业务逻辑,封装到了Domain Serv ...

  5. 【Python】str类方法说明

    #capitalize():字符串首字符大写 string = 'this is a string.'new_str = string.capitalize()print(new_str)#输出:Th ...

  6. hdu1000,hdu1001,hdu1002,hdu1003

    hdu1000 仅仅是为了纪念 #include <cstdio> int main() { int a,b; while (scanf("%d%d",&a,& ...

  7. java 集合:实现

    集合本来就是为了方便开发的,实现了一些基本数据结构,一般来说数据结构有两种物理的实现:数组和链表.数组是连续的空间,链表是不连续的.基于这两种又扩展了很多的数据结构.队列,栈,hash表,树. 在ja ...

  8. 字符编码详解及由来(UNICODE,UTF-8,GBK)[转帖]

    相信許多人對字符編碼都不是很了解,透過下文可以清晰的理解各种字符编码方式详解及由来. 一直对字符的各种编码方式懵懵懂懂,什么ANSI.UNICODE.UTF-8.GB2312.GBK.DBCS.UCS ...

  9. 上下文管理、线程池、redis订阅和发布

    一:上下文管理: 对于一些对象在使用之后,需要关闭操作的.比如说:socket.mysql数据库连接.文件句柄等. 都可以用上下文来管理. 语法结构: Typical usage: @contextm ...

  10. robotframework接口测试初探2

    python这个requests模块常被用来测试接口.使用RequestLibrary库测试之前,先来看下这个模块是怎样使用的 最简单的调用是 r=requests.get("http:// ...