Cocos2Dx之游戏启动过程-欧阳左至

Cocos2Dx之游戏启动过程-欧阳左至,第1张

概述Cocos2Dx应用由导演(CCDirector)、场景(CCSprite)、层(CCLayer)和精灵(CCSprite)构成。导演负责管理整个游戏。映射到 *** 作系统,每个 *** 作系统需要的应用入口都不一样。可以看到CCApplication是平台相关的,每个 *** 作系统平台都有自己的CCApplication实现 ,具体代码放在platform下面。为了对开发者透明,Cocos2Dx给出了AppDele

Cocos2Dx应用由导演(CCDirector)、场景(CCSprite)、层(cclayer)和精灵(CCSprite)构成。导演负责管理整个游戏。映射到 *** 作系统,每个 *** 作系统需要的应用入口都不一样。可以看到CCApplication是平台相关的,每个 *** 作系统平台都有自己的CCApplication实现 ,具体代码放在platform下面。为了对开发者透明,Cocos2Dx给出了AppDelegate,一个AppDelegate就是继承自CCApplication的一个代表该应用的代理者。Cocos2Dx提供了Python脚本帮我我们生成所有平台的项目文件,自己不用手动去选择设置合适的CCApplication。

AppDelegate一般只是暴露CCApplicationProtocol的几个重载函数给开发者。

?
1 2 3 4 5 6 7 8 9 class AppDelegate: private cocos2d::CCApplication @H_301_45@ { public : AppDelegate(); virtual ~AppDelegate(); virtual bool applicationDIDFinishLaunching(); virtual voID applicationDIDEnterBackground(); applicationWillEnterForeground(); };

我们只需要给出applicationDIDFinishLaunching、applicationDIDEnterBackground和applicationWillEnterForeground的实现即可。暴露给开发者的应用入口,看不到 *** 作系统的细节,封装得很好。

在AppDelegate::applicationDIDFinishLaunching()中,我们会创建导演和场景,然后调用导演的runWithScene函数。

