Cocos2d-x学习笔记(二) 永远的HelloWorld

HelloCpp是Cocos2d-x自带的一个工程,它演示了Cocos2d-x最基本的使用方法和流程。先看一下它的基本构成

Cocos2d-x学习笔记(二) 永远的HelloWorld_第1张图片

win32目录中包含了对应平台的代码,而Classes目录中包含了我们自己的实现代码。编译运行的结果如下图

Cocos2d-x学习笔记(二) 永远的HelloWorld_第2张图片

 


 main函数变形记


  

看到main命名的文件就会想到著名的main函数一定在这个文件里面,那么就让我们先看看这个文件吧。main.h里面主要是include了各种各样的头文件,main.cpp是我们真正需要关注的

 1 int APIENTRY _tWinMain(HINSTANCE hInstance,
 2                        HINSTANCE hPrevInstance,
 3                        LPTSTR    lpCmdLine,
 4                        int       nCmdShow)
 5 {
 6     UNREFERENCED_PARAMETER(hPrevInstance);
 7     UNREFERENCED_PARAMETER(lpCmdLine);
 8 
 9     // create the application instance
10     AppDelegate app;
11     CCEGLView* eglView = CCEGLView::sharedOpenGLView();
12     eglView->setFrameSize(960, 640 );
13     return CCApplication::sharedApplication()->run();
14 }

 

1.1  APIENTRY

首先出现的不明物体是APIENTRY。我们先看看它的定义

1 #define APIENTRY    WINAPI

APIENTRY是WINAPI的一个替身,而WINAPI是微软定义的宏,实际就是__stdcall

1 #define WINAPI      __stdcall

众所周知,__stdcall声明了函数从右至左将参数压栈并由被调用者清理堆栈的调用约定。

 

1.2  _tWinMain

如果我说_tWinMain是程序的入口函数,你会不会发飙:我去,入口函数不应该是main吗?是的!对于常规的C/C++程序而言main是入口函数的归宿,但在Windows程序中WinMain才是入口函数。

好吧!但为什么这里对的入口函数是_tWinMain而不是WinMain呢?我们先来看看_tWinMain的真正定义
1 #ifdef _UNICODE
2 
3     #define _tWinMain wWinMain
4 
5 #else
6 
7     #define _tWinMain WinMain
8 
9 #endif
为了支持UNICODE,C运行库对WinMain区分了UNICODE版和ANSI版。对UNICODE版的程序,C运行库将调用wWinMain;而对于ANSI版的应用,则调用WinMain。至于WinMain更深入的知识请阅读 《Windows程序设计》,这里只需要知道它是Windows程序的入口函数即可。
 

1.3  UNREFERENCED_PARAMETER

进入_tWinMain函数后就是两句很神奇的语句

1 UNREFERENCED_PARAMETER(hPrevInstance);
2 UNREFERENCED_PARAMETER(lpCmdLine);

要理解这句话首先是要搞清楚UNREFERENCED_PARAMETER是神马玩意儿。如果我告诉你它神马都不是,你信吗?

1 #define UNREFERENCED_PARAMETER(P)          (P)

承认吧骚年,它真的神马都不是啊~(你逗我玩儿呐?)

 


 AppDelegate的前世今生


  

_tWinMain函数的真正主体是从AppDelegate app开始的,所以我们就首先从AppDelegate说起。我们先看看AppDelegate的宗族关系

Cocos2d-x学习笔记(二) 永远的HelloWorld_第3张图片

要把AppDelegate弄清楚搞明白,我们还得从源头开始。

 

2.1  CCApplicationProtocol

作为Cocos2d-x Application的源头,CCApplicationProtocal定义如下

 1 class CC_DLL CCApplicationProtocol
 2 {
 3 public:
 4 
 5     virtual ~CCApplicationProtocol() {}
 6 
 7     /**
 8     @brief    Implement CCDirector and CCScene init code here.
 9     @return true    Initialize success, app continue.
10     @return false   Initialize failed, app terminate.
11     */
12     virtual bool applicationDidFinishLaunching() = 0;
13 
14     /**
15     @brief  The function be called when the application enter background
16     @param  the pointer of the application
17     */
18     virtual void applicationDidEnterBackground() = 0;
19 
20     /**
21     @brief  The function be called when the application enter foreground
22     @param  the pointer of the application
23     */
24     virtual void applicationWillEnterForeground() = 0;
25 
26     /**
27     @brief    Callback by CCDirector for limit FPS.
28     @interval       The time, expressed in seconds, between current frame and next. 
29     */
30     virtual void setAnimationInterval(double interval) = 0;
31 
32     /**
33     @brief Get current language config
34     @return Current language config
35     */
36     virtual ccLanguageType getCurrentLanguage() = 0;
37     
38     /**
39      @brief Get target platform
40      */
41     virtual TargetPlatform getTargetPlatform() = 0;
42 };

可以看到,CCApplicationProtocol是一个抽象类,它定义并导出作为DLL的接口。这其中有一个陌生CC_DLL,它定义了在DLL中的符号是导出还是导入

1 #if defined(_USRDLL)
2     #define CC_DLL     __declspec(dllexport)
3 #else         /* use a DLL library */
4     #define CC_DLL     __declspec(dllimport)
5 #endif

