簡述:
我靠上面圖是不是太大了, 有點看不清了。
總結一下過程:
之前說過的appController 之后經過了若干初始化, 最后調用了displayLinker 的定時調用, 這里調用了函數 mainLoop, 我們在這里進行詳細分解這個mainloop 到底做些些啥, 看完這篇,應該能初步了解到cocos2dx是如何把Scene或界面元素顯示到屏幕上的。
我們主要分析的是 void DisplayLinkDirector::mainLoop()
這個函數, 它最后調用了Director::drawScene 這里對drawScene進行展開,不明白這個mainloop如何調用的, 可以參考一下我寫的第一篇分析 “1 cocos2dx 3.0 源碼分析-程序啟動與主循環”。
目錄:
Director::drawScene() 主要做了哪些事呢? 其中省去了一些我覺得無關緊要的小東西,最好你看看源碼, 看哪里我沒說到。
1 計算時間差, 2幀之前的時間差,calculateDeltaTime()
2 定時任務調用 _scheduler->update(_deltaTime);
3 事件處理 EventAfterUpdate
4 設置當前的下一個場景, 主要就是把當前場景釋放掉, 之后把_nextScene 設置為當前的場景
5 調用當前場景的渲染方法 _runningScene->render(_renderer);
6 事件處理意思是Visit調用完了, _eventAfterVisit
7 調用渲染引擎進行渲染 _renderer->render();
8 事件處理 _eventAfterDraw
9 調用 openGLView 平台提供的屏幕顯示方法, _openGLView->swapBuffers();
下面我一個一個的展開, 簡單的介紹一下, 比較復雜的咱們留在后面進行分析說明:
1 計算時間差, 2幀之前的時間差 calculateDeltaTime()
我們在update(float dt) 中得到的float dt. 就是這里計算的,計算了, 上次調用和這次調用之前的差。
2 定時任務調用 _scheduler->update(_deltaTime);
Scheduler 本身就是用來處理更新函數調用的。 我們在自己的場景中經常會使用schedulerUpdate(), 然后實現一個update(float dt) 方法,其實就是在內部把當前的調用請求注冊到了 scheudler 中, 之后在這個主循環每幀時進行統一的調用update() 函數.
這里可以看到, 這個update調用的時機還是蠻早的呢, 啥都沒干呢, 就先調用它了。 稍等我要把這點記住。
3 事件處理 EventAfterUpdate
_eventDispatcher->dispatchEvent(_eventAfterUpdate);
觸發事件“更新調用完了”, 這里大家也不用糾結, 我們可以簡單的認為這是一個通知,發送了一個 “更新調用完了“的通知,之后由事件管理器EventDispatcher 來調用注冊了這個事件的函數。
事件處理器Dispatcher 典型的觀察者模式, 細節這里不說, 以后單出一章說明。
4 設置當前的下一個場景,其實就場景的處理, 場景的用處理就在這里體現了。
setNextScene();
主要就是把當前場景釋放掉, 之后把_nextScene 設置為當前的場景, 還記得我們在調用場景時要調用一個 director->runWithScene(scene);
這里的scene 可以被認為是下一個場景了。 下面是它的幾個過程
1> 清理正在運行的Scene _runningScene->release(); 2> 設置當前Scene _runningScene = _nextScene; _nextScene->retain(); _nextScene = nullptr; 3> 調用當前場景onEnter _runningScene->onEnter();
5 調用當前場景的渲染方法 _runningScene->render(_renderer);
1> 加載當前的Camera
Camera::_visitingCamera = defaultCamera;
2> 裝載當前Camera的投影矩陣 director->loadMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION,Camera::_visitingCamera->getViewProjectionMatrix());
3> 調用visit; 這個函數可重要了,這個函數主要是分別調用當前節點下的子節點的visit(), 之后調用了自身的draw將相應的Command命令把渲染請求加入的當前readerer的渲染隊列中。 visit(renderer, Mat4::IDENTITY, 0);
3.1 對當前子節點排序 sortAllChildren
3.2 遞歸調用當前localZOrder 小於當前元素的子元素的visit()
// draw children zOrder < 0 for( ; i < _children.size(); i++ ) { auto node = _children.at(i); if ( node && node->_localZOrder < 0 ) node->visit(renderer, _modelViewTransform, flags); else break; }
3.3 調用自身的draw()把指定的渲染命令添加到當前的渲染隊列
if (visibleByCamera) this->draw(renderer, _modelViewTransform, flags); 下面是一個標准的Sprite的Draw() void Sprite::draw(Renderer *renderer, const Mat4 &transform, uint32_t flags) { // Don't do calculate the culling if the transform was not updated _insideBounds = (flags & FLAGS_TRANSFORM_DIRTY) ? renderer->checkVisibility(transform,_contentSize) : _insideBounds; if(_insideBounds) { _quadCommand.init(_globalZOrder, _texture->getName(), getGLProgramState(),_blendFunc, &_quad, 1, transform); renderer->addCommand(&_quadCommand); } }
3.4 遞歸調用其它子節點的visit()
for(auto it=_children.cbegin()+i; it != _children.cend(); ++it) (*it)->visit(renderer, _modelViewTransform, flags);
4> 調用渲染引擎進行渲染 renderer->render();
這塊渲染相關的, 我不展開了,回頭會詳細說, 大家知道引擎就是使用它來把元素顯示的屏幕上的,它封裝了大部分的OpenGL的指令。
6 事件處理意思是Visit調用完了, _eventAfterVisit
通知Visit調用完了, 這個visit就是把渲染請求封裝成命令,壓入Render的渲染隊列。
7 調用渲染引擎進行渲染 _renderer->render();
其它渲染操作。 就是畫東西
8 事件處理 _eventAfterDraw
通知我畫完了。
9 調用 openGLView 平台提供的屏幕顯示方法, _openGLView->swapBuffers();
所有操作做完了, 這里我們的操作都還只在 OpenGL 里幀緩存中, 我們要把它顯示在屏幕上 就要調用 不同平台提供的對OpenGLES 的支持函數, iOS上調用的是 [context_presentRenderbuffer:GL_RENDERBUFFER], 主要就是把幀緩存中的內容真正的交換或者說是顯示在屏幕上。
總結:
可以看到, 主循環里做了很多工作, 更新,事件處理, 場景切換,渲染, 是不是全了。 我看差不多。 后面繼續。。