cocos2dx源码分析之一:大体运行流程
引擎版本为3.13,从入口开始一点点看。
iOS平台的入口在RootViewController.m文件的loadView方法中,app->run()开始。
run方法调用了一个名叫startMainLoop的方法,从名字就能知道这是要开启游戏主循环。
// Draw the Scene void Director::drawScene() { // calculate "global" dt calculateDeltaTime(); if (_openGLView) { _openGLView->pollEvents(); } //tick before glClear: issue #533 if (! _paused) { _eventDispatcher->dispatchEvent(_eventBeforeUpdate); _scheduler->update(_deltaTime); _eventDispatcher->dispatchEvent(_eventAfterUpdate); } _renderer->clear(); experimental::FrameBuffer::clearAllFBOs(); /* to avoid flickr, nextScene MUST be here: after tick and before draw. * FIXME: Which bug is this one. It seems that it can't be reproduced with v0.9 */ if (_nextScene) { setNextScene(); } pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW); if (_runningScene) { #if (CC_USE_PHYSICS || (CC_USE_3D_PHYSICS && CC_ENABLE_BULLET_INTEGRATION) || CC_USE_NAVMESH) _runningScene->stepPhysicsAndNavigation(_deltaTime); #endif //clear draw stats _renderer->clearDrawStats(); //render the scene _openGLView->renderScene(_runningScene, _renderer); _eventDispatcher->dispatchEvent(_eventAfterVisit); } // draw the notifications node if (_notificationNode) { _notificationNode->visit(_renderer, Mat4::IDENTITY, 0); } if (_displayStats) { showStats(); } _renderer->render(); _eventDispatcher->dispatchEvent(_eventAfterDraw); popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW); _totalFrames++; // swap buffers if (_openGLView) { _openGLView->swapBuffers(); } if (_displayStats) { calculateMPF(); } }
void Director::calculateDeltaTime() { auto now = std::chrono::steady_clock::now(); // new delta time. Re-fixed issue #1277 if (_nextDeltaTimeZero) { _deltaTime = 0; _nextDeltaTimeZero = false; } else { _deltaTime = std::chrono::duration_cast<std::chrono::microseconds>(now - _lastUpdate).count() / 1000000.0f; _deltaTime = MAX(0, _deltaTime); } #if COCOS2D_DEBUG // If we are debugging our code, prevent big delta time if (_deltaTime > 0.2f) { _deltaTime = 1 / 60.0f; } #endif _lastUpdate = now; }
if (_openGLView) { _openGLView->pollEvents(); }
if (! _paused) { _eventDispatcher->dispatchEvent(_eventBeforeUpdate); _scheduler->update(_deltaTime); _eventDispatcher->dispatchEvent(_eventAfterUpdate); }
_renderer->clear();
experimental::FrameBuffer::clearAllFBOs();
if (_nextScene) { setNextScene(); }
_nextScene用来标记是否有将要切换到的下一个界面,当有下一个界面的时候,会做一些相应的处理,比如调用上一个界面的onExit,释放上一个界面的资源等等,以及调用下一个界面的onEnter等,还有切换动画的一些处理。
pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
这句也是与OpenGL相关,暂时不提。继续向下看
if (_runningScene) { #if (CC_USE_PHYSICS || (CC_USE_3D_PHYSICS && CC_ENABLE_BULLET_INTEGRATION) || CC_USE_NAVMESH) _runningScene->stepPhysicsAndNavigation(_deltaTime); #endif //clear draw stats _renderer->clearDrawStats(); //render the scene _openGLView->renderScene(_runningScene, _renderer); _eventDispatcher->dispatchEvent(_eventAfterVisit); }
void GLView::renderScene(Scene* scene, Renderer* renderer) { CCASSERT(scene, "Invalid Scene"); CCASSERT(renderer, "Invalid Renderer"); if (_vrImpl) { _vrImpl->render(scene, renderer); } else { scene->render(renderer, Mat4::IDENTITY, nullptr); } }
_vrImpl暂时不看,先看scene->render(renderer,Mat4:IDENTITY,nullptr);这一句调用了Scene的render方法,这个方法很重要,详细看
void Scene::render(Renderer* renderer, const Mat4& eyeTransform, const Mat4* eyeProjection) { auto director = Director::getInstance(); Camera* defaultCamera = nullptr; const auto& transform = getNodeToParentTransform(); for (const auto& camera : getCameras()) { if (!camera->isVisible()) continue; Camera::_visitingCamera = camera; if (Camera::_visitingCamera->getCameraFlag() == CameraFlag::DEFAULT) { defaultCamera = Camera::_visitingCamera; } // There are two ways to modify the "default camera" with the eye Transform: // a) modify the "nodeToParentTransform" matrix // b) modify the "additional transform" matrix // both alternatives are correct, if the user manually modifies the camera with a camera->setPosition() // then the "nodeToParent transform" will be lost. // And it is important that the change is "permanent", because the matrix might be used for calculate // culling and other stuff. if (eyeProjection) camera->setAdditionalProjection(*eyeProjection * camera->getProjectionMatrix().getInversed()); camera->setAdditionalTransform(eyeTransform.getInversed()); director->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION); director->loadMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION, Camera::_visitingCamera->getViewProjectionMatrix()); camera->apply(); //clear background with max depth camera->clearBackground(); //visit the scene visit(renderer, transform, 0); #if CC_USE_NAVMESH if (_navMesh && _navMeshDebugCamera == camera) { _navMesh->debugDraw(renderer); } #endif renderer->render(); camera->restore(); director->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION); // we shouldn't restore the transform matrix since it could be used // from "update" or other parts of the game to calculate culling or something else. // camera->setNodeToParentTransform(eyeCopy); } #if CC_USE_3D_PHYSICS && CC_ENABLE_BULLET_INTEGRATION if (_physics3DWorld && _physics3DWorld->isDebugDrawEnabled()) { director->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION); director->loadMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION, _physics3dDebugCamera != nullptr ? _physics3dDebugCamera->getViewProjectionMatrix() : defaultCamera->getViewProjectionMatrix()); _physics3DWorld->debugDraw(renderer); renderer->render(); director->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION); } #endif Camera::_visitingCamera = nullptr; // experimental::FrameBuffer::applyDefaultFBO(); }
关于摄像机的内容先不讨论,以后单独拿出来研究,先看绘制相关的主要代码,
const auto& transform = getNodeToParentTransform();这一句获取了坐标系变换所需的变换矩阵。
director->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION);
director->loadMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION, Camera::_visitingCamera->getViewProjectionMatrix());将投影矩阵压入栈并为其赋值。
visit(renderer, transform, 0);这一句很关键,visit是基类Node的一个成员方法,我们详细来看。
void Node::visit(Renderer* renderer, const Mat4 &parentTransform, uint32_t parentFlags) { // quick return if not visible. children won't be drawn. if (!_visible) { return; } uint32_t flags = processParentFlags(parentTransform, parentFlags); // IMPORTANT: // To ease the migration to v3.0, we still support the Mat4 stack, // but it is deprecated and your code should not rely on it _director->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW); _director->loadMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW, _modelViewTransform); bool visibleByCamera = isVisitableByVisitingCamera(); int i = 0; if(!_children.empty()) { sortAllChildren(); // 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; } // self draw if (visibleByCamera) this->draw(renderer, _modelViewTransform, flags); for(auto it=_children.cbegin()+i; it != _children.cend(); ++it) (*it)->visit(renderer, _modelViewTransform, flags); } else if (visibleByCamera) { this->draw(renderer, _modelViewTransform, flags); } _director->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW); // FIX ME: Why need to set _orderOfArrival to 0?? // Please refer to https://github.com/cocos2d/cocos2d-x/pull/6920 // reset for next frame // _orderOfArrival = 0; }
首先,不可见的节点不遍历,直接return,然后如果该节点有子节点,先对所有子节点排序,排序的规则是根据localZOrder从小到大排列,排序完成后,递归遍历所有子节点,其中,优先遍历localZOrder小于0的节点,然后是自己,再然后是localZOrder大于等于0的节点,需要注意的是,遍历的顺序就是将来绘制的顺序,也就是说引擎会先绘制子节点中localZOrder小于0的,然后绘制自己,再绘制子节点中localZOrder大于等于0的,先绘制的会被后绘制的内容遮挡住,也就是后绘制的在上层。这个遍历过程会涵盖整个节点树,遍历的结果就是调用每个节点的draw()方法,draw在Node中是一个虚函数,每个继承自Node的节点都会去实现draw方法。draw方法之后的绘制内容以后单独分析。我们回到Director::drawScene()方法,下面这一句
if (_notificationNode) { _notificationNode->visit(_renderer, Mat4::IDENTITY, 0); }
是放在遍历整个节点树之后来执行的,也就是说这个_notifictionNode如果有的话,将会在所有节点的上层。Director::setNotificationNode(Node *node)这个方法可以设置_notifictionNode的值。
if (_displayStats) { showStats(); }
这几句是控制是否显示左下角的渲染信息,包括帧率、顶点数、绘制次数等内容。
_renderer->render();后面这一句是开始绘制,可以看到引擎是将节点的遍历和绘制分离的,在2.x的引擎版本中遍历和绘制是放到一起的,其实这里分离开来是有很多好处的,具体的分析渲染的时候再研究。
drawScene方法基本分析完了,中间夹杂的OpenGL和事件处理等内容将来单独分析,所以这里不做过多的研究。
前面说了mainLoop主循环就做了两件事,第一件是绘制,第二件事就是PoolManager::getInstance()->getCurrentPool()->clear();这一句跟引擎的内存管理有关系,我们也单独拿出来说。那么大体的运行流程就是这样的。