整个CCApplicationProtocol除了析构函数以外的其他所有函数都是纯虚函数,这也就是它为什么叫Protocol的原因。每个函数的含义和作用在注释里有简要的说明,但具体的实现何其作用需要进一步才能理解。

 

2.2 CCApplication

作为对Cocos2d-x Application的抽象,CCApplication所扮演的角色是非常重要的。它的定义如下

 1 class CC_DLL CCApplication : public CCApplicationProtocol
 2 {
 3 public:
 4     CCApplication();
 5     virtual ~CCApplication();
 6 
 7     /**
 8     @brief    Run the message loop.
 9     */
10     int run();
11 
12     /**
13     @brief    Get current applicaiton instance.
14     @return Current application instance pointer.
15     */
16     static CCApplication* sharedApplication();
17 
18     /* override functions */
19     virtual void setAnimationInterval(double interval);
20     virtual ccLanguageType getCurrentLanguage();
21     
22     /**
23      @brief Get target platform
24      */
25     virtual TargetPlatform getTargetPlatform();
26 
27     /* set the Resource root path */
28     void setResourceRootPath(const std::string& rootResDir);
29 
30     /* get the Resource root path */
31     const std::string& getResourceRootPath(void)
32     {
33         return m_resourceRootPath;
34     }
35 
36     void setStartupScriptFilename(const std::string& startupScriptFile);
37 
38     const std::string& getStartupScriptFilename(void)
39     {
40         return m_startupScriptFilename;
41     }
42 
43 protected:
44     HINSTANCE           m_hInstance;
45     HACCEL              m_hAccelTable;
46     LARGE_INTEGER       m_nAnimationInterval;
47     std::string         m_resourceRootPath;
48     std::string         m_startupScriptFilename;
49 
50     static CCApplication * sm_pSharedApplication;
51 };
虽然CCApplication提供了public的构造函数,但我们却不能直接实例化CCApplication的,因为它没有实现CCApplicationProtocol定义的所有接口函数(它还是一个抽象类)。
就Hello World这个示例而言,我们需要关注的并不多:构造函数、sharedApplication函数和run函数,我们会进一步全面剖析这些函数。

 

2.2.1  构造函数

CCApplication的构造函数所完成的工作就是对成员变量的初始化

1 CCApplication::CCApplication()
2     : m_hInstance(NULL)
3     , m_hAccelTable(NULL)
4 {
5     m_hInstance    = GetModuleHandle(NULL);
6     m_nAnimationInterval.QuadPart = 0;
7     CC_ASSERT(! sm_pSharedApplication);
8     sm_pSharedApplication = this;
9 }

m_hInstance保存了当前模块的句柄,而sm_pSharedApplicaton则保存了当前对象的指针。值得注意的是,sm_pSharedApplication是一个static的CCApplication对象指针

 

2.2.2  Application是唯一的

我们再把_tWinMain函数请出来仔细看看(这里去掉了几句无用的代码)

int APIENTRY _tWinMain(HINSTANCE hInstance,
                       HINSTANCE hPrevInstance,
                       LPTSTR    lpCmdLine,
                       int       nCmdShow)
{
     // create the application instance
    AppDelegate app;
    CCEGLView* eglView = CCEGLView::sharedOpenGLView();
    eglView->setFrameSize(960, 640 );
    return CCApplication::sharedApplication()->run();
}

是否对AppDelegate app这句感到疑惑:在定义了app以后却从未使用过它。那么我们是不是可以理解为这句是多余的呢?好吧!我们把它注释掉,结果程序崩溃了!崩溃了!!溃了!!!

Cocos2d-x学习笔记(二) 永远的HelloWorld_第4张图片

这是由sm_pSharedApplication引发的断言异常。sm_pSharedApplication是CCApplication类的一个protected的static成员,虽然在构造函数中将它赋值为this,但它的初始化却是0(空指针)

1 // sharedApplication pointer
2 CCApplication * CCApplication::sm_pSharedApplication = 0;

同时CCApplication提供一个public的static函数来访问它

1 CCApplication* CCApplication::sharedApplication()
2 {
3     CC_ASSERT(sm_pSharedApplication);
4     return sm_pSharedApplication;
5 }

若果你熟悉设计模式就能一眼看出来这个实现实际上是单例模式,它保证了CCApplication只有一个实例。

看到这里,你可能会惊呼:一定是_tWinMain函数的最后一句 return CCApplication::sharedApplication()->run() 引入了错误。哎,事实总是残酷的!实际上异常早在这之前就发生了。我们在CCApplication::sharedApplication函数中下断点,F5运行程序,从CallStack可以看到异常在CCEGLView::WindowProc函数中就发生了

进入CCEGLView::WindowProc函数,很快我们就能发现引发异常的代码段

 1 case WM_SIZE:
 2         switch (wParam)
 3         {
 4         case SIZE_RESTORED:
 5             CCApplication::sharedApplication()->applicationWillEnterForeground();
 6             break;
 7         case SIZE_MINIMIZED:
 8             CCApplication::sharedApplication()->applicationDidEnterBackground();
 9             break;
10         }
11         break;

