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

前端之家收集整理的这篇文章主要介绍了Cocos2Dx之游戏启动过程-欧阳左至前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
@H_404_0@ Cocos2Dx应用由导演(CCDirector)、场景(CCSprite)、层(CCLayer)和精灵(CCSprite)构成。导演负责管理整个游戏。映射到操作系统,每个操作系统需要的应用入口都不一样。可以看到CCApplication是平台相关的,每个操作系统平台都有自己的CCApplication实现 ,具体代码放在platform下面。为了对开发者透明,Cocos2Dx给出了AppDelegate,一个AppDelegate就是继承自CCApplication的一个代表该应用的代理者。Cocos2Dx提供了Python脚本帮我我们生成所有平台的项目文件,自己不用手动去选择设置合适的CCApplication。

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

? @H_403_9@
1 @H_403_9@
2 @H_403_9@
3 @H_403_9@
4 @H_403_9@
5 @H_403_9@
6 @H_403_9@
7 @H_403_9@
8 @H_403_9@
9 @H_403_9@
class AppDelegate: private cocos2d::CCApplication @H_403_9@
{ @H_403_9@
public : @H_403_9@
AppDelegate(); @H_403_9@
virtual ~AppDelegate(); @H_403_9@
virtual bool applicationDidFinishLaunching(); @H_403_9@
virtual void applicationDidEnterBackground(); @H_403_9@
applicationWillEnterForeground(); @H_403_9@
}; @H_403_9@ @H_403_9@
@H_403_9@ @H_403_9@

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

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

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

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

10 @H_403_9@ 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_403_9@
UNREFERENCED_PARAMETER(hPrevInstance); @H_403_9@
UNREFERENCED_PARAMETER(lpCmdLine); @H_403_9@
AppDelegateapp; @H_403_9@
CCEGLView*eglView=CCEGLView::sharedOpenGLView(); @H_403_9@
eglView->setViewName( "TestCpp" ); @H_403_9@
eglView->setFrameSize(960,640); @H_403_9@
return CCApplication::sharedApplication()->run(); @H_403_9@
在操作系统的入口函数,我们创建了AppDelegate对象。此时为调用AppDelegate的构造函数,其为空,进一步调用基类CCApplication的构造函数,也只是简单初始化一些成员,没有其他动作。CCEGLView::sharedOpenGLView()构造一个窗口,如果你了解WIN32 API,那么CCEGLView的Create函数看着就非常熟悉。首先创建一个窗口样式WNDCLASS,然后创建一个窗口。窗口创建完毕后,调用initGL()初始化OpenGL。最后,检查是否支持触控,如果支持调用user32.dll里的RegisterTouchWindow函数将刚刚创建的窗口进行注册,操作系统将触控事件发送给我们创建的窗口。这个窗口后面会作为游戏界面的容器,我们的游戏也就能够接收到操作系统发送过来的触控消息了。

14 @H_403_9@
15 @H_403_9@
16 @H_403_9@
17 @H_403_9@
18 @H_403_9@
19 @H_403_9@
20 @H_403_9@
21 @H_403_9@
22 @H_403_9@
23 @H_403_9@
24 @H_403_9@
25 @H_403_9@
26 @H_403_9@
27 @H_403_9@
28 @H_403_9@
29 @H_403_9@
30 @H_403_9@
31 @H_403_9@
32 @H_403_9@
33 @H_403_9@
34 @H_403_9@
35 @H_403_9@
36 @H_403_9@
37 @H_403_9@ @H_218_301@ 38 @H_403_9@
39 @H_403_9@
40 @H_403_9@
41 @H_403_9@
42 @H_403_9@
43 @H_403_9@
44 @H_403_9@
45 @H_403_9@
46 @H_403_9@
47 @H_403_9@
48 @H_403_9@
49 @H_403_9@
50 @H_403_9@ CCEGLView::Create() @H_403_9@
bRet= false ; @H_403_9@
do @H_403_9@
{ @H_403_9@
CC_BREAK_IF(m_hWnd); @H_403_9@
hInstance=GetModuleHandle(NULL); @H_403_9@
WNDCLASSwc; //WindowsClassStructure @H_403_9@
wc.style=CS_HREDRAW|CS_VREDRAW|CS_OWNDC; @H_403_9@
wc.lpfnWndProc=_WindowProc; //WndProcHandlesMessages @H_403_9@
wc.cbClsExtra=0; //NoExtraWindowData @H_403_9@
wc.cbWndExtra=0; //NoExtraWindowData @H_403_9@
wc.hInstance=hInstance; //SetTheInstance @H_403_9@
wc.hIcon=LoadIcon(NULL,IDI_WINlogo); //LoadTheDefaultIcon @H_403_9@
wc.hCursor=LoadCursor(NULL,IDC_ARROW); //LoadTheArrowPointer @H_403_9@ @H_492_403@ wc.hbrBackground=NULL; //NoBackgroundrequiredForGL @H_403_9@
wc.lpszMenuName=m_menu; // @H_403_9@
wc.lpszClassName=kWindowClassName; //SetTheClassName @H_403_9@
CC_BREAK_IF(!RegisterClass(&wc)&&1410!=GetLastError()); @H_403_9@
RECTrcDesktop; @H_403_9@
GetWindowRect(GetDesktopWindow(),&rcDesktop); @H_403_9@
WCHAR wszBuf[50]={0}; @H_403_9@
MultiByteToWideChar(CP_UTF8,m_szViewName,-1,wszBuf, sizeof (wszBuf)); @H_403_9@
m_hWnd=CreateWindowEx( @H_403_9@
WS_EX_APPWINDOW|WS_EX_WINDOWEDGE, //ExtendedStyleForTheWindow @H_403_9@
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 @H_403_9@
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 @H_403_9@
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 @H_403_9@
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 @H_403_9@
//TODO:Initializingwidthwithalargevaluetoavoidgettingawrongclientareaby'GetClientRect'function. @H_403_9@
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 @H_403_9@
//WindowHeight @H_403_9@
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 @H_403_9@
//NoMenu @H_403_9@
//Instance @H_403_9@
NULL); @H_403_9@
CC_BREAK_IF(!m_hWnd); @H_403_9@
bRet=initGL(); @H_403_9@
if (!bRet)destroyGL(); @H_403_9@
CC_BREAK_IF(!bRet); @H_403_9@
s_pMainWindow= this ; @H_403_9@
; @H_403_9@
} while (0); @H_403_9@
m_bSupportTouch=CheckTouchSupport(); @H_403_9@
(m_bSupportTouch) @H_403_9@
{ @H_403_9@
m_bSupportTouch=(s_pfRegisterTouchWindowFunction(m_hWnd,0)!=0); @H_403_9@
} @H_403_9@
bRet; @H_403_9@
在WIN 32入口函数_tWinMain的最后,我们调用CCApplication::sharedApplication()->run(),实际上是调用的是cocos2dx\platform\win32\CCApplication.cpp的run函数

