上一章我们完整的跟了一遍HelloWorld的源码,了解了Cocos2d-x的启动流程。其中Director这个类贯穿了整个Application程序,这章随小鱼一起把这个类分析透彻。
小鱼的阅读源码的习惯是,一层层地分析代码,在阅读Director这个类的时候,碰到了很多其它的Cocos2d-x类,我的方式是先大概了解一下类的作用,完整的去了解Director类,之后再去按照重要程度去分析碰到的其它类。
一点一点分析 CCDirector.h
@H_
404_17@@H_
502_18@#ifndef __CCDIRECTOR_H__
#define __CCDIRECTOR_H__@H_
502_18@
#include
"CCPlatformMacros.h"@H_
502_18@
#include
"CCRef.h"@H_
502_18@
#include
"ccTypes.h"@H_
502_18@
#include
"CCGeometry.h"@H_
502_18@
#include
"CCVector.h"@H_
502_18@
#include
"CCGL.h"@H_
502_18@
#include
"CCLabelAtlas.h"@H_
502_18@
#include
"kazmath/mat4.h"@H_
502_18@
NS_CC_BEGIN
/**
* @addtogroup base_nodes
* @{
*/
/* Forward declarations. */
class@H_
502_18@ LabelAtlas;
class@H_
502_18@ Scene;
class@H_
502_18@ GLView;
class@H_
502_18@ DirectorDelegate;
class@H_
502_18@ Node;
class@H_
502_18@ Scheduler;
class@H_
502_18@ ActionManager;
class@H_
502_18@ EventDispatcher;
class@H_
502_18@ EventCustom;
class@H_
502_18@ EventListenerCustom;
class@H_
502_18@ TextureCache;
class@H_
502_18@ Renderer;
#if (CC_TARGET_PLATFORM != CC_PLATFORM_WINRT) && (CC_TARGET_PLATFORM != CC_PLATFORM_WP8)
class@H_
502_18@ Console;
#endif
从ccdirector.h的包含文件和引用的类来看,我们可以看到Director类都管些什么,做个初步了解。
管理的有 Label(标签) 、Scene(场景)、 GLView(OpenGL渲染) 、Node(结点?不知道是什么玩意后面我们再仔细分析)、Scheduler(程序调度)、ActionManager(动画管理)、EventDispatcher(事件管理)、EventCuston(也和事件有关)、EventListenerCuston(事件侦听有关系)、TextureCache(纹理缓存)、Renderer(渲染器)、Console(控制台)
这个大管家管了这么多东西,后面的章节我来逐个分析这些东西是什么,现在只要不阻碍分析Director这个大管家类,可以暂时不用理会其它类的实现的具体内容。
继续往下看Director类的具体定义
@H_
404_17@
class CC_DLL Director :
public Ref
Director类继承了 Ref类,参看Ref类的定义可以大体了解到是一个用来做引用记数的类,相关还有PoolManager,AutoreleasePool,等,从命名可以了解cocos2d-x有自己的内存管理机制,用到了引用记数来确定对象是否应该释放,相应的有管理类来控制。后面我们单独去分析coocs2d-x的内存管理。在这里只知道Director类也是由统一的内存管理器来控制的。
下面看一下Director类的公有函数
@H_
404_17@
static const char *@H_
502_18@EVENT_PROJECTION_CHANGED;
static const char*@H_
502_18@ EVENT_AFTER_UPDATE;
static const char*@H_
502_18@ EVENT_AFTER_VISIT;
static const char* EVENT_AFTER_DRAW;
@H_
404_17@
const char *Director::EVENT_PROJECTION_CHANGED =
"director_projection_changed"@H_
502_18@;
const char *Director::EVENT_AFTER_DRAW =
"director_after_draw"@H_
502_18@;
const char *Director::EVENT_AFTER_VISIT =
"director_after_visit"@H_
502_18@;
const char *Director::EVENT_AFTER_UPDATE =
"director_after_update";
最开始定义了几个事件Director类的事件类型, 依次是 工程类型改变,draw(渲染) visit(访问) update(更新)之后的事件 可猜测 当 draw visit update之后director可抛出相应事件外部捕获后进后自己的处理。
@H_
404_17@
enum class@H_
502_18@ Projection
{
/// sets a 2D projection (orthogonal projection)
@H_
502_18@ _2D,
/// sets a 3D projection with a fovy=60,znear=0.5f and zfar=1500.
@H_
502_18@ _3D,
/// it calls "updateProjection" on the projection delegate.
@H_
502_18@ CUSTOM,
/// Default projection is 3D projection
DEFAULT =@H_
502_18@ _3D,};
这个枚举定义了工程类型 有 2D 3D 和自定义,默认为3D游戏类型。
@H_
404_17@
/** returns a shared instance of the director */
static Director*@H_
502_18@ getInstance();
/** @deprecated Use getInstance() instead */@H_
502_18@
CC_DEPRECATED_ATTRIBUTE
static Director* sharedDirector() {
return@H_
502_18@ Director::getInstance(); }
/**
* @js ctor
*/@H_
502_18@
Director(
void@H_
502_18@);
/**
* @js NA
* @lua NA
*/
virtual ~Director();
这段代码可以知道 Director也是单例创建型。并且提供了外部得到实例的接口sharedDirector;
@H_
404_17@
virtual bool init();
init整个director对象的初始化工作都在这里面。这个函数很重要,我们稍后单独分析它
@H_502_18@后面代码里很多方法注释里面已经描述的很详细了,这里我们简单过一遍,大多是些Get Set的方法。
@H_
404_17@
/** 得到director当前正在运行的场景,director同一时间只能有一个场景在运行*/@H_
502_18@
inline Scene* getRunningScene() {
return@H_
502_18@ _runningScene; }
/** 得到动画的帧速率*/@H_
502_18@
inline
double getAnimationInterval() {
return@H_
502_18@ _animationInterval; }
/** 设置动画的帧频,这里看到这是一个纯虚函数,所以Director是一个抽象类,不能被实例化,使用的时候必须继承这个类开实现自己的Director. */
virtual void setAnimationInterval(
double interval) =
0@H_
502_18@;
/** 询问是否在左下角显示帧频,我们看helloworld里面有一个fps显示,这里应该就是控制显示fps的地方 */@H_
502_18@
inline
bool isDisplayStats() {
return@H_
502_18@ _displayStats; }
/** 设置是否要在左下角显示帧频*/@H_
502_18@
inline
void setDisplayStats(
bool displayStats) { _displayStats =@H_
502_18@ displayStats; }
/** 得到每一帧消耗时间多少秒 如每秒60的帧频那么这个返回值就是 1/60秒*/@H_
502_18@
inline
float getSecondsPerFrame() {
return _secondsPerFrame; }
@H_
404_17@
/** 得到封装OpenGl操作的对象GLView的接口
* @js NA
* @lua NA
*/@H_
502_18@
inline GLView* getOpenGLView() {
return@H_
502_18@ _openGLView; }
void setOpenGLView(GLView *openGLView);
纹理缓存的对象
@H_
404_17@TextureCache* getTextureCache()
const;
下面几个函数是用来控制游戏循环中帧与帧之间的时间间隔的,其中涉及到两个成员变量
@H_
404_17@
/* 标记是否下次帧逻辑时是否清除(忽略)_deltaTime */
bool _nextDeltaTimeZero;
@H_
404_17@
/* 上一次逻辑帧运行到当前的时间间隔,用来判断是否应该进行下次逻辑帧,上一次帧执行的时间记录在 _lastUpdate 变量里面*/
float _deltaTime;
下面两个函数是用来操作下一次的 _deltaTime是否有效的,当整个游戏暂停的时候,这时_deltaTime会不断累计,就会用到了_nextDeltaTimeZero这个变量,标记着下次的_deltaTime为0这样就会不出现恢复暂停后跳帧,而是继续当前帧顺序开始。
@H_
404_17@inline
bool isNextDeltaTimeZero() {
return@H_
502_18@ _nextDeltaTimeZero; }
void setNextDeltaTimeZero(
bool nextDeltaTimeZero);
计算_deltaTime的函数,会在每个逻辑循环里面都调用。
@H_
404_17@
/** 计算 deltaTime 上次逻辑帧调用的时间和当前时间的时间间隔。如果 nextDeltaTimeZero为true则deltaTime为0*/
void calculateDeltaTime();
@H_
404_17@
/*上次主循环帧执行到当前的时间间隔 _deltaTime*/
float getDeltaTime()
const;
继续看代码
@H_
404_17@
/** 询问当前是否是暂停状态 游戏暂停用 _paused这个变量记录 */@H_
502_18@
inline
bool isPaused() {
return@H_
502_18@ _paused; }
/** director运行后一共执行了多少帧*/@H_
502_18@
inline unsigned
int getTotalFrames() {
return@H_
502_18@ _totalFrames; }
/** 设置/读取 _projection变量,标记工程类型 2d?3d?
@since v0.8.2
* @js NA
* @lua NA
*/@H_
502_18@
inline Projection getProjection() {
return@H_
502_18@ _projection; }
void@H_
502_18@ setProjection(Projection projection);
/** 设置opengl的viewport*/
void setViewport();
下面是一些坐标的操作方法
@H_
404_17@
/** 可以得到通知消息的node结点,具体后面分析Node再讨论,现在大概了解一下*/@H_
502_18@
Node* getNotificationNode()
const {
return@H_
502_18@ _notificationNode; }
void setNotificationNode(Node *@H_
502_18@node);
// 下面是设置和获得窗口尺寸的一些函数 注释已经很详细了,这里就不翻译了
/** returns the size of the OpenGL view in points.
*/
const Size& getWinSize()
const@H_
502_18@;
/** returns the size of the OpenGL view in pixels.
*/@H_
502_18@
Size getWinSizeInPixels()
const@H_
502_18@;
/** returns visible size of the OpenGL view in points.
* the value is equal to getWinSize if don't invoke
* GLView::setDesignResolutionSize()
*/@H_
502_18@
Size getVisibleSize()
const@H_
502_18@;
/** returns visible origin of the OpenGL view in points.
*/@H_
502_18@
Point getVisibleOrigin()
const@H_
502_18@;
/** converts a UIKit coordinate to an OpenGL coordinate
Useful to convert (multi) touch coordinates to the current layout (portrait or landscape)
*/@H_
502_18@
Point convertToGL(
const Point&@H_
502_18@ point);
/** converts an OpenGL coordinate to a UIKit coordinate 坐标转换
Useful to convert node points to window points for calls such as glScissor
*/@H_
502_18@
Point convertToUI(
const Point&@H_
502_18@ point);
/// XXX: missing description
float getZEye()
const;
下面是场景管理的一些方法 这部分挺重要的,我们深入分析
先看一下关于Scene场景的一些属性
@H_
404_17@
/* 当前正在执行的场景,由这个变量可以知道,Cocos2d-x同一时间只能执行一个场景。*/@H_
502_18@
Scene *@H_
502_18@_runningScene;
/* 下一个要执行的场景,这块肯定是在场景切换的时候要用到的 */@H_
502_18@
Scene *@H_
502_18@_nextScene;
/* 是否清除场景的标记,当为真时,旧的场景就收到清除消息 */
bool@H_
502_18@ _sendCleanupToScene;
/* 场景的堆栈 */@H_
502_18@
Vector<Scene*> _scenesStack;
通过这几个关于场景的属性可以大体了解到,Cocos2d-x同时只能执行一个场景,场景切换的时候有一个 _nextScene。清除场景时有一个标记 _scendCleanupToScene,等待执行的场景都存在 一个栈里面 _scenesStack
@H_
404_17@
/** 设置要执行的场景 */
void runWithScene(Scene *@H_
502_18@scene);
/** 将新的场景加入到执行堆栈里面,新加入的场景将会被立即执行,使用的时候避免这个堆栈里的场景太多,防止设备内存不足,当已经有场景在执行的时候可以调用此方法来切换场景 */
void pushScene(Scene *@H_
502_18@scene);
/** 从堆栈中弹出最后加入的场景,在使用这个函数的时候要确保已经有一个场景在执行且在堆栈里面。弹出的场景会被清除,如果栈空了,那么Director就会停止 */
void@H_
502_18@ popScene();
/** 通过调用 `popToSceneStackLevel(1)` 这个方法来实现清理栈里的场景只留下根场景,就是剩下第一个入栈的场景 */
void@H_
502_18@ popToRootScene();
/** 按栈的层次来清理栈里的场景,level=0全清除 =1时 为 popToRootScene() 如果值超出了栈里的场景数量则不处理 */
void popToSceneStackLevel(
int@H_
502_18@ level);
/** 当有场景在执行的时候,替换当前运行的场景 */
void replaceScene(Scene *@H_
502_18@scene);
/** 停止当前场景 */
void@H_
502_18@ end();
/** 暂停场景 */
void@H_
502_18@ pause();
/** 暂停后恢复场景
*/
void resume();
@H_
404_17@
/** 停止动画及所有逻辑 */
virtual void stopAnimation() =
0@H_
502_18@;
/**开始动画循环
*/
virtual void startAnimation() =
0@H_
502_18@;
/** 渲染场景
*/
void drawScene();
下面是一些内存控制的
@H_
404_17@
/** 清除Direct的内存缓存,看下源码可以大概了解都有字体,纹理,文件等内存资源 */
void@H_
502_18@ purgeCachedData();
/** 设置默认值,具体有哪些看下代码就知道了,很清楚写的*/
void setDefaultValues();
OpenGl的一些操作
@H_
404_17@
/** 设置OpenGl的默认值*/
void@H_
502_18@ setGLDefaultValues();
/** 设置是否开启透明*/
void setAlphaBlending(
bool@H_
502_18@ on);
/** 设置是否开启深度测试*/
void setDepthTest(
bool on);
Director主循环 所有Director场景逻辑都会在这里触发
@H_
404_17@
virtual void mainLoop() =
0;
还有一些方法,简单过一遍,从命名上就可以知道大概的含义了,有些后面我们分章节来详细分析
@H_
404_17@
/** 设置/获得缩放比例 */
void setContentScaleFactor(
float@H_
502_18@ scaleFactor);
float getContentScaleFactor()
const {
return@H_
502_18@ _contentScaleFactor; }
/** 得到调度控制对象 ,这个Scheduler应该是类似一个定时期和一堆回调方法的东西,后面我们专门分析这玩意 */@H_
502_18@
Scheduler* getScheduler()
const {
return@H_
502_18@ _scheduler; }
/** 设置定时器 */
void setScheduler(Scheduler*@H_
502_18@ scheduler);
/** 获得、设置动作管理器对象 后面单独分析这个类 */@H_
502_18@
ActionManager* getActionManager()
const {
return@H_
502_18@ _actionManager; }
void setActionManager(ActionManager*@H_
502_18@ actionManager);
/**事件分发器的 get set操作 后面单独分析这个类*/@H_
502_18@
EventDispatcher* getEventDispatcher()
const {
return@H_
502_18@ _eventDispatcher; }
void setEventDispatcher(EventDispatcher*@H_
502_18@ dispatcher);
/** 渲染器 后面单独分析这个类 */@H_
502_18@
Renderer* getRenderer()
const {
return _renderer; }
上面解剖了Director类,有几个方法我们着重看一下
先看返回单例对象的方法
@H_
404_17@Director*@H_
502_18@ Director::getInstance()
{
if (!@H_
502_18@s_SharedDirector)
{
s_SharedDirector =
new@H_
502_18@ DisplayLinkDirector();
s_SharedDirector->@H_
502_18@init();
}
return@H_
502_18@ s_SharedDirector;
}
值得注意的是,返回的是DisplayLinkDirector这个类,并且在创建完 DisplayLinkDirector对象后调用了init方法,
咱们先不管DisplayLinkDirector类是什么,肯定是一个Director的一个子类,因为Director是一个抽象类
先看一下init方法 从这个方法里面我们再一次了解一下,Director具体都能干什么,和一些内部初始化的工作是怎么完成的
@H_
404_17@
bool Director::init(
void@H_
502_18@)
{
setDefaultValues();
// scenes
_runningScene =@H_
502_18@ nullptr;
_nextScene =@H_
502_18@ nullptr;
_notificationNode =@H_
502_18@ nullptr;
_scenesStack.reserve(
15@H_
502_18@);
// FPS
_accumDt =
0.0f@H_
502_18@;
_frameRate =
0.0f@H_
502_18@;
_FPSLabel = _drawnBatchesLabel = _drawnVerticesLabel =@H_
502_18@ nullptr;
_totalFrames = _frames =
0@H_
502_18@;
_lastUpdate =
new struct@H_
502_18@ timeval;
// paused ?
_paused =
false@H_
502_18@;
// purge ?
_purgeDirectorInNextLoop =
false@H_
502_18@;
_winSizeInPoints =@H_
502_18@ Size::ZERO;
_openGLView =@H_
502_18@ nullptr;
_contentScaleFactor =
1.0f@H_
502_18@;
// scheduler
_scheduler =
new@H_
502_18@ Scheduler();
//init()方法中new了Scheduler和ActionManager对象,但是Director的析构函数并没有直接delete,
// action manager //而是 CC_SAFE_RELEASE(_scheduler); CC_SAFE_RELEASE(_actionManager); 即调用了release()
_actionManager =
new@H_
502_18@ ActionManager();
_scheduler->scheduleUpdate(_actionManager,Scheduler::PRIORITY_SYSTEM,
false@H_
502_18@);
_eventDispatcher =
new@H_
502_18@ EventDispatcher();
_eventAfterDraw =
new@H_
502_18@ EventCustom(EVENT_AFTER_DRAW);
_eventAfterDraw->setUserData(
this@H_
502_18@);
_eventAfterVisit =
new@H_
502_18@ EventCustom(EVENT_AFTER_VISIT);
_eventAfterVisit->setUserData(
this@H_
502_18@);
_eventAfterUpdate =
new@H_
502_18@ EventCustom(EVENT_AFTER_UPDATE);
_eventAfterUpdate->setUserData(
this@H_
502_18@);
_eventProjectionChanged =
new@H_
502_18@ EventCustom(EVENT_PROJECTION_CHANGED);
_eventProjectionChanged->setUserData(
this@H_
502_18@);
//init TextureCache
@H_
502_18@ initTextureCache();
_renderer =
new@H_
502_18@ Renderer;
#if (CC_TARGET_PLATFORM != CC_PLATFORM_WINRT) && (CC_TARGET_PLATFORM != CC_PLATFORM_WP8)@H_
502_18@
_console =
new@H_
502_18@ Console;
#endif
return true@H_
502_18@;
}
可以看到,Director这个大管家初始化了 ActionManager 动作管理器 并将 _actionManager加到了定时器里
初始化了EventDispatcher EventCustom 等事件
初始化了纹理 和渲染器 Renderer
下面我们再看一下DisplayLinkDirector这个类
这是Director的实体类。
@H_
404_17@
class DisplayLinkDirector :
public@H_
502_18@ Director
{
public@H_
502_18@:
DisplayLinkDirector()
: _invalid(
false@H_
502_18@)
{}
//
// Overrides
//
virtual void mainLoop()
override@H_
502_18@;
virtual void setAnimationInterval(
double value)
override@H_
502_18@;
virtual void startAnimation()
override@H_
502_18@;
virtual void stopAnimation()
override@H_
502_18@;
protected@H_
502_18@:
bool@H_
502_18@ _invalid;
};
这个类实现了Director的几个关键的虚函数。
mainLoop这个是最主要的了,上面我们一再说逻辑循环 逻辑循环,其实都是指这个函数,所有的操作,动画,渲染,定时器都在这里驱动的。
游戏主循环里反复的调度 mainLoop来一帧一帧的实现游戏的各种动作,动画……. mainLoop来决定当前帧该执行什么,是否到时间执行等等。
@H_
404_17@
void@H_
502_18@ DisplayLinkDirector::mainLoop()
{
if@H_
502_18@ (_purgeDirectorInNextLoop)
{
_purgeDirectorInNextLoop =
false@H_
502_18@;
purgeDirector();
}
else if (!@H_
502_18@ _invalid)
{
drawScene();
// release the objects
PoolManager::getInstance()->getCurrentPool()->@H_
502_18@clear();
}
}
代码很简单,根据对 purgeDirectorInNextLoop 判断来决定mainLoop是否应该清除。
_invalid来决定 Director是否应该进行逻辑循环
这段代码很简单,主要操作都封闭到了 drawScene里面后面我们跟进drawScene来看看每个逻辑帧都干了些什么。
后面还有一个代码PoolManager::getInstance()->getCurrentPool()->@H_502_18@clear(); 从命名上来看是做清除操作,应该是内存操作,每帧回收不用的引用对象应该是在这里触发的,我们在内存应用的章节再回过头来讨论这块。
@H_502_18@下面看drawScene的代码
@H_
404_17@
void@H_
502_18@ Director::drawScene()
{
// 计算帧之间的时间间隔,下面根据这个时间间隔来判断是否应该进行某某操作
@H_
502_18@ calculateDeltaTime();
// skip one flame when _deltaTime equal to zero.
if(_deltaTime <@H_
502_18@ FLT_EPSILON)
{
return@H_
502_18@;
}
if@H_
502_18@ (_openGLView)
{
_openGLView->@H_
502_18@pollInputEvents();
}
//Director没有暂停的情况下,更新定时器,分发 update后的消息
if (!@H_
502_18@ _paused)
{
_scheduler->@H_
502_18@update(_deltaTime);
_eventDispatcher->@H_
502_18@dispatchEvent(_eventAfterUpdate);
}
//opengl清理
@H_
404_17@@H_
502_18@ glClear(GL_COLOR_BUFFER_BIT |@H_
502_18@ GL_DEPTH_BUFFER_BIT);
/*设置下个场景*/
if@H_
502_18@ (_nextScene)
{
setNextScene();
}
kmGLPushMatrix();
// global identity matrix is needed... come on kazmath!
@H_
502_18@ kmMat4 identity;
kmMat4Identity(&@H_
502_18@identity);
// 渲染场景
if@H_
502_18@ (_runningScene)
{
_runningScene->visit(_renderer,identity,
false@H_
502_18@);
@H_
404_17@
// 分发场景渲染后的消息 @H_
502_18@
_eventDispatcher->@H_
502_18@dispatchEvent(_eventAfterVisit);
}
// 渲染notifications 结点,这个结点有什么用现在还看不太清楚,后面章节我们一定会摸清楚的
if@H_
502_18@ (_notificationNode)
{
_notificationNode->visit(_renderer,
false@H_
502_18@);
}
if@H_
502_18@ (_displayStats)
// 渲染 FPS等帧频显示
{
showStats();
}
_renderer->@H_
502_18@render();
// 调用了渲染器的render方法,具体我们在分析Render类的时候再回过来看都干了些什么
_eventDispatcher->@H_
502_18@dispatchEvent(_eventAfterDraw);
kmGLPopMatrix();
_totalFrames++@H_
502_18@;
// swap buffers
if@H_
502_18@ (_openGLView)
{
_openGLView->@H_
502_18@swapBuffers();
}
if@H_
502_18@ (_displayStats)
{
calculateMPF();
}
}
到现在,我们完整的分析了Director类,了解了这个大管家都管理了哪些对象。下面我们做个总结。
Director主要管理了场景,四个事件的分发,渲染,Opengl对象,等
它主要是以场景为单位来控制游戏的逻辑帧,通过场景的切换来实现游戏中不同界面的变化。
其实 mainloop这个函数 调用 了drawscene来实现每一帧的逻辑主要是渲染逻辑。
上一章节,我们读到了application里面有一个run方法 ,在run方法里面有一个死循环,那个是游戏的主循环,在那个死循环里不断的调用 director->mainLoop这个就是在主游戏循环里不断的执行逻辑帧的操作.
下一节我们从最基本的开始分析,看一下 ref这个类cocos2d-x的内存管理机制。