WM_SIZE是Windows消息机制中的一个重要消息,每当窗口的大小改变时它就会进入到程序的消息队列中。这段代码就是处理WM_SIZE消息的。当程序启动时,窗口的大小就会发生变化,此时就会调用CCApplication::sharedApplication函数获取CCApplication::sm_pSharedApplication指针。然而,因为我们注释掉了“AppDelegate app”这句代码导致CCApplication的构造函数没有被调用,使得CCApplication::sm_pSharedApplication始终为初始值0,导致调用CCApplication::sharedApplication函数时引发了其内部实现的断言。

是不是被绕晕了?我也有点晕!没关系,我们把CCEGLView插入到这里来分析一下cocos2d-x是如何构成完整Windows程序框架的。熬过这话题就不会晕了。

 

2.2.3  构建Windows程序框架

从经典教科书《Windows程序设计》中可以看到,典型的Windows程序的框架大体如下

1 int WINAPI WinMain(HINSTANCE hInstance,
2                    HINSTANCE hPreInstance,
3                    PSTR      szCmdLine,
4                    int       iCmdShow)
5 {
6     1)注册窗口类,并注册消息处理函数WindowProc
7     2)创建并显示窗口
8     3)循环获取消息
9 }

消息处理函数的结构如下

 1 LRESULT CALLBACK WindowProc(HWND   hwnd, 
 2                             UINT   uMsg,
 3                             WPARAM wParam,
 4                             LPARAM lParam)
 5 {
 6     switch (uMsg)
 7     {
 8         处理各种消息
 9     }
10 }

 对比一下HelloWorld的_tWinMain函数

 1 int APIENTRY _tWinMain(HINSTANCE hInstance,
 2                        HINSTANCE hPrevInstance,
 3                        LPTSTR    lpCmdLine,
 4                        int       nCmdShow)
 5 {
 6     UNREFERENCED_PARAMETER(hPrevInstance);
 7     UNREFERENCED_PARAMETER(lpCmdLine);
 8 
 9     // create the application instance
10     AppDelegate app;
11 
12     // 以下代码对应Windows程序设计的一般框架
13     CCEGLView* eglView = CCEGLView::sharedOpenGLView();
14     eglView->setFrameSize(960, 640 );
15     return CCApplication::sharedApplication()->run();
16 }

首先,我们看一下CCEGLView::sharedOpenGLView函数的实现细节

1 CCEGLView* CCEGLView::sharedOpenGLView()
2 {
3     static CCEGLView* s_pEglView = NULL;
4     if (s_pEglView == NULL)
5     {
6         s_pEglView = new CCEGLView();
7     }
8     return s_pEglView;
9 }

这是单例模式的一种变形,通过CCEGLView::sharedOpenGLView函数始终获取同一个CCEGLView对象的指针。同时它也通过new操作符调用CCEGLView的构造函数实例化了一个CCEGLView对象,而CCEGLView的构造函数只不过是完成了成员变量的初始化。可见,“注册窗口类并同时注册消息处理函数”并非通过CCEGLView::sharedOpenGLView函数完成的。

接下来,我们分析CCEGLView::setFrameSize函数。其实现如下

1 void CCEGLView::setFrameSize(float width, float height)
2 {
3     Create((LPCTSTR)m_szViewName, (int)width, (int)height);
4     CCEGLViewProtocol::setFrameSize(width, height);
5 
6     resize(width, height); // adjust window size for menubar
7     centerWindow();
8 }

哈哈,居然有一个CCEGLView::Create函数

 1 bool CCEGLView::Create(LPCTSTR pTitle, int w, int h)
 2 {
 3     bool bRet = false;
 4     do 
 5     {
 6         CC_BREAK_IF(m_hWnd);
 7         // 创建窗口类
 8         HINSTANCE hInstance = GetModuleHandle( NULL );
 9         WNDCLASS  wc;        // Windows Class Structure
10 
11         // Redraw On Size, And Own DC For Window.
12         wc.style          = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;  
13         wc.lpfnWndProc    = _WindowProc;                    // WndProc Handles Messages
14         wc.cbClsExtra     = 0;                              // No Extra Window Data
15         wc.cbWndExtra     = 0;                              // No Extra Window Data
16         wc.hInstance      = hInstance;                      // Set The Instance
17         wc.hIcon          = LoadIcon( NULL, IDI_WINLOGO );  // Load The Default Icon
18         wc.hCursor        = LoadCursor( NULL, IDC_ARROW );  // Load The Arrow Pointer
19         wc.hbrBackground  = NULL;                           // No Background Required For GL
20         wc.lpszMenuName   = m_menu;                         // 
21         wc.lpszClassName  = kWindowClassName;               // Set The Class Name
22         // 注册窗口类
23         CC_BREAK_IF(! RegisterClass(&wc) && 1410 != GetLastError());        
24 
25         // center window position
26         RECT rcDesktop;
27         GetWindowRect(GetDesktopWindow(), &rcDesktop);
28 
29         WCHAR wszBuf[50] = {0};
30         MultiByteToWideChar(CP_UTF8, 0, m_szViewName, -1, wszBuf, sizeof(wszBuf));
31 
32         // 创建窗口(create window)
33         m_hWnd = CreateWindowEx(
34             WS_EX_APPWINDOW | WS_EX_WINDOWEDGE,                  // Extended Style For The Window
35             kWindowClassName,                                    // Class Name
36             wszBuf,                                              // Window Title
37             WS_CAPTION | WS_POPUPWINDOW | WS_MINIMIZEBOX,        // Defined Window Style
38             0, 0,                                                // Window Position
39             0,                                                   // Window Width
40             0,                                                   // Window Height
41             NULL,                                                // No Parent Window
42             NULL,                                                // No Menu
43             hInstance,                                           // Instance
44             NULL );
45 
46         CC_BREAK_IF(! m_hWnd);
47 
48         resize(w, h);
49 
50         bRet = initGL();
51         CC_BREAK_IF(!bRet);
52         
53         s_pMainWindow = this;
54         bRet = true;
55     } while (0);
56 
57     return bRet;
58 }

