重开发人员的劳动成果,转载的时候请务必注明出处:http://blog.csdn.net/haomengzhu/article/details/30478251

Cocos2d 的一大特色就是提供了事件驱动的游戏框架,
引擎会在合适的时候调用事件处理函数。我们仅仅须要在函数中加入对各种游戏事件的处理,
就能够完毕一个完整的游戏了。
比如,为了实现游戏的动态变化。Cocos2d 提供了两种定时器事件。
为了响应用户输入,Cocos2d 提供了触摸事件和传感器事件;
此外,Cocos2d 还提供了一系列控制程序生命周期的事件。


Cocos2d 的调度原理管理着全部的事件,Cocos2d 已经为我们隐藏了游戏主循环的实现。

首先来看看游戏实现的原理:
游戏乃至图形界面的本质是不断地画图。然而画图并非任意的。不论什么游戏都须要遵循一定的规则来呈现出来,这些规则就体现为游戏逻辑。游戏逻辑会控制游戏内容。使其依据用户输入和时间流逝而改变。

因此,游戏能够抽象为不断地反复下面动作:
处理用户输入 ;
处理定时事件 ;
画图 。

游戏主循环就是这种一个循环,它会重复运行以上动作,保持游戏进行下去,直到玩家退出游戏。

在 Cocos2d-x 3.0 中,以上的动作包括在 Director 的某个方法之中,而引擎会依据不同的平台设法使系统不断地调用这种方法,从而完毕了游戏主循环。

在cocos2d-x 3.0中。Director 包括一个管理引擎逻辑的方法,它就是 Director::mainLoop()方法。
这种方法负责调用定时器,画图,发送全局通知,并处理内存回收池。
该方法按帧调用, 每帧调用一次。而帧间间隔取决于两个因素,一个是预设的帧率,默觉得 60 帧每秒;
还有一个是每帧的计算量大小。

当逻辑 处理与画图计算量过大时,设备无法完毕每秒 60 次绘制。此时帧率就会减少。 

mainLoop()方法会被定时调用。然而在不同的平台下它的调用者不同。
通常 Application 类负责处理平台相关的任务,当中就包括了对 mainLoop()的调用。
不同的平台详细实现也不同样,详细可參考cocos\2d\platform文件夹;

mainLoop()方法是定义在 Director 中的抽象方法。它的实现位于同一个文件里的 DisplayLinkDirector类中;
virtual void mainLoop() = 0;
详细实现是:
void DisplayLinkDirector::mainLoop()
{
if (_purgeDirectorInNextLoop)
{
_purgeDirectorInNextLoop = false;
purgeDirector();
}
else if (! _invalid)
{
drawScene(); // release the objects
//释放资源对象
PoolManager::getInstance()->getCurrentPool()->clear();
}
}
上述代码主要包括例如以下 3 个步骤。
1、推断是否须要释放 Director,假设须要,则删除 Director 占用的资源。通常,游戏结束时才会运行这个步骤。

2、调用 drawScene()方法,绘制当前场景并进行其它必要的处理。

3、弹出自己主动回收池。使得这一帧被放入自己主动回收池的对象所有释放。

mainLoop()把内存管理以外的操作都交给了 drawScene()方法,因此关键的步骤都在 drawScene()方法之中;
再来看看drawScene方法:
void Director::drawScene()
{
// calculate "global" dt
//计算全局帧间时间差 dt
calculateDeltaTime(); // skip one flame when _deltaTime equal to zero.
if(_deltaTime < FLT_EPSILON)
{
return;
} if (_openGLView)
{
_openGLView->pollInputEvents();
} //tick before glClear: issue #533
if (! _paused)
{
_scheduler->update(_deltaTime);
_eventDispatcher->dispatchEvent(_eventAfterUpdate);
} glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); /* to avoid flickr, nextScene MUST be here: after tick and before draw.
XXX: Which bug is this one. It seems that it can't be reproduced with v0.9 */
if (_nextScene)
{
setNextScene();
} kmGLPushMatrix(); // global identity matrix is needed... come on kazmath!
kmMat4 identity;
kmMat4Identity(&identity); // draw the scene
//绘制场景
if (_runningScene)
{
_runningScene->visit(_renderer, identity, false);
_eventDispatcher->dispatchEvent(_eventAfterVisit);
} // draw the notifications node
//处理通知节点
if (_notificationNode)
{
_notificationNode->visit(_renderer, identity, false);
} if (_displayStats)
{
showStats();
} _renderer->render();
_eventDispatcher->dispatchEvent(_eventAfterDraw); kmGLPopMatrix(); _totalFrames++; // swap buffers
//交换缓冲区
if (_openGLView)
{
_openGLView->swapBuffers();
} if (_displayStats)
{
calculateMPF();
}
}
能够分析出:
在主循环中,我们主要进行了下面 3 个操作。
1、调用了定时调度器的 update 方法,引发定时器事件。
2、假设场景须要被切换,则调用 setNextStage 方法。在显示场景前切换场景。