39 @H_403_9@ CCApplication::run() @H_403_9@
//在注册表里设置PVRFrame的快捷键。PVRFrame是由ImaginationTechnologies开发的一套模拟OpenGLES的环境。 @H_403_9@
//http://community.imgtec.com/developers/powervr/tools/pvrvframe/ @H_403_9@
PVRFrameEnableControlWindow( ); @H_403_9@
//目的是获取精确时间 @H_403_9@
QueryPerformanceFrequency(&nFreq); @H_403_9@
QueryPerformanceCounter(&nLast); @H_403_9@
//调用AppDelegate的applicationDidFinishLaunching回调函数创建好了当前游戏场景 @H_403_9@
(!applicationDidFinishLaunching()) @H_403_9@
{ @H_403_9@
0; @H_403_9@
} @H_403_9@
//游戏场景已经设置了,可以显示窗口 @H_403_9@
CCEGLView*pMainWnd=CCEGLView::sharedOpenGLView(); @H_403_9@ @H_492_403@ pMainWnd->centerWindow(); @H_403_9@
ShowWindow(pMainWnd->getHWnd(),SW_SHOW); @H_403_9@
//主循环 @H_403_9@
(1) @H_403_9@
{ @H_403_9@
(!PeekMessage(&msg,NULL,PM_REMOVE)) @H_403_9@
{ @H_403_9@
//获取当前时间 @H_403_9@
QueryPerformanceCounter(&nNow); @H_403_9@
//达到了指定的时间间隔,绘制下一帧,否则放弃cpu @H_403_9@
(nNow.QuadPart-nLast.QuadPart>m_nAnimationInterval.QuadPart) @H_403_9@
{ @H_403_9@
nLast.QuadPart=nNow.QuadPart; @H_403_9@
CCDirector::sharedDirector()->mainLoop(); @H_403_9@
} @H_403_9@
else @H_403_9@
{ @H_403_9@
Sleep(0); @H_403_9@
} @H_403_9@
continue ; @H_403_9@
} @H_403_9@
} @H_403_9@
( )msg.wParam; @H_403_9@
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¡¢Java_org_cocos2dx_lib_Cocos2dxRenderer.cpp

3 @H_403_9@
JNIEXPORT JNICALLJava_org_cocos2dx_lib_Cocos2dxRenderer_nativeRender(JNIEnv*env){ @H_403_9@
cocos2d::CCDirector::sharedDirector()->mainLoop(); @H_403_9@
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 @H_403_9@ Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeInit(JNIEnv*env,jobjectthiz,jintw,jinth) @H_403_9@
(!CCDirector::sharedDirector()->getOpenGLView()) @H_403_9@
{ @H_403_9@
CCEGLView*view=CCEGLView::sharedOpenGLView(); @H_403_9@
view->setFrameSize(w,h); @H_403_9@
AppDelegate*pAppDelegate= AppDelegate(); @H_403_9@
CCApplication::sharedApplication()->run(); @H_403_9@
} @H_403_9@
else @H_403_9@
ccGLInvalidateStateCache(); @H_403_9@
CCShaderCache::sharedShaderCache()->reloadDefaultShaders(); @H_403_9@
ccDrawInit(); @H_403_9@
CCTextureCache::reloadAllTextures(); @H_403_9@ @H_492_403@ CCNotificationCenter::sharedNotificationCenter()->postNotification(EVENT_COME_TO_FOREGROUND,NULL); @H_403_9@
CCDirector::sharedDirector()->setGLDefaultValues(); @H_403_9@
} @H_403_9@
Android进一步的细节已经涉及到Android内部实现。这里就不进一步讨论了。

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

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

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

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

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

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

猜你在找的Cocos2d-x相关文章