在CCEGLView::Create函数中完成了注册窗口类和注册消息处理函数_WindowProc,并且完成了窗口的创建。作为Windows程序的核心函数,我们有必要看看_WindowProc看看

 1 static CCEGLView* s_pMainWindow = NULL;
 2 
 3 static LRESULT CALLBACK _WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
 4 {
 5     if (s_pMainWindow && s_pMainWindow->getHWnd() == hWnd)
 6     {
 7         return s_pMainWindow->WindowProc(uMsg, wParam, lParam);
 8     }
 9     else
10     {
11         return DefWindowProc(hWnd, uMsg, wParam, lParam);
12     }
13 }

_WindowProc函数是一个全局函数,当满足一定条件时就将消息转给s_pMainWindow->WindowProc函数处理。s_pMainWindow是一个全局变量,在CCEGLView::Create中赋值为this指针。那么,我们就得进入CCEGLView::WindowProc看看

  1 LRESULT CCEGLView::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
  2 {
  3     BOOL bProcessed = FALSE;
  4 
  5     switch (message)
  6     {
  7     case WM_LBUTTONDOWN:
  8         if (m_pDelegate && MK_LBUTTON == wParam)
  9         {
 10             POINT point = {(short)LOWORD(lParam), (short)HIWORD(lParam)};
 11             CCPoint pt(point.x/CC_CONTENT_SCALE_FACTOR(), point.y/CC_CONTENT_SCALE_FACTOR());
 12             CCPoint tmp = ccp(pt.x, m_obScreenSize.height - pt.y);
 13             if (m_obViewPortRect.equals(CCRectZero) || m_obViewPortRect.containsPoint(tmp))
 14             {
 15                 m_bCaptured = true;
 16                 SetCapture(m_hWnd);
 17                 int id = 0;
 18                 pt.x *= m_windowTouchScaleX;
 19                 pt.y *= m_windowTouchScaleY;
 20                 handleTouchesBegin(1, &id, &pt.x, &pt.y);
 21             }
 22         }
 23         break;
 24 
 25     case WM_MOUSEMOVE:
 26         if (MK_LBUTTON == wParam && m_bCaptured)
 27         {
 28             POINT point = {(short)LOWORD(lParam), (short)HIWORD(lParam)};
 29             CCPoint pt(point.x/CC_CONTENT_SCALE_FACTOR(), point.y/CC_CONTENT_SCALE_FACTOR());
 30             int id = 0;
 31             pt.x *= m_windowTouchScaleX;
 32             pt.y *= m_windowTouchScaleY;
 33             handleTouchesMove(1, &id, &pt.x, &pt.y);
 34         }
 35         break;
 36 
 37     case WM_LBUTTONUP:
 38         if (m_bCaptured)
 39         {
 40             POINT point = {(short)LOWORD(lParam), (short)HIWORD(lParam)};
 41             CCPoint pt(point.x/CC_CONTENT_SCALE_FACTOR(), point.y/CC_CONTENT_SCALE_FACTOR());
 42             int id = 0;
 43             pt.x *= m_windowTouchScaleX;
 44             pt.y *= m_windowTouchScaleY;
 45             handleTouchesEnd(1, &id, &pt.x, &pt.y);
 46 
 47             ReleaseCapture();
 48             m_bCaptured = false;
 49         }
 50         break;
 51     case WM_SIZE:
 52         switch (wParam)
 53         {
 54         case SIZE_RESTORED:
 55             CCApplication::sharedApplication()->applicationWillEnterForeground();
 56             break;
 57         case SIZE_MINIMIZED:
 58             CCApplication::sharedApplication()->applicationDidEnterBackground();
 59             break;
 60         }
 61         break;
 62     case WM_KEYDOWN:
 63         if (wParam == VK_F1 || wParam == VK_F2)
 64         {
 65             CCDirector* pDirector = CCDirector::sharedDirector();
 66             if (GetKeyState(VK_LSHIFT) < 0 ||  GetKeyState(VK_RSHIFT) < 0 || GetKeyState(VK_SHIFT) < 0)
 67                 pDirector->getKeypadDispatcher()->dispatchKeypadMSG(wParam == VK_F1 ? kTypeBackClicked : kTypeMenuClicked);
 68         }
 69         if ( m_lpfnAccelerometerKeyHook!=NULL )
 70         {
 71             (*m_lpfnAccelerometerKeyHook)( message,wParam,lParam );
 72         }
 73         break;
 74     case WM_KEYUP:
 75         if ( m_lpfnAccelerometerKeyHook!=NULL )
 76         {
 77             (*m_lpfnAccelerometerKeyHook)( message,wParam,lParam );
 78         }
 79         break;
 80     case WM_CHAR:
 81         {
 82             if (wParam < 0x20)
 83             {
 84                 if (VK_BACK == wParam)
 85                 {
 86                     CCIMEDispatcher::sharedDispatcher()->dispatchDeleteBackward();
 87                 }
 88                 else if (VK_RETURN == wParam)
 89                 {
 90                     CCIMEDispatcher::sharedDispatcher()->dispatchInsertText("\n", 1);
 91                 }
 92                 else if (VK_TAB == wParam)
 93                 {
 94                     // tab input
 95                 }
 96                 else if (VK_ESCAPE == wParam)
 97                 {
 98                     // ESC input
 99                     //CCDirector::sharedDirector()->end();
100                 }
101             }
102             else if (wParam < 128)
103             {
104                 // ascii char
105                 CCIMEDispatcher::sharedDispatcher()->dispatchInsertText((const char *)&wParam, 1);
106             }
107             else
108             {
109                 char szUtf8[8] = {0};
110                 int nLen = WideCharToMultiByte(CP_UTF8, 0, (LPCWSTR)&wParam, 1, szUtf8, sizeof(szUtf8), NULL, NULL);
111                 CCIMEDispatcher::sharedDispatcher()->dispatchInsertText(szUtf8, nLen);
112             }
113             if ( m_lpfnAccelerometerKeyHook!=NULL )
114             {
115                 (*m_lpfnAccelerometerKeyHook)( message,wParam,lParam );
116             }
117         }
118         break;
119     case WM_PAINT:
120         PAINTSTRUCT ps;
121         BeginPaint(m_hWnd, &ps);
122         EndPaint(m_hWnd, &ps);
123         break;
124 
125     case WM_CLOSE:
126         CCDirector::sharedDirector()->end();
127         break;
128 
129     case WM_DESTROY:
130         destroyGL();
131         PostQuitMessage(0);
132         break;
133 
134     default:
135         if (m_wndproc)
136         {
137             
138             m_wndproc(message, wParam, lParam, &bProcessed);
139             if (bProcessed) break;
140         }
141         return DefWindowProc(m_hWnd, message, wParam, lParam);
142     }
143 
144     if (m_wndproc && !bProcessed)
145     {
146         m_wndproc(message, wParam, lParam, &bProcessed);
147     }
148     return 0;
149 }