9 10 11 12 13 14 AppDelegate::applicationDIDFinishLaunching() @H_301_45@ { CCDirector*pDirector=CCDirector::sharedDirector(); pDirector->setopenGLVIEw(CCEGLVIEw::sharedOpenGLVIEw()); CCSizescreenSize=CCEGLVIEw::sharedOpenGLVIEw()->getFrameSize(); CCSizedesignSize=CCSizeMake(480,320); CCEGLVIEw::sharedOpenGLVIEw()->setDesignResolutionSize(designSize.wIDth,designSize.height,kResolutionNoborder); CCScene*pScene=CCScene::create(); cclayer*pLayer= new TestController(); pLayer->autorelease(); //将cclayer添加到自动回收池当中,但并不会立即释放。随后的addChild会调用cclayer的retain函数,增加一次饮用计数 pScene->addChild(pLayer); pDirector->runWithScene(pScene); return true ; }

我们创建好了导演,如何跟 *** 作系统的入口函数关联起来呢?同样以WIN32为例。

10 int APIENTRY_tWinMain( HINSTANCE hInstance, hPrevInstance,monospace!important; color:grey!important; border:0px!important; bottom:auto!important; float:none!important; height:auto!important; left:auto!important; line-height:1.1em!important; outline:0px!important; overflow:visible!important; position:static!important; right:auto!important; top:auto!important; vertical-align:baseline!important; wIDth:auto!important; Font-weight:bold!important; Font-size:1em!important; min-height:auto!important; background:none!important">LPTSTR lpCmdline,monospace!important; border:0px!important; bottom:auto!important; float:none!important; height:auto!important; left:auto!important; line-height:1.1em!important; outline:0px!important; overflow:visible!important; position:static!important; right:auto!important; top:auto!important; vertical-align:baseline!important; wIDth:auto!important; Font-size:1em!important; min-height:auto!important; background:none!important">nCmdshow) @H_301_45@ UNREFERENCED_ParaMETER(hPrevInstance); UNREFERENCED_ParaMETER(lpCmdline); AppDelegateapp; CCEGLVIEw*eglVIEw=CCEGLVIEw::sharedOpenGLVIEw(); eglVIEw->setVIEwname( "TestCpp" ); eglVIEw->setFrameSize(960,640); return CCApplication::sharedApplication()->run(); 在 *** 作系统的入口函数,我们创建了AppDelegate对象。此时为调用AppDelegate的构造函数,其为空,进一步调用基类CCApplication的构造函数,也只是简单初始化一些成员,没有其他动作。CCEGLVIEw::sharedOpenGLVIEw()构造一个窗口,如果你了解WIN32 API,那么CCEGLVIEw的Create函数看着就非常熟悉。首先创建一个窗口样式WNDCLASS,然后创建一个窗口。窗口创建完毕后,调用initGL()初始化OpenGL。最后,检查是否支持触控,如果支持,调用user32.dll里的RegistertouchWindow函数将刚刚创建的窗口进行注册, *** 作系统将触控事件发送给我们创建的窗口。这个窗口后面会作为游戏界面的容器,我们的游戏也就能够接收到 *** 作系统发送过来的触控消息了。

14 15 16 17 18 19 @H_419_265@ 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 @H_295_301@ 38 39 40 41 42 43 44 45 46 47 48 49 50 CCEGLVIEw::Create()
@H_301_45@ bRet= false ; do { CC_BREAK_IF(m_hWnd); hInstance=GetModuleHandle(NulL); WNDCLASSwc; //windowsClassstructure wc.style=CS_HREDRAW|CS_VREDRAW|CS_OWNDC; wc.lpfnWndProc=_WindowProc; //WndProcHandlesMessages wc.cbClsExtra=0; //NoExtraWindowData wc.cbWndExtra=0; //NoExtraWindowData wc.hInstance=hInstance; //SetTheInstance wc.hIcon=LoadIcon(NulL,IDI_WINlogo); //LoadTheDefaultIcon wc.hCursor=LoadCursor(NulL,IDC_ARROW); //LoadTheArrowPointer @H_984_403@ wc.hbrBackground=NulL; //NoBackgroundrequiredForGL wc.lpszMenuname=m_menu; // wc.lpszClassname=kWindowClassname; //SetTheClassname CC_BREAK_IF(!RegisterClass(&wc)&&1410!=GetLastError()); RECTrcDesktop; GetwindowRect(GetDesktopWindow(),&rcDesktop); WCHAR wszBuf[50]={0}; MultiBytetoWIDeChar(CP_UTF8,m_szVIEwname,-1,wszBuf, sizeof (wszBuf)); m_hWnd=CreateWindowEx( WS_EX_APPWINDOW|WS_EX_WINDOWEDGE, //ExtendedStyleForTheWindow kWindowClassname,0)!important; border:0px!important; bottom:auto!important; float:none!important; height:auto!important; left:auto!important; line-height:1.1em!important; outline:0px!important; overflow:visible!important; position:static!important; right:auto!important; top:auto!important; vertical-align:baseline!important; wIDth:auto!important; Font-size:1em!important; min-height:auto!important; background:none!important">//Classname wszBuf,0)!important; border:0px!important; bottom:auto!important; float:none!important; height:auto!important; left:auto!important; line-height:1.1em!important; outline:0px!important; overflow:visible!important; position:static!important; right:auto!important; top:auto!important; vertical-align:baseline!important; wIDth:auto!important; Font-size:1em!important; min-height:auto!important; background:none!important">//WindowTitle WS_CAPTION|WS_POPUPWINDOW|WS_MINIMIZEBox,0)!important; border:0px!important; bottom:auto!important; float:none!important; height:auto!important; left:auto!important; line-height:1.1em!important; outline:0px!important; overflow:visible!important; position:static!important; right:auto!important; top:auto!important; vertical-align:baseline!important; wIDth:auto!important; Font-size:1em!important; min-height:auto!important; background:none!important">//definedwindowstyle 0,0)!important; border:0px!important; bottom:auto!important; float:none!important; height:auto!important; left:auto!important; line-height:1.1em!important; outline:0px!important; overflow:visible!important; position:static!important; right:auto!important; top:auto!important; vertical-align:baseline!important; wIDth:auto!important; Font-size:1em!important; min-height:auto!important; background:none!important">//Windowposition //Todo:InitializingwIDthwithalargevaluetoavoIDgettingawrongclIEntareaby'GetClIEntRect'function. 1000,0)!important; border:0px!important; bottom:auto!important; float:none!important; height:auto!important; left:auto!important; line-height:1.1em!important; outline:0px!important; overflow:visible!important; position:static!important; right:auto!important; top:auto!important; vertical-align:baseline!important; wIDth:auto!important; Font-size:1em!important; min-height:auto!important; background:none!important">//WindowWIDth //WindowHeight NulL,0)!important; border:0px!important; bottom:auto!important; float:none!important; height:auto!important; left:auto!important; line-height:1.1em!important; outline:0px!important; overflow:visible!important; position:static!important; right:auto!important; top:auto!important; vertical-align:baseline!important; wIDth:auto!important; Font-size:1em!important; min-height:auto!important; background:none!important">//noparentwindow //NoMenu //Instance NulL); CC_BREAK_IF(!m_hWnd); bRet=initGL(); if (!bRet)destroyGL(); CC_BREAK_IF(!bRet); s_pMainWindow= this ; ; } while (0); m_bSupporttouch=ChecktouchSupport(); (m_bSupporttouch) { m_bSupporttouch=(s_pfRegistertouchWindowFunction(m_hWnd,0)!=0); } bRet; 在WIN 32入口函数_tWinMain的最后,我们调用CCApplication::sharedApplication()->run(),实际上是调用的是cocos2dx\platform\win32\CCApplication.cpp的run函数。

39 CCApplication::run()
@H_301_45@ //在注册表里设置PVRFrame的快捷键。PVRFrame是由ImaginationTechnologIEs开发的一套模拟OpenGLES的环境。 //http://community.imgtec.com/developers/powervr/tools/pvrvframe/ PVRFrameEnableControlWindow( ); //目的是获取精确时间 queryPerformanceFrequency(&nFreq); queryPerformanceCounter(&nLast); //调用AppDelegate的applicationDIDFinishLaunching回调函数创建好了当前游戏场景 (!applicationDIDFinishLaunching()) { 0; } //游戏场景已经设置了,可以显示窗口 CCEGLVIEw*pMainWnd=CCEGLVIEw::sharedOpenGLVIEw(); @H_984_403@ pMainWnd->centerWindow(); ShowWindow(pMainWnd->getHWnd(),SW_SHOW); //主循环 (1) { (!PeekMessage(&msg,NulL,PM_REMOVE)) { //获取当前时间 queryPerformanceCounter(&nNow); //达到了指定的时间间隔,绘制下一帧,否则放弃cpu (nNow.QuadPart-nLast.QuadPart>m_nAnimationInterval.QuadPart) { nLast.QuadPart=nNow.QuadPart; CCDirector::sharedDirector()->mainLoop(); } else { Sleep(0); } continue ; } } ( )msg.wParam; queryPerformanceFrequency(&nFreq)和queryPerformanceCounter(&nLast)目的是获取精确的时间间隔。在定时前先调用queryPerformanceFrequency()函数获得机器内部计时器的时钟频率。接着在需要严格计时的事件发生前和发生之后分别调用queryPerformanceCounter(),利用两次获得的计数之差和时钟频率,就可以计算出事件经历的精确时间。数据类型LARGE_INTEGER既可以是一个作为8字节长的整数,也可以是作为两个4字节长的整数的联合结构,其具体用法根据编译器是否支持64位而定。这里使用的是64位的QuadPart。

setAnimationInterval使用的单位是秒,内部通过Tick来做的判断,所以setAnimationInterval内部将传入的帧间隔乘以了始终频率。

nNow存放着当前的Tick,nLast存放着前一帧绘制的时间,通过检查是否过了我们设定的帧间隔时间等价的Tick来决定是否绘制下一帧。如果需要绘制,调用CCdisplaylinkDirector::mainLoop()。这里已经到了所有平台通用的代码了。

为了比较,我们再看看AndroID的应用入口。代码位于cocos2dx\platform\androID\CCApplication.cpp。AndroID使用的CCApplication很简单,直接就去调用AppDelegate的applicationDIDFinishLaunching回调函数创建好了当前游戏场景。因为AndroID的窗口创建是通过Java Cocos2dxActivity实现的。

但AndroID怎么走到CCDirector::sharedDirector()->mainLoop()呢?毕竟mainLoop()才是Cocos2Dx的主循环。CCApplication的run()实现只是调用了applicationDIDFinishLaunching。我们反过来看,首先看AndroID上谁调用了CCDirector::sharedDirector()->mainLoop():

cocos2dx\platform\androID\jni&IExcl;¢Java_org_cocos2dx_lib_Cocos2dxRenderer.cpp

3 JNIEXPORT JNICALLJava_org_cocos2dx_lib_Cocos2dxRenderer_nativeRender(jnienv*env){ @H_301_45@ cocos2d::CCDirector::sharedDirector()->mainLoop(); Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeRender是一个本地方法,从名字上,可以看出它映射到的Java方法是:Cocos2dxRenderer的private static native voID nativeRender()。这个Java方法会在Cocos2dxRenderer的voID onDrawFrame(final GL10 gl)中被唯一地调用。

onDrawFrame是androID.opengl.GLSurfaceVIEw.Renderer提供了一个专供覆盖的方法。用来为GLSurfaceVIEw提供渲染所需的Render。

GLSurfaceVIEw是一个很好的基类对于构建一个使用OpenGL ES进行部分或全部渲染的应用程序。它可以帮助我们更容易地使用OpenGL ES渲染你的应用程序。它提供了粘合代码把OpenGL ES连接到你的视图系统,也提供粘合代码使得OpenGL ES按照Acticity(活动)的生命周期工作,它创建和管理一个独立的渲染线程,产生平滑的动画。 在AndroID上开发动画,主要也是提供一个自己的继承自androID.opengl.GLSurfaceVIEw.Renderer的Render。

androID.opengl.GLSurfaceVIEw.Renderer还有两个方法:

onSurfaceCreated() :在开始渲染的时候被调用

onSurfaceChanged():该方法在Surface大小改变时被调用

onSurfaceChanged在Cocos2Dx中什么也不需要做。onSurfaceCreated会调用Cocos2dxRenderer.nativeInit(this.mScreenWIDth,this.mScreenHeight),对应的C++原生代码一般都是App自己提供的实现。比如:

samples\Cpp\HelloCpp\proj.androID\jni\hellocpp\main.cpp

19 Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeInit(jnienv*env,jobjectthiz,jintw,jinth)
@H_301_45@ (!CCDirector::sharedDirector()->getopenGLVIEw()) { CCEGLVIEw*vIEw=CCEGLVIEw::sharedOpenGLVIEw(); vIEw->setFrameSize(w,h); AppDelegate*pAppDelegate= AppDelegate(); CCApplication::sharedApplication()->run(); } else ccGlinvalIDateStateCache(); CCshadercache::sharedshadercache()->reloadDefaultShaders(); ccDrawInit(); CCTextureCache::reloadAllTextures(); @H_984_403@ CCNotificationCenter::sharednotificationCenter()->postNotification(EVENT_COME_TO_FOREGROUND,NulL); CCDirector::sharedDirector()->setGLDefaultValues(); } AndroID进一步的细节已经涉及到AndroID内部实现。这里就不进一步讨论了。

回到CCdisplaylinkDirector::mainLoop()。由于CCDirector只有CCdisplaylinkDirector一个子类,所以代码中使用CCDirector::mainLoop()的地方就是CCdisplaylinkDirector::mainLoop()。

13 CCdisplaylinkDirector::mainLoop(
) @H_301_45@ (m_bPurgeDirecotorInNextLoop) m_bPurgeDirecotorInNextLoop= ; purgeDirector(); } else (!m_bInvalID) { drawScene(); CCPoolManager::sharedPoolManager()->pop(); } 如果程序调用了CCDirector的end()函数,设置m_bPurgeDirecotorInNextLoop为true,意味需要进行CCDirector的清理工作了。end()并不会自己做清理工作。设置m_bPurgeDirecotorInNextLoop后,Cocos2Dx还是会等到帧间隔时间到期以后,才真正地去做清理工作。

如果程序并没有调用end(),还在继续运行,每当帧间隔时间到期以后,调用drawScene()绘制新的场景。随后,做一次内存回收池的释放。

40 CCDirector::drawScene(
//计算上次drawScene到现在的时间变化dt,dt会传给调度器 calculateDeltaTime(); (!m_bPaused) { m_pScheduler->update(m_fDeltaTime); } //清除当前缓冲区的颜色缓冲和深度缓冲 glClear(GL_color_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); (m_pNextScene) { setNextScene(); } kmGLPushmatrix(); @H_984_403@ //绘制当前的场景 (m_pRunningScene) { m_pRunningScene->visit(); } //通知setNotificationNode注册的CCNode (m_pNotificationNode) { m_pNotificationNode->visit(); } (m_bdisplayStats) showStats(); } kmGLPopMatrix(); m_uTotalFrames++; (m_pobOpenGLVIEw) { m_pobOpenGLVIEw->swapBuffers(); } (m_bdisplayStats) { calculateMPF(); } drawScene会调用CCScene的visit()方法。CCScene使用的是基类CCNode的visit()方法。

50 51 52 53 54 55 56 57 58 CCNode::visit()
@H_301_45@ //如果当前Scene不可见,直接返回,它的子节点也同样不会被绘制 (!m_bVisible) ; kmGLPushmatrix(); (m_pGrID&&m_pGrID->isActive()) { m_pGrID->beforeDraw(); } ->transform(); CCNode*pNode=NulL; unsigned i=0; @H_984_403@ (m_pChildren&&m_pChildren->count()>0) { //对当前Scene的所有子节点进行排序 sortAllChildren(); //先绘制zOrder<0的子节点 ccArray*arrayData=m_pChildren->data; for (;i<arrayData->num;i++) pNode=(CCNode*)arrayData->arr[i]; (pNode&&pNode->m_nZOrder<0) { //绘制子节点,可以是CCScene,CCSprite,cclayer pNode->visit(); else { break ; } //绘制本Scene ->draw(); //最后绘制zOrder>=0的子节点 (;i<arrayData->num;i++) { pNode=(CCNode*)arrayData->arr[i]; (pNode) { pNode->visit(); } } } else { //绘制本Scene ->draw(); } m_uOrderOfArrival=0; (m_pGrID&&m_pGrID->isActive()) { m_pGrID->afterDraw( ); } kmGLPopMatrix(); CCNode::visit()通过zOrder来绘制自身和放在当前Scene里面的子节点。zOrder 越大,绘制出来的结果越在上层。子节点的绘制交给子节点自己处理,做了很好的隔离。visit()只是CCNode的访问入口,真正的绘制是CCNode的draw()完成的。CCNode的不同子类,比如CCSprite,cclayer都有自己的实现,但CCScene默认使用的是CCNode的空实现。

到现在为止,我们已经能够看到一个游戏的界面了。

总结

以上是内存溢出为你收集整理的Cocos2Dx之游戏启动过程-欧阳左至全部内容,希望文章能够帮你解决Cocos2Dx之游戏启动过程-欧阳左至所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/web/1076341.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-26
下一篇 2022-05-26

发表评论

登录后才能评论

评论列表(0条)