cocos2dx源码分析之一:大体运行流程

引擎版本为3.13,从入口开始一点点看。

 

iOS平台的入口在RootViewController.m文件的loadView方法中,app->run()开始。

run方法调用了一个名叫startMainLoop的方法,从名字就能知道这是要开启游戏主循环。

注意这个CADisplayLink,详细介绍看这里
简单来说,CADisplayLink是一个能让我们以和屏幕刷新率相同的频率将内容画到屏幕上的定时器。
这就相当于游戏循环,每帧会调用这个doCaller方法,而这个方法调用了Director的mainLoop方法。

可以看到mainLoop方法里做了两件事,绘制和引用计数的管理。我们先看绘制
// 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();
    }
}
内容比较多,我们一点一点来,首先是calculateDeltaTime()方法,来看看calculateDeltaTime()都做了什么
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;
}
简单来看的话就是在计算两帧的时间间隔,这里有用到chrono这个库,详细介绍看这里,就是一个与时间有关的库
 
继续来看drawScene方法后面的内容
if (_openGLView)
    {
        _openGLView->pollEvents();
    }
这里的pollEvents方法最终会调用到glfwPollEvents()这个方法,这是glfw库的一个方法,用于处理用户输入的事件,比如鼠标键盘之类的。继续看
if (! _paused)
    {
        _eventDispatcher->dispatchEvent(_eventBeforeUpdate);
        _scheduler->update(_deltaTime);
        _eventDispatcher->dispatchEvent(_eventAfterUpdate);
    }
这里是在处理引擎的scheduler事件(这部分会单独详细研究),在update方法前后都会分发事件。
_renderer->clear();
experimental::FrameBuffer::clearAllFBOs();
这里可以简单理解成在重置openGL的一些状态,引擎对于OpenGL的封装以后会详细研究。
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);
    }
_runningScene->stepPhysicsAndNavigation(_deltaTime);这一句与3D内容有关先不去研究。
 _renderer->clearDrawStats();这一句是在清理绘制的状态信息。
_openGLView->renderScene(_runningScene,_renderer);这一句很关键我们具体来看
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();这一句跟引擎的内存管理有关系,我们也单独拿出来说。那么大体的运行流程就是这样的。

 

 

posted @ 2017-10-11 11:24  子衿时代  阅读(1472)  评论(0编辑  收藏  举报