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

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
{
public :
     AppDelegate();
     virtual  ~AppDelegate();
     virtual  bool  applicationDidFinishLaunching();
     virtual  void  applicationDidEnterBackground();
     virtual  void  applicationWillEnterForeground();
};

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

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
bool  AppDelegate::applicationDidFinishLaunching()
{
     CCDirector *pDirector = CCDirector::sharedDirector();
     pDirector->setOpenGLView(CCEGLView::sharedOpenGLView());
     CCSize screenSize = CCEGLView::sharedOpenGLView()->getFrameSize();
     CCSize designSize = 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为例。

?
1
2
3
4
5
6
7
8
9
10
int  APIENTRY _tWinMain( HINSTANCE  hInstance,  HINSTANCE  hPrevInstance,  LPTSTR  lpCmdLine,  int  nCmdShow)
{
     UNREFERENCED_PARAMETER(hPrevInstance);
     UNREFERENCED_PARAMETER(lpCmdLine);
     AppDelegate app;
     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函数将刚刚创建的窗口进行注册,操作系统将触控事件发送给我们创建的窗口。这个窗口后面会作为游戏界面的容器,我们的游戏也就能够接收到操作系统发送过来的触控消息了。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
bool  CCEGLView::Create()
{
     bool  bRet =  false ;
     do
     {
         CC_BREAK_IF(m_hWnd);
         HINSTANCE  hInstance = GetModuleHandle( NULL );
         WNDCLASS wc;  // Windows Class Structure
         wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
         wc.lpfnWndProc = _WindowProc;  // WndProc Handles Messages
         wc.cbClsExtra = 0;  // No Extra Window Data
         wc.cbWndExtra = 0;  // No Extra Window Data
         wc.hInstance = hInstance;  // Set The Instance
         wc.hIcon = LoadIcon( NULL, IDI_WINLOGO );  // Load The Default Icon
         wc.hCursor = LoadCursor( NULL, IDC_ARROW );  // Load The Arrow Pointer
         wc.hbrBackground = NULL;  // No Background Required For GL
         wc.lpszMenuName = m_menu;  //
         wc.lpszClassName = kWindowClassName;  // Set The Class Name
         CC_BREAK_IF(! RegisterClass(&wc) && 1410 != GetLastError());
         RECT rcDesktop;
         GetWindowRect(GetDesktopWindow(), &rcDesktop);
         WCHAR  wszBuf[50] = {0};
         MultiByteToWideChar(CP_UTF8, 0, m_szViewName, -1, wszBuf,  sizeof (wszBuf));
         m_hWnd = CreateWindowEx(
             WS_EX_APPWINDOW | WS_EX_WINDOWEDGE,  // Extended Style For The Window
             kWindowClassName,  // Class Name
             wszBuf,  // Window Title
             WS_CAPTION | WS_POPUPWINDOW | WS_MINIMIZEBOX,  // Defined Window Style
             0, 0,  // Window Position
             //TODO: Initializing width with a large value to avoid getting a wrong client area by 'GetClientRect' function.
             1000,  // Window Width
             1000,  // Window Height
             NULL,  // No Parent Window
             NULL,  // No Menu
             hInstance,  // Instance
             NULL );
         CC_BREAK_IF(! m_hWnd);
         bRet = initGL();
   if (!bRet) destroyGL();
         CC_BREAK_IF(!bRet);
         s_pMainWindow =  this ;
         bRet =  true ;
     while  (0);
     m_bSupportTouch = CheckTouchSupport();
     if (m_bSupportTouch)
  {
      m_bSupportTouch = (s_pfRegisterTouchWindowFunction(m_hWnd, 0) != 0);
     }
     return  bRet;
}

在WIN 32入口函数_tWinMain的最后,我们调用CCApplication::sharedApplication()->run(),实际上是调用的是cocos2dx\platform\win32\CCApplication.cpp的run函数。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
int  CCApplication::run()
{
     //在注册表里设置PVRFrame的快捷键。PVRFrame是由Imagination Technologies开发的一套模拟OpenGL ES的环境。               
     //http://community.imgtec.com/developers/powervr/tools/pvrvframe/
     PVRFrameEnableControlWindow( false );
     //目的是获取精确时间
     QueryPerformanceFrequency(&nFreq);
     QueryPerformanceCounter(&nLast);
     //调用AppDelegate的applicationDidFinishLaunching回调函数创建好了当前游戏场景
     if  (!applicationDidFinishLaunching())
     {
         return  0;
     }
     //游戏场景已经设置了,可以显示窗口
     CCEGLView* pMainWnd = CCEGLView::sharedOpenGLView();
     pMainWnd->centerWindow();
     ShowWindow(pMainWnd->getHWnd(), SW_SHOW);
     //主循环
     while  (1)
     {
         if  (! PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
         {
             // 获取当前时间
             QueryPerformanceCounter(&nNow);
             // 达到了指定的时间间隔,绘制下一帧,否则放弃CPU
             if  (nNow.QuadPart - nLast.QuadPart > m_nAnimationInterval.QuadPart)
             {
                 nLast.QuadPart = nNow.QuadPart;
                 CCDirector::sharedDirector()->mainLoop();
             }
             else
             {
                 Sleep(0);
             }
             continue ;
         }
     }
     return  ( int ) 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¡¢Java_org_cocos2dx_lib_Cocos2dxRenderer.cpp

?
1
2
3
JNIEXPORT  void  JNICALL Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeRender(JNIEnv* env) {
     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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void  Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeInit(JNIEnv* env, jobject thiz, jint w, jint h)
{
     if  (!CCDirector::sharedDirector()->getOpenGLView())
     {
         CCEGLView *view = CCEGLView::sharedOpenGLView();
         view->setFrameSize(w, h);
         AppDelegate *pAppDelegate =  new  AppDelegate();
         CCApplication::sharedApplication()->run();
     }
     else
     {
         ccGLInvalidateStateCache();
         CCShaderCache::sharedShaderCache()->reloadDefaultShaders();
         ccDrawInit();
         CCTextureCache::reloadAllTextures();
         CCNotificationCenter::sharedNotificationCenter()->postNotification(EVENT_COME_TO_FOREGROUND, NULL);
         CCDirector::sharedDirector()->setGLDefaultValues();
     }
}

Android进一步的细节已经涉及到Android内部实现。这里就不进一步讨论了。

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
void  CCDisplayLinkDirector::mainLoop( void )
{
     if  (m_bPurgeDirecotorInNextLoop)
     {
         m_bPurgeDirecotorInNextLoop =  false ;
         purgeDirector();
     }
     else  if  (! m_bInvalid)
      {
          drawScene();
          CCPoolManager::sharedPoolManager()->pop();
      }
}

如果程序调用了CCDirector的end()函数,设置m_bPurgeDirecotorInNextLoop为true,意味需要进行CCDirector的清理工作了。end()并不会自己做清理工作。设置m_bPurgeDirecotorInNextLoop后,Cocos2Dx还是会等到帧间隔时间到期以后,才真正地去做清理工作。

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
void  CCDirector::drawScene( void )
{
     //计算上次drawScene到现在的时间变化dt,dt会传给调度器
     calculateDeltaTime();
     if  (! m_bPaused)
     {
         m_pScheduler->update(m_fDeltaTime);
     }
     //清除当前缓冲区的颜色缓冲和深度缓冲
     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
     if  (m_pNextScene)
     {
         setNextScene();
     }
     kmGLPushMatrix();
     //绘制当前的场景
     if  (m_pRunningScene)
     {
         m_pRunningScene->visit();
     }
     //通知setNotificationNode注册的CCNode
     if  (m_pNotificationNode)
     {
         m_pNotificationNode->visit();
     }
     if  (m_bDisplayStats)
     {
         showStats();
     }
     kmGLPopMatrix();
     m_uTotalFrames++;
     if  (m_pobOpenGLView)
     {
         m_pobOpenGLView->swapBuffers();
     }
     if  (m_bDisplayStats)
     {
         calculateMPF();
     }
}

drawScene会调用CCScene的visit()方法。CCScene使用的是基类CCNode的visit()方法。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
void  CCNode::visit()
{
     // 如果当前Scene不可见,直接返回,它的子节点也同样不会被绘制
     if  (!m_bVisible)
     {
         return ;
     }
     kmGLPushMatrix();
     if  (m_pGrid && m_pGrid->isActive())
     {
         m_pGrid->beforeDraw(); 
     }
     this ->transform();
     CCNode* pNode = NULL;
     unsigned  int  i = 0;
     if (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];
             if  ( pNode && pNode->m_nZOrder < 0 )
             {
                 // 绘制子节点,可以是CCScene,CCSprite,CCLayer
                 pNode->visit();
             }
             else
             {
                 break ;
             }
         }
         // 绘制本Scene
         this ->draw();
        // 最后绘制zOrder >= 0 的子节点
         for ( ; i < arrayData->num; i++ )
         {
             pNode = (CCNode*) arrayData->arr[i];
             if  (pNode)
             {
                 pNode->visit();
             }
         }
     }
     else
     {
         // 绘制本Scene
         this ->draw();
     }
     m_uOrderOfArrival = 0;
     if  (m_pGrid && m_pGrid->isActive())
     {
          m_pGrid->afterDraw( this );
     }
     kmGLPopMatrix();
}

CCNode::visit()通过zOrder来绘制自身和放在当前Scene里面的子节点。zOrder 越大,绘制出来的结果越在上层。子节点的绘制交给子节点自己处理,做了很好的隔离。visit()只是CCNode的访问入口,真正的绘制是CCNode的draw()完成的。CCNode的不同子类,比如CCSprite,CCLayer都有自己的实现,但CCScene默认使用的是CCNode的空实现。

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值