如果我们抛开具体的消息及其处理过程,CCEGLView::WindowProc函数可以简化为

 1 LRESULT CCEGLView::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
 2 {
 3     switch (message)
 4     {
 5         // 处理各种消息
 6     }
 7     // 调用自定义的处理函数
 8     if (m_wndproc && !bProcessed)
 9     {
10         m_wndproc(message, wParam, lParam, &bProcessed);
11     }
12 
13     return 0;
14 }

当然,如果不满足指定的条件就会使用Windows默认的DefWindowProc方法处理消息。

最后,就剩下“循环获取消息”这部分了。这个时候你不是想起了_tWinMain函数的最后一句代码了吗?让我们再看他一眼

1 return CCApplication::sharedApplication()->run();

锁定目标,我们进入CCApplication::run函数探究一番,毕竟这个函数也是我们之前列出的重要函数之一。

 

2.2.4  run出来的消息

CCApplication::run函数的实现如下

 1 int CCApplication::run()
 2 {
 3     PVRFrameEnableControlWindow(false);
 4 
 5     // Main message loop:
 6     MSG msg;
 7     LARGE_INTEGER nFreq;
 8     LARGE_INTEGER nLast;
 9     LARGE_INTEGER nNow;
10 
11     QueryPerformanceFrequency(&nFreq);
12     QueryPerformanceCounter(&nLast);
13 
14     // Initialize instance and cocos2d.
15     if (!applicationDidFinishLaunching())
16     {
17         return 0;
18     }
19 
20     CCEGLView* pMainWnd = CCEGLView::sharedOpenGLView();
21     pMainWnd->centerWindow();
22     ShowWindow(pMainWnd->getHWnd(), SW_SHOW);
23 
24     while (1)
25     {
26         if (! PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
27         {
28             // Get current time tick.
29             QueryPerformanceCounter(&nNow);
30 
31             // If it's the time to draw next frame, draw it, else sleep a while.
32             if (nNow.QuadPart - nLast.QuadPart > m_nAnimationInterval.QuadPart)
33             {
34                 nLast.QuadPart = nNow.QuadPart;
35                 CCDirector::sharedDirector()->mainLoop();
36             }
37             else
38             {
39                 Sleep(0);
40             }
41             continue;
42         }
43 
44         if (WM_QUIT == msg.message)
45         {
46             // Quit message loop.
47             break;
48         }
49 
50         // Deal with windows message.
51         if (! m_hAccelTable || ! TranslateAccelerator(msg.hwnd, m_hAccelTable, &msg))
52         {
53             TranslateMessage(&msg);
54             DispatchMessage(&msg);
55         }
56     }
57 
58     return (int) msg.wParam;
59 }

注意了,这里面居然有一个循环!而且是条件为“真”的循环!我们先把函数简化后再来分析

 1 int CCApplication::run()
 2 {
 3     // 显示窗口
 4     CCEGLView* pMainWnd = CCEGLView::sharedOpenGLView();
 5     pMainWnd->centerWindow();
 6     ShowWindow(pMainWnd->getHWnd(), SW_SHOW);
 7 
 8     while (1)
 9     {
10         if (! PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
11         {
12             // 处理一些数据
13             continue;
14         }
15 
16         if (WM_QUIT == msg.message)
17         {
18             // 退出消息循环(Quit message loop)
19             break;
20         }
21 
22         // 处理快捷键
23     }
24 
25     return (int) msg.wParam;
26 }

这下整个CCApplication::run函数就清晰了,总体来说主要完成了两个功能:

1)显示窗口

2)进入消息循环

