Cocos2d-x學習筆記(二) 永遠的HelloWorld

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

win32目錄中包含了對應平臺的代碼,而Classes目錄中包含了咱們本身的實現代碼。編譯運行的結果以下圖node

 


 main函數變形記linux


  

看到main命名的文件就會想到著名的main函數必定在這個文件裏面,那麼就讓咱們先看看這個文件吧。main.h裏面主要是include了各類各樣的頭文件,main.cpp是咱們真正須要關注的android

 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  APIENTRYexpress

首先出現的不明物體是APIENTRY。咱們先看看它的定義windows

1 #define APIENTRY    WINAPI

APIENTRY是WINAPI的一個替身,而WINAPI是微軟定義的宏,實際就是__stdcall設計模式

1 #define WINAPI      __stdcall

衆所周知,__stdcall聲明瞭函數從右至左將參數壓棧並由被調用者清理堆棧的調用約定app

 

1.2  _tWinMain框架

若是我說_tWinMain是程序的入口函數,你會不會發飆:我去,入口函數不該該是main嗎?是的!對於常規的C/C++程序而言main是入口函數的歸宿,但在Windows程序中WinMain纔是入口函數。iphone

好吧!但爲何這裏對的入口函數是_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的宗族關係

要把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之後卻從未使用過它。那麼咱們是否是能夠理解爲這句是多餘的呢?好吧!咱們把它註釋掉,結果程序崩潰了!崩潰了!!潰了!!!

這是由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的繼承關係

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.一、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

相關文章
相關標籤/搜索