重开发人员的劳动成果,转载的时候请务必注明出处: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. PHP面试随笔

    1.常见的HTTP状态码: 1xx系列:代表请求已被接受,需要继续处理 2xx系列:代表请求已成功被服务器接收.理解并接受 200:表示请求已成功,请求所希望的响应头或数据体将随此响应返回 201:表 ...

  2. Java 内存区域划分 备忘录

    最近看了<深入理解虚拟机>的内存分配与管理这部分的内容,这里做一个的总结,以加深我对知识点的理解,如有错误的地方,还望大神们指出,我及时更正:  内存区域划分 首先是下面这幅图: 图 1. ...

  3. 前端如何处理emoji表情

    这段时间在做移动端的开发, 有一个功能就是发表评论,其实这个功能本身是比较简单的, 但是在提测是的时候QA给哦提了一个bug,说输入手机自带的emoji表情发送失败了.我就奇怪了,emoji表情也是文 ...

  4. java多线程系列(九)---ArrayBlockingQueue源码分析

    java多线程系列(九)---ArrayBlockingQueue源码分析 目录 认识cpu.核心与线程 java多线程系列(一)之java多线程技能 java多线程系列(二)之对象变量的并发访问 j ...

  5. 详谈Javascript类与继承

    本文将从以下几方面介绍类与继承 类的声明与实例化 如何实现继承 继承的几种方式 类的声明与实例化 类的声明一般有两种方式 //类的声明 var Animal = function () { this. ...

  6. C# 通过url地址获取页面内容

    using System.Net; using System.IO; HttpWebRequest request = (HttpWebRequest)WebRequest.Create(" ...

  7. 人体姿态的相似性评价基于OpenCV实现最近邻分类KNN K-Nearest Neighbors

    最近学习了人体姿态的相似性评价.需要用到KNN来统计与当前姿态相似的k个姿态信息. 假设我们已经有了矩阵W和给定的测试样本姿态Xi,需要寻找与Xi相似的几个姿态,来估计当前Xi的姿态标签. //knn ...

  8. jsp运行原理及运行过程

    JSP的执行过程主要可以分为以下几点: 1)客户端发出请求. 2)Web容器将JSP转译成Servlet源代码. 3)Web容器将产生的源代码进行编译. 4)Web容器加载编译后的代码并执行. 5)把 ...

  9. leecode -- 3sum Closet

    Given an array S of n integers, find three integers in S such that the sum is closest to a given num ...

  10. shell自动化巡检

    #!/bin/bash#主机信息每日巡检 IPADDR=$(ifconfig eth0|grep 'inet addr'|awk -F '[ :]' '{print $13}')#环境变量PATH没设 ...