3、调用当前场景的 visit 方法,绘制当前场景。


在游戏主循环 drawScene 方法中。我们能够看到每一帧引擎都会调用 _scheduler的 update 方法。

【Scheduler *_scheduler;】_scheduler 是 Scheduler 类型的对象,是一个定时调度器。

所谓定时调度器,就是一个管理全部节点定时器的对象。 
它负责记录定时器。并在合适的时间触发定时事件。


再来分析一下定时器的情况:
Cocos2d-x 提供了两种定时器,各自是:
update 定时器。每一帧都被触发。使用 scheduleUpdate 方法来启用。
schedule 定时器,能够设置触发的间隔,使用 schedule 方法来启用。
看下Node中的实现:
void Node::scheduleUpdateWithPriority(int priority)
{
_scheduler->scheduleUpdate(this, priority, !_running);
} 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);
}
当中 _scheduler是 Scheduler 对象。
能够看到。这两个方法的内部除去检查參数是否合法,仅仅是调用了 Scheduler提供的方法。
换句话说,Node 提供的定时器仅仅是对 Scheduler 的包装而已。
不仅这两个方法如此,其它定时器相关的方法也都是这样。

Scheduler的分析
经过上面的分析,我们已经知道 Node 提供的定时器不是由它本身而是由 Scheduler管理的。

因此,我们把注意力转移到定时调度器上。

显而易见。定时调度器应该对每个节点维护一个定时器列表。在恰当的时候就会触发其定时事件。

Scheduler的主要成员请查看:cocos\2d\CCScheduler.h

为了注冊一个定时器,开发人员仅仅要调用调度器提供的方法就可以。

同一时候调度器还提供了一系列对定时器的控制接口。比如暂停和恢复定时器。

在调度器内部维护了多个容器,用于记录每一个节点注冊的定时器;
同一时候。调度器会接受其它组件(通常 与平台相关)的定时调用。随着系统时间的改变驱动调度器。


调度器能够随时增删或改动被注冊的定时器。
详细来看,调度器将 update 定时器与普通定时器分别处理:
当某个节点注冊 update 定时器时,调度器就会把节点加入到 Updates 容器中,
即struct _hashUpdateEntry *_hashForUpdates里面;
为了提高调度器效率。Cocos2d-x 使用了散列表与链表结合的方式来保存定时器信息;
当某个节点注冊普通定时器时,调度器会把回调函数和其它信息保存到 Selectors 散列表中。
即struct _hashSelectorEntry *_hashForTimers里面。

在游戏主循环中。我们已经见到了 update 方法。

能够看到,游戏主循环会不停地调用 update 方法。
该方法包括一个实型參数,表示两次调用的时间间隔。

在该方法中,引擎会利用两次调用的间隔来计算何时触发定时器。