到此,典型的Windows程序框架就完整了!如果你还是晕的,请从头再看一遍吧。

 

2.3  AppDelegate

作为对HelloWorld应用程序的抽象,AppDelegate从CCApplication派生而来,重载了纯虚函数

 1 class  AppDelegate : private cocos2d::CCApplication
 2 {
 3 public:
 4     AppDelegate();
 5     virtual ~AppDelegate();
 6 
 7     /**
 8     @brief    Implement CCDirector and CCScene init code here.
 9     @return true    Initialize success, app continue.
10     @return false   Initialize failed, app terminate.
11     */
12     virtual bool applicationDidFinishLaunching();
13 
14     /**
15     @brief  The function be called when the application enter background
16     @param  the pointer of the application
17     */
18     virtual void applicationDidEnterBackground();
19 
20     /**
21     @brief  The function be called when the application enter foreground
22     @param  the pointer of the application
23     */
24     virtual void applicationWillEnterForeground();
25 };

在AppDelegate类中最主要是实现了applicationDidFinishLaunching函数。在程序启动后,执行CCApplication::run函数的过程中就会调用这个函数

 1 bool AppDelegate::applicationDidFinishLaunching()
 2 {
 3     // initialize director
 4     CCDirector *pDirector = CCDirector::sharedDirector();
 5 
 6     pDirector->setOpenGLView(CCEGLView::sharedOpenGLView());
 7     
 8     TargetPlatform target = getTargetPlatform();
 9     
10     if (target == kTargetIpad)
11     {
12         // ipad
13         CCFileUtils::sharedFileUtils()->setResourceDirectory("iphonehd");
14         
15         // don't enable retina because we don't have ipad hd resource
16         CCEGLView::sharedOpenGLView()->setDesignResolutionSize(960, 640, kResolutionNoBorder);
17     }
18     else if (target == kTargetIphone)
19     {
20         // iphone
21         
22         // try to enable retina on device
23         if (true == CCDirector::sharedDirector()->enableRetinaDisplay(true))
24         {
25             // iphone hd
26             CCFileUtils::sharedFileUtils()->setResourceDirectory("iphonehd");
27         }
28         else 
29         {
30             CCFileUtils::sharedFileUtils()->setResourceDirectory("iphone");
31         }
32     }
33     else 
34     {
35         // android, windows, blackberry, linux or mac
36         // use 960*640 resources as design resolution size
37         CCFileUtils::sharedFileUtils()->setResourceDirectory("iphonehd");
38         CCEGLView::sharedOpenGLView()->setDesignResolutionSize(960, 640, kResolutionNoBorder);
39     }
40 
41     // turn on display FPS
42     pDirector->setDisplayStats(true);
43 
44     // set FPS. the default value is 1.0/60 if you don't call this
45     pDirector->setAnimationInterval(1.0 / 60);
46 
47     // create a scene. it's an autorelease object
48     CCScene *pScene = HelloWorld::scene();
49 
50     // run
51     pDirector->runWithScene(pScene);
52 
53     return true;
54 }
这个函数主要是完成CCDirector类和CCScene类对象的初始化,设置资源路径、分辨率大小和帧率(FPS:Frames Per Second);最后通过CCDirector::runWithScene函数开始场景。
对于另外两个函数,他们的实现就相对简单的多(至少代码量就少很多):把所有事情都交给CCDirector去完成
 1 // This function will be called when the app is inactive. When comes a phone call,it's be invoked too
 2 void AppDelegate::applicationDidEnterBackground() 
 3 {
 4     CCDirector::sharedDirector()->stopAnimation();
 5 
 6     // if you use SimpleAudioEngine, it must be pause
 7     // SimpleAudioEngine::sharedEngine()->pauseBackgroundMusic();
 8 }
 9 
10 // this function will be called when the app is active again
11 void AppDelegate::applicationWillEnterForeground() 
12 {
13     CCDirector::sharedDirector()->startAnimation();
14 
15     // if you use SimpleAudioEngine, it must resume here
16     // SimpleAudioEngine::sharedEngine()->resumeBackgroundMusic();
17 }

