cocos2dx 3.0版本出来之后,可以发现绘制流程与cocos2dx 2.2.x版本相比,改动很大。这里笔记下两个版本的不同。
CONTENTS
cocos2dx 2.2.x版本绘制流程
首先打开main.cpp:
int APIENTRY _tWinMain(。。。。。) { 。。。。。 AppDelegate app; 。。。。。 return CCApplication::sharedApplication()->run(); }
然后打开AppDelegate文件:
class AppDelegate : private cocos2d::CCApplication { public: AppDelegate(); virtual ~AppDelegate(); virtual bool applicationDidFinishLaunching(); virtual void applicationDidEnterBackground(); virtual void applicationWillEnterForeground(); }
可以看到AppDelegate继承自CCApplication,并且重写了3个虚函数。AppDelegate初始化时,会调用到CCApplication构造函数:
// sharedApplication pointer CCApplication * CCApplication::sm_pSharedApplication = 0; CCApplication::CCApplication() : m_hInstance(NULL) , m_hAccelTable(NULL) { m_hInstance = GetModuleHandle(NULL); m_nAnimationInterval.QuadPart = 0; CC_ASSERT(! sm_pSharedApplication); sm_pSharedApplication = this; }
这里就知道了sm_pSharedApplication的指针为AppDelegate的实例。那么根据代码
CCApplication* CCApplication::sharedApplication() { CC_ASSERT(sm_pSharedApplication); return sm_pSharedApplication; } 。。。。。 int CCApplication::run() { 。。。。。 if (!applicationDidFinishLaunching()) { return 0; } 。。。。。 while (1) { CCDirector::sharedDirector()->mainLoop(); 。。。。。 } return (int) msg.wParam; }
可知,程序初始化时会调用AppDelegate的applicationDidFinishLaunching方法,然后循环调用mainLoop。
分析mainLoop代码:
CCDirector* CCDirector::sharedDirector(void) { if (!s_SharedDirector) { s_SharedDirector = new CCDisplayLinkDirector(); s_SharedDirector->init(); } return s_SharedDirector; } void CCDisplayLinkDirector::mainLoop(void) { 。。。。。 drawScene(); 。。。。。 }
可知,mainLoop其实是调用了drawScene方法。
void CCDirector::drawScene(void) { 。。。。。 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 。。。。。 kmGLPushMatrix(); // draw the scene if (m_pRunningScene) { m_pRunningScene->visit(); } 。。。。。 }
可以看到,drawScene方法实际上就是cocos2dx的绘图流程主入口,每次循环中,都是先glClear,然后调用场景的viist方法进行绘制场景。
class CC_DLL CCScene : public CCNode void CCNode::visit() { // quick return if not visible. children won't be drawn. if (!m_bVisible) { return; } kmGLPushMatrix(); 。。。。。 this->transform(); CCNode* pNode = NULL; unsigned int i = 0; if(m_pChildren && m_pChildren->count() > 0) { sortAllChildren(); // draw children zOrder < 0 ccArray *arrayData = m_pChildren->data; for( ; i < arrayData->num; i++ ) { pNode = (CCNode*) arrayData->arr[i]; if ( pNode && pNode->m_nZOrder < 0 ) { pNode->visit(); } else { break; } } // self draw this->draw(); for( ; i < arrayData->num; i++ ) { pNode = (CCNode*) arrayData->arr[i]; if (pNode) { pNode->visit(); } } } else { this->draw(); } 。。。。。 kmGLPopMatrix(); }
分析下CCNode的visit方法,可以发现它的流程:
如果Node没有子节点,那么直接调用draw方法,
如果Node有子节点,那么就根据子节点的Zorder进行排序,先找到子节点中Zorder小于Node本身的这些子节点,调用它们的visit方法,再调用Node本身的draw方法,在调用其余子节点的visit方法,这样就可以保证Zorder下的正确的层次关系。而且这里子节点调用的是visit方法,如果这个子节点还有子节点,就会一层一层的递归调用,直到子节点下没有节点了,直接调用draw方法。
由于CCSprite,CCLayer,CCScene都是继承自CCNode,而且重写了draw方法,那么通过这样的树形设计和递归调用,就可以使场景中每个节点(Sprite,Layer等)的draw方法都被调用而且是按Zorder的顺序调用的。
我们这里再看一下CCSprite的draw方法:
void CCSprite::draw(void) { 。。。。。 glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); 。。。。。 }
可以看到,这里CCSprite的draw方法中直接把自己绘制出来了。
cocos2dx 2.2.x版本基本上都这个绘制流程。这个绘制流程有个好处,就是便于理解,每个节点都有自己的绘制方法,而且会在这个绘制方法中把自己绘制出来。但是有两个问题:
1 opengl绘制方法和游戏业务方法混在一块,opengl的代码遍布于整个cocos2dx工程,不好管理,代码层次较乱。
2 性能较低,由于各个节点都只绘制自己,无法得到其他节点的绘制情况。在有遮盖的情况下,绘制效率较低。比如某个节点A先绘制,然后第二个节点B绘制的时候覆盖掉了节点A,从效率上将,由于节点A被覆盖,实际上是可以不绘制的(绘制了也会被B遮住)。
cocos2dx 3.0版本绘制流程
cocos2dx 3.0版本和2.2.x版本的初始化流程差不多,最后都会调到Director的drawScene方法:
void Director::drawScene() { 。。。。。 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 。。。。。 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); } 。。。。。 _renderer->render(); _eventDispatcher->dispatchEvent(_eventAfterDraw); 。。。。。 }
这里看下3.0版本的CCNode的visit方法:
void Node::visit(Renderer* renderer, const kmMat4 &parentTransform, bool parentTransformUpdated) { // quick return if not visible. children won't be drawn. if (!_visible) { return; } bool dirty = _transformUpdated || parentTransformUpdated; if(dirty) _modelViewTransform = this->transform(parentTransform); _transformUpdated = false; // IMPORTANT: // To ease the migration to v3.0, we still support the kmGL stack, // but it is deprecated and your code should not rely on it kmGLPushMatrix(); kmGLLoadMatrix(&_modelViewTransform); 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, dirty); else break; } // self draw this->draw(renderer, _modelViewTransform, dirty); for(auto it=_children.cbegin()+i; it != _children.cend(); ++it) (*it)->visit(renderer, _modelViewTransform, dirty); } else { this->draw(renderer, _modelViewTransform, dirty); } // reset for next frame _orderOfArrival = 0; kmGLPopMatrix(); }
和2.2.x的类似,也是遍历子节点,调用draw方法。那么看下draw方法:
void Node::draw(Renderer* renderer, const kmMat4 &transform, bool transformUpdated) { }
空函数,没有实现,我们在看下CCSprite的draw方法:
void Sprite::draw(Renderer *renderer, const kmMat4 &transform, bool transformUpdated) { // Don't do calculate the culling if the transform was not updated _insideBounds = transformUpdated ? renderer->checkVisibility(transform, _contentSize) : _insideBounds; if(_insideBounds) { _quadCommand.init(_globalZOrder, _texture->getName(), _shaderProgram, _blendFunc, &_quad, 1, transform); renderer->addCommand(&_quadCommand); 。。。。。 } }
这里就有区别了,可以发现,3.0版本的Sprite的draw方法中没有直接调用opengl绘制,而是把要绘制的内容和信息添加到了renderer中。那么通过树形设计和递归调用,场景中每个节点(Sprite,Layer等)的draw方法都会把自己要绘制的信息添加到renderer中。然后回到drawScene方法,可以发现,当场景的visit方法调用完并返回后,程序会调用_renderer的render()方法,在此方法中进行统一绘制。
分析到此,就可以初步的看出3.0版本的绘制流程了:先把要绘制的信息和数据收集起来,然后统一绘制。由于篇幅关系,这里就不继续分析CCRenderer类的方法了。
发表评论