我们再来分析下 update 方法的工作流程:
// main loop
void Scheduler::update(float dt)
{
_updateHashLocked = true; //a.预处理
if (_timeScale != 1.0f)
{
dt *= _timeScale;
} //
// Selector callbacks
// // Iterate over all the Updates' selectors
//b.枚举全部的 update 定时器
tListEntry *entry, *tmp; // updates with priority < 0
//优先级小于 0 的定时器
DL_FOREACH_SAFE(_updatesNegList, entry, tmp)
{
if ((! entry->paused) && (! entry->markedForDeletion))
{
entry->callback(dt);
}
} // updates with priority == 0
//优先级等于 0 的定时器
DL_FOREACH_SAFE(_updates0List, entry, tmp)
{
if ((! entry->paused) && (! entry->markedForDeletion))
{
entry->callback(dt);
}
} // updates with priority > 0
//优先级大于 0 的定时器
DL_FOREACH_SAFE(_updatesPosList, entry, tmp)
{
if ((! entry->paused) && (! entry->markedForDeletion))
{
entry->callback(dt);
}
} // Iterate over all the custom selectors
//c.枚举全部的普通定时器
for (tHashTimerEntry *elt = _hashForTimers; elt != nullptr; )
{
_currentTarget = elt;
_currentTargetSalvaged = false; if (! _currentTarget->paused)
{
// The 'timers' array may change while inside this loop
//枚举此节点中的全部定时器
//timers 数组可能在循环中改变,因此在此处须要小心处理
for (elt->timerIndex = 0; elt->timerIndex < elt->timers->num; ++(elt->timerIndex))
{
elt->currentTimer = (Timer*)(elt->timers->arr[elt->timerIndex]);
elt->currentTimerSalvaged = false; elt->currentTimer->update(dt); if (elt->currentTimerSalvaged)
{
// The currentTimer told the remove itself. To prevent the timer from
// accidentally deallocating itself before finishing its step, we retained
// it. Now that step is done, it's safe to release it.
elt->currentTimer->release();
} elt->currentTimer = nullptr;
}
} // elt, at this moment, is still valid
// so it is safe to ask this here (issue #490)
elt = (tHashTimerEntry *)elt->hh.next; // only delete currentTarget if no actions were scheduled during the cycle (issue #481)
if (_currentTargetSalvaged && _currentTarget->timers->num == 0)
{
removeHashElement(_currentTarget);
}
} // delete all updates that are marked for deletion
// updates with priority < 0
//d.清理全部被标记了删除记号的 update 方法
//优先级小于 0 的定时器
DL_FOREACH_SAFE(_updatesNegList, entry, tmp)
{
if (entry->markedForDeletion)
{
this->removeUpdateFromHash(entry);
}
} // updates with priority == 0
//优先级等于 0 的定时器
DL_FOREACH_SAFE(_updates0List, entry, tmp)
{
if (entry->markedForDeletion)
{
this->removeUpdateFromHash(entry);
}
} // updates with priority > 0
//优先级大于 0 的定时器
DL_FOREACH_SAFE(_updatesPosList, entry, tmp)
{
if (entry->markedForDeletion)
{
this->removeUpdateFromHash(entry);
}
} _updateHashLocked = false;
_currentTarget = nullptr; #if CC_ENABLE_SCRIPT_BINDING
//
// Script callbacks
// // Iterate over all the script callbacks
//e.处理脚本引擎相关的事件
if (!_scriptHandlerEntries.empty())
{
for (auto i = _scriptHandlerEntries.size() - 1; i >= 0; i--)
{
SchedulerScriptHandlerEntry* eachEntry = _scriptHandlerEntries.at(i);
if (eachEntry->isMarkedForDeletion())
{
_scriptHandlerEntries.erase(i);
}
else if (!eachEntry->isPaused())
{
eachEntry->getTimer()->update(dt);
}
}
}
#endif
//
// Functions allocated from another thread
// // Testing size is faster than locking / unlocking.
// And almost never there will be functions scheduled to be called.
if( !_functionsToPerform.empty() ) {
_performMutex.lock();
// fixed #4123: Save the callback functions, they must be invoked after '_performMutex.unlock()', otherwise if new functions are added in callback, it will cause thread deadlock.
auto temp = _functionsToPerform;
_functionsToPerform.clear();
_performMutex.unlock();
for( const auto &function : temp ) {
function();
} }
}
借助凝视。可以看出 update 方法的流程大致例如以下所看到的。

1、參数 dt 乘以一个缩放系数,以改变游戏全局的速度,当中缩放系数能够由 Scheduler的TimeScale属性设置。

2、分别枚举优先级小于 0、等于 0、大于 0 的 update 定时器。
假设定时器没有暂停,也没有被标记为即将删除,则触发定时器。
3、枚举全部注冊过普通定时器的节点,再枚举该节点的定时器。调用定时器的更新方法,从而决定是否触发该定时器。
4、再次枚举优先级小于 0、等于 0、大于 0 的 update 定时器,移除前几个步骤中被标记了删除记号的定时器。
我们暂不关心脚本引擎相关的处理。 