如注释所陈述的,applicationDidEnterBackground函数会在程序进入“非活跃”状态(即失去窗口焦点)时被调用,而applicationWillEnterForeground函数会在程序进入“活跃”状态(即获得窗口焦点)时被调用(可以自己在这个函数里面下断点看看具体执行的流程)。

 


舞台需要场景


  

演员站在舞台上,却表演于场景中

                                —— by 我挂科了

赋词一句略显文艺范儿,求勿喷!言归正传,首先我们来看看HelloWorld的继承关系

Cocos2d-x学习笔记(二) 永远的HelloWorld_第5张图片

HelloWorld从CCLayer继承,而CCLayer又是一个非常复杂的(至少它的father太多了)。你一定觉得HelloWorld很复杂吧,其实它没有传说中那么复杂

 1 class HelloWorld : public cocos2d::CCLayer
 2 {
 3 public:
 4     // Here's a difference. Method 'init' in cocos2d-x returns bool, instead of returning 'id' in cocos2d-iphone
 5     virtual bool init();  
 6 
 7     // there's no 'id' in cpp, so we recommend returning the class instance pointer
 8     static cocos2d::CCScene* scene();
 9     
10     // a selector callback
11     void menuCloseCallback(CCObject* pSender);
12 
13     // touch callback
14     void ccTouchesBegan(cocos2d::CCSet *pTouches, cocos2d::CCEvent *pEvent);
15     
16     // implement the "static node()" method manually
17     CREATE_FUNC(HelloWorld);
18 };

多么单纯的类啊!你也许会说:CREATE_FUNC是神马东东?它一点都不单纯啊~  

 

3.1、CREATE_FUNC

其实HelloWorld还是很单纯的,因为CREATE_FUNC定义很简单

 1 #define CREATE_FUNC(__TYPE__) \
 2 static __TYPE__* create() \
 3 { \
 4     __TYPE__ *pRet = new __TYPE__(); \
 5     if (pRet && pRet->init()) \
 6     { \
 7         pRet->autorelease(); \
 8         return pRet; \
 9     } \
10     else \
11     { \
12         delete pRet; \
13         pRet = NULL; \
14         return NULL; \
15     } \
16 }

CREATE_FUNC是一个宏,它定义了一个名为create的static函数,该函数完成下面几个事情:

1)创建__TYPE__类型的对象指针

2)如果创建成功,则调用该对象的init函数

a)如果init函数执行成功,则调用该对象的autorelease函数并返回该对象指针

b)如果init函数执行失败,则释放该对象并返回NULL

将这个宏在HelloWorld类中展开,HelloWorld就露出了它的真面目了

 1 class HelloWorld : public cocos2d::CCLayer
 2 {
 3 public:
 4     // Here's a difference. Method 'init' in cocos2d-x returns bool, instead of returning 'id' in cocos2d-iphone
 5     virtual bool init();  
 6 
 7     // there's no 'id' in cpp, so we recommend returning the class instance pointer
 8     static cocos2d::CCScene* scene();
 9     
10     // a selector callback
11     void menuCloseCallback(CCObject* pSender);
12 
13     // touch callback
14     void ccTouchesBegan(cocos2d::CCSet *pTouches, cocos2d::CCEvent *pEvent);
15     
16     // implement the "static node()" method manually
17     static HelloWorld* create() 
18     { 
19         HelloWorld *pRet = new HelloWorld(); 
20         if (pRet && pRet->init()) 
21         { 
22             pRet->autorelease(); 
23             return pRet; 
24         } 
25         else 
26         { 
27             delete pRet; 
28             pRet = NULL; 
29             return NULL; 
30         } 
31     }
32 };

我比较奇怪的是,为什么注释上写的是“static node()”而不是“static create()”呢?隐约听到有人在喊:这一定是笔误!好吧,不要在意这些细节。

 

3.2  梦回AppDelegate

还记得AppDelegate::applicationDidFinishLauching函数吗?它的实现中有这么一句

 1 bool AppDelegate::applicationDidFinishLaunching() 
 2 {
 3     // 其他操作
 4 
 5     // create a scene. it's an autorelease object
 6     CCScene *pScene = HelloWorld::scene();
 7     // run
 8     pDirector->runWithScene(pScene);
 9 
10     // 其他操作
11 }

这是我们的HelloWorld第一次在程序中被使用,那我们就从HelloWorld::scene函数入手吧

 1 CCScene* HelloWorld::scene()
 2 {
 3     // 'scene' is an autorelease object
 4     CCScene *scene = CCScene::create();
 5     
 6     // 'layer' is an autorelease object
 7     HelloWorld *layer = HelloWorld::create();
 8 
 9     // add layer as a child to scene
10     scene->addChild(layer);
11 
12     // return the scene
13     return scene;
14 }

这个函数的实现完成了3个事情:

1)创建了一个空的CCScene对象scene

2)通过上面分析过的HelloWorld::create函数创建了一个HelloWorld对象layer,并将layer作为scene的一个子节点添加到scene中

3)将scene返回

 

3.3  初始化

在前面我们已经展示了HelloWorld::create函数,它创建了HelloWorld对象后会调用该对象的init函数来初始化对象。HelloWorld::init函数实现如下

 1 // on "init" you need to initialize your instance
 2 bool HelloWorld::init()
 3 {
 4     //
 5     // 1. super init first
 6     if ( !CCLayer::init() )
 7     {
 8         return false;
 9     }
10     
11     CCSize visibleSize = CCDirector::sharedDirector()->getVisibleSize();
12     CCPoint origin = CCDirector::sharedDirector()->getVisibleOrigin();
13 
14     /////
15     // 2. add a menu item with "X" image, which is clicked to quit the program
16     //    you may modify it.
17 
18     // add a "close" icon to exit the progress. it's an autorelease object
19     CCMenuItemImage *pCloseItem = CCMenuItemImage::create(
20                                         "CloseNormal.png",
21                                         "CloseSelected.png",
22                                         this,
23                                         menu_selector(HelloWorld::menuCloseCallback));
24     pCloseItem->setPosition(ccp(origin.x + visibleSize.width - pCloseItem->getContentSize().width/2 ,
25                                 origin.y + pCloseItem->getContentSize().height/2));
26     // create menu, it's an autorelease object
27     CCMenu* pMenu = CCMenu::create(pCloseItem, NULL);
28     pMenu->setPosition(CCPointZero);
29     this->addChild(pMenu, 1);
30 
31     /////
32     // 3. add your codes below...
33 
34     // add a label shows "Hello World"
35     // create and initialize a label
36     CCLabelTTF* pLabel = CCLabelTTF::create("Hello World", "Arial", 24);
37     // position the label on the center of the screen
38     pLabel->setPosition(ccp(origin.x + visibleSize.width/2,
39                             origin.y + visibleSize.height - pLabel->getContentSize().height));
40     // add the label as a child to this layer
41     this->addChild(pLabel, 1);
42 
43     // add "HelloWorld" splash screen"
44     CCSprite* pSprite = CCSprite::create("HelloWorld.png");
45     // position the sprite on the center of the screen
46     pSprite->setPosition(ccp(visibleSize.width/2 + origin.x, visibleSize.height/2 + origin.y));
47     // add the sprite as a child to this layer
48     this->addChild(pSprite, 0);
49     
50     // enable standard touch
51     this->setTouchEnabled(true);
52     
53     return true;
54 }

不要看这个函数很长,实际上主要完成了4个事情

1)调用父类的初始化函数(CCLayer::init())完成对继承自父类成员的初始化

2)创建一个菜单(CCMenu)并在其中添加一个菜单项(CCMenuItem),将菜单显示在HelloWorld中。点击这个菜单项可以关闭程序

3)创建一个标签(CCLabel),让它在HelloWorld中显示“HelloWorld”字串

4)创建一个精灵(CCSprite),让它在HelloWorld中显示图片“HelloWorld.png”

HelloWorld::init函数中所创建的都是我们在运行起来的界面中所能看到的东西,这其中涉及到一些类将在后面学习,这里不深究。我比较好奇的是菜单项所实现的功能:点击后关闭程序(不信你亲自点一下试试)。这是怎么实现的呢?仔细分析一下菜单项的创建过程

1 // add a "close" icon to exit the progress. it's an autorelease object
2 CCMenuItemImage *pCloseItem = CCMenuItemImage::create(
3                                     "CloseNormal.png",
4                                     "CloseSelected.png",
5                                     this,
6                                     menu_selector(HelloWorld::menuCloseCallback));

最后一个参数形式很特别啊~ 我们先看看menu_selector是神马东西

1 #define menu_selector(_SELECTOR) (SEL_MenuHandler)(&_SELECTOR)

又是一个宏!它所完成的事情是将_SELECTOR取址并强制转换为SEL_MenuHandler类型。那么SEL_MenuHandler又是什么类型呢?

1 typedef void (CCObject::*SEL_MenuHandler)(CCObject*);

它是一个函数指针,这类函数有一个CCObject*类型的参数。此时我们可以将创建菜单项的代码展开,看看其真实的原貌

1 // add a "close" icon to exit the progress. it's an autorelease object
2 CCMenuItemImage *pCloseItem = CCMenuItemImage::create(
3                                     "CloseNormal.png",
4                                     "CloseSelected.png",
5                                     this,
6                                     (SEL_MenuHandler)&HelloWorld::menuCloseCallback);

HelloWorld::menuCloseCallback函数以回调函数的方式传入菜单项,在点击菜单项时被触发。也就是说实现关闭程序功能的是HelloWorld::menuCloseCallback函数

1 void HelloWorld::menuCloseCallback(CCObject* pSender)
2 {
3     CCDirector::sharedDirector()->end();
4 
5 #if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS)
6     exit(0);
7 #endif
8 }

最终,由CCDirector::end函数完成程序的关闭(在IOS中还需要调用exit函数)。

 


参考文献 


Cocos2d-x 高级开发教程:制作自己的捕鱼达人

  

 


欢迎转载,但请保留原文出处:http://www.cnblogs.com/xieheng/p/3611588.html 

转载于:https://www.cnblogs.com/xieheng/p/3611588.html

你可能感兴趣的:(Cocos2d-x学习笔记(二) 永远的HelloWorld)