对于 update 定时器来说,每一节点仅仅可能注冊一个定时器,
因此调度器中存储定时器数据的结构体tListEntry *entry主要保存了注冊者与优先级。
对于普通定时器来说,每个节点能够注冊多个定时器,
引擎使用回调函数(选择器)来区分同一节点下注冊的不同定时器。

调度器为每个定时器创建了一个 Timer 对象,
它记录了定时器的目标、回调函数、触发周期、反复触发还是仅触发一次等属性。


Timer 也提供了 update 方法。它的名字和參数都与 Scheduler 的 update 方法一样,
并且它们也都须要被定时调用。
不同的是。Timer 的 update 方法会把每一次调用时接收的时间间隔 dt 积累下来,
假设经历的时间达到了周期就会引发定时器的定时事件。 
第一次引发了定时事件后,假设是仅触发一次的定时器。
则 update 方法会中止,否则定时器会又一次计时,从而重复地触发定时事件。


来看看Timer的update方法:
void Timer::update(float dt)
{
if (_elapsed == -1)
{
_elapsed = 0;
_timesExecuted = 0;
}
else
{
if (_runForever && !_useDelay)
{//standard timer usage
_elapsed += dt;
if (_elapsed >= _interval)
{
trigger(); _elapsed = 0;
}
}
else
{//advanced usage
_elapsed += dt;
if (_useDelay)
{
if( _elapsed >= _delay )
{
trigger(); _elapsed = _elapsed - _delay;
_timesExecuted += 1;
_useDelay = false;
}
}
else
{
if (_elapsed >= _interval)
{
trigger(); _elapsed = 0;
_timesExecuted += 1; }
} if (!_runForever && _timesExecuted > _repeat)
{ //unschedule timer
cancel();
}
}
}
}

再次回到 Scheduler 的 update 方法上来。

在步骤 c 中。程序首先枚举了每个注冊过定时器的对象,然后再枚举对象中定时 器相应的 Timer 对象。
调用 Timer 对象的 update 方法来更新定时器状态,以便触发定时事件。


至此。我们能够看到事件驱动的普通定时器调用顺序为:
系统的时间事件驱动游戏主循环,游戏主循环调用 Scheduler 的 update 方法,Scheduler 调用普通定时器相应的 Timer 对象的 update 方法。Timer
类的 update 方法调用定时器 相应的回调函数。

对于 update 定时器,调用顺序更为简单。因此前面仅列出了普通定时器的调用顺序。 
同一时候,我们也能够看到,在定时器被触发的时刻,Scheduler 类的 update 方法正在迭代之中,
开发人员全然可能在定时器 事件中启用或停止其它定时器。
只是。这么做会导致 update 方法中的迭代被破坏。

Cocos2d-x 的设计已经考虑到了这个问题,採用了一些技巧避免迭代被破坏。
比如,update 定时器被删除时,不会直接删除,而是标记为将要删除。在定时器迭代完成后再清理被标记的定时器,这样就可以保证迭代的正确性。


Cocos2d-x 的设计使得非常多离散在各处的代码通过事件联系起来,在每一帧中起作用。
基于事件驱动的游戏框架易于掌握。使用灵活,并且全部事件串行地在同一线程中运行,不会出现线程同步的问题。


能够看到,Cocos2d-x是多么的强大!!。
小伙伴们,知道cococs2d-x是怎么调度了咩!。
咩!


郝萌主友情提示:
多看看源代码,你就能更了解cocos2d-x了、、、

18、Cocos2dx 3.0游戏开发找小三之cocos2d-x,请问你是怎么调度的咩的更多相关文章

  1. 6、Cocos2dx 3.0游戏开发找小三之游戏的基本概念

    重开发人员的劳动成果,转载的时候请务必注明出处:http://blog.csdn.net/haomengzhu/article/details/27689713 郝萌主友情提示: 人是习惯的产物,当你 ...

  2. 13、Cocos2dx 3.0游戏开发找小三之3.0中的Director :郝萌主,一统江湖

    重开发人员的劳动成果.转载的时候请务必注明出处:http://blog.csdn.net/haomengzhu/article/details/27706967 游戏中的基本元素 在曾经文章中.我们具 ...

  3. 1、Cocos2dx 3.0游戏开发找小三之前言篇

    尊重开发人员的劳动成果,转载的时候请务必注明出处:http://blog.csdn.net/haomengzhu/article/details/27094663 前言 Cocos2d-x 是一个通用 ...

  4. 3、Cocos2dx 3.0游戏开发找小三之搭建开发环境

    尊重开发人员的劳动成果.转载的时候请务必注明出处:http://blog.csdn.net/haomengzhu/article/details/27107295 搭建开发环境 使用 Cocos2d- ...

  5. 12、Cocos2dx 3.0游戏开发找小三之3.0中的生命周期分析

    重开发人员的劳动成果.转载的时候请务必注明出处:http://blog.csdn.net/haomengzhu/article/details/27706303 生命周期分析 在前面文章中我们执行了第 ...

  6. 23、Cocos2dx 3.0游戏开发找小三之粒子系统:你那里下雪了吗?

    重开发人员的劳动成果.转载的时候请务必注明出处:http://blog.csdn.net/haomengzhu/article/details/30485919 春雨惊春清谷天,夏满芒夏暑相连, 秋处 ...

  7. 19、Cocos2dx 3.0游戏开发找小三之Action:流动的水没有形状,漂流的风找不到踪迹、、、

    重开发人员的劳动成果.转载的时候请务必注明出处:http://blog.csdn.net/haomengzhu/article/details/30478985 流动的水没有形状.漂流的风找不到踪迹. ...

  8. 7、Cocos2dx 3.0游戏开发找小三之3.0版本号的代码风格

    重开发人员的劳动成果,转载的时候请务必注明出处:http://blog.csdn.net/haomengzhu/article/details/27691337 Cocos2d-x代码风格 前面我们已 ...

  9. 4、Cocos2dx 3.0游戏开发找小三之Hello World 分析

    尊重开发人员的劳动成果.转载的时候请务必注明出处:http://blog.csdn.net/haomengzhu/article/details/27186557 Hello World 分析 打开新 ...

随机推荐

  1. python3 多线程获取数据实例

    import requestsimport jsonfrom retrying import retryfrom lxml import etreefrom queue import Queueimp ...

  2. 【深度学习笔记】(一)TensorFlow安装及环境搭建

    在学习了一段时间台大李宏毅关于deep learning的课程,以及一些其他机器学习的书之后,终于打算开始动手进行一些实践了. 感觉保完研之后散养状态下,学习效率太低了,于是便想白天学习,晚上对白天学 ...

  3. ASP.NET Core 异常处理与日志记录

    1. ASP.NET Core 异常处理与日志记录 1.1. 异常处理 1.1.1. 异常产生的原因及处理 1.1.2. ASP.NET Core中启动开发人员异常页面 1.2. 日志记录 1.2.1 ...

  4. 基础教程:视图中的ASP.NET Core 2.0 MVC依赖注入

    问题 如何在ASP.NET Core MVC Views中注入和使用服务. 解 更新 启动 类来为MVC添加服务和中间件. 添加一项服务 添加一个Controller,返回 ViewResult. 添 ...

  5. curl安装

    问题1: curl: error while loading shared libraries: libcurl.so.4: cannot open shared object file: No su ...

  6. 【JAVA零基础入门系列】Day15 对象的比较

    最近一直有事,博客也停笔了一段时间,十分抱歉. 这一篇主要讲讲对象的比较,什么是对象的比较,我们知道两个数值类型只需要用"=="符号即可进行相等判断,但如果是两个Goods对象呢? ...

  7. platform 收集linux/windows操作系统信息

    调用python的platform模块 #!/usr/bin/evn python #_*_ coding:utf-8 -*- import platform print "######## ...

  8. 用JavaScript实现表格编辑器

    实现效果: htm l代码: <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> ...

  9. RecyclerView.ItemDecoration

    decoration 英文意思: 英[ˌdekəˈreɪʃn] 美[ˌdɛkəˈreʃən] n. 装饰品; 装饰,装潢; 装饰图案,装饰风格; 奖章; [例句]The decoration and ...

  10. ASP.NET异常处理机制

    try{ //获取并使用资源,可能出现异常}catch(DivideByZeroException de){}catch(ArithmeticException ae){}catch(Exceptio ...