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

2023-11-17

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

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

 


 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的宗族关系

要把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.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 高级开发教程:制作自己的捕鱼达人

  


FROM:  http://www.cnblogs.com/xieheng/p/3611588.html





本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Cocos2d-x学习笔记(二) 永远的HelloWorld 的相关文章

  • 解决Jenkins构建前端时node-sass的.node文件下载报错问题的一种方案

    问题背景 公司的npm仓库未跟外网联通 为什么不联通 我也好鸡儿纳闷 使用Jenkins构建前端时 会在下载node sass的 node时报错 默认情况下会从github上去下 node文件 当然也可以通过配置 npmrc文件指定下载路径
  • 浅析Jetty与tomcat区别

    一 Jetty介绍 1 Jetty概述 Jetty是一个开源项目 最初由Mort Bay Consulting公司创建 它的目标是提供一个快速 灵活 可嵌入的Web服务器和Servlet容器 使Java开发人员能够轻松构建高性能的Web应用
  • 如何在 Mac 上录制屏幕?mac录屏教程分享

    您可以为整个屏幕或屏幕上的选定部分录制视频 1 使用 截屏 工具栏 要查看 截屏 工具栏 请同时按下以下三个按键 Shift Command 和 5 您将看到用于录制整个屏幕 录制屏幕的选定部分或拍摄屏幕静态图像的屏幕控制项 录制整个屏幕
  • 泛型的概念

    一 什么是泛型 参数化类型 为什么要引入泛型 1 将不同类型的数据添加到Arraylist中 取出数据要使用时 要进行强制转换 还原 向下转型 2 同时在编写程序时 不会报错 无类型安全监测机制 而结果出错ClassCastExceptio

随机推荐

  • 摄像头在H5的实时播放功能实现历程

    一 问题解决的路程 1 需求来源 因项目发展需求 需要在3D地图上进行实时摄像头监控展示 3D地图是基于浏览器H5页面展示的 在H5页面实时播放摄像头监控就需要可以直接拿到取流地址进行直接播放 以下各大摄像头产商取流方式 海康威视 默认IP
  • 网络层

    网络层 从它的名字可以看出 它解决的是网络与网络之间 即网际的通信问题 而不是同一网段内部的事 用于网络互联的设备都处于网络层 如 路由器 网络交互机等 一个底层网络内部只存在两层 即数据链接层 与 物理层 没有其它层
  • 考研C++/C数据结构之单链表两种查找方法

    继上篇文章我们探讨了单链表的两种创建方法 头插法和尾插法 今天我们来学习一下单链表的两种查找方法 按序查找和按值查找 按序查找的代码实现如下 按位查找 LinkList GetElem LinkList L int i int j 1 Li
  • python是真刑啊!爬虫这样用,离好日子越铐越近了~

    一个程序员写了个爬虫程序 整个公司200多人被端了 不可能吧 刚从朋友听到这个消息的时候 我有点不太相信 做为一名程序员来讲 谁还没有写过几段爬虫呢 只因写爬虫程序就被端有点夸张了吧 朋友说 消息很确认并且已经进入审判阶段了 01 对消息进
  • 求解视觉里程计(基于特征点法)

    目录 1 视觉里程计 VO 2 基于特征点法的视觉里程计算法 2 1 特征点 2 2 ORB特征点的提取与匹配 2 2 1 关键点与描述子 灰度质心法 特征描述子计算 2 2 2 特征点匹配 2 3 特征点法估计相机位姿 2 3 1 对极几
  • MySQL事务简介

    一 事务的起源 原子性 Atomicity 要么全做 要么全不做 一致性 Consistency 数据库中的数据全部符合现实中的约束 隔离型 Isolation 操作以原子性执行 且不同事务操作互不干扰 多种隔离级别 持久性 Durabil
  • Ubuntu18.04配置Seetaface6

    目录 一 下载安装Qt软件 1 安装包下载 2 安装Qt 3 配置 二 下载源码 三 编译工具 四 编译 1 编译OpenRoleZoo 2 编译SeetaAuthorize 3 编译TenniS 五 运行 1 修改lib路径 2 buil
  • 360n6pro刷鸿蒙系统,360N6和N6Pro通用刷机包MIUI9开发版V8.6.9紫火定制版

    本帖最后由 360fans 80867761 于 2018 8 7 19 44 编辑 360N6和N6Pro通用MIUI9开发版V8 6 9紫火定制版刷机包更新指纹解 除了有个小BUG 相机有时候加载有点慢 其他都很正常 无任何推广软件 刷
  • Vue实例选项之【methods】

    methods div h1 site site h1 h1 url url h1 h1 alexa alexa h1 p 通过调用方法返回数据 p div
  • 0欧电阻和磁珠的区别

    来源 B站https www bilibili com video BV1Yi4y1x7JL 0欧姆电阻实际上并不能达到真正的0欧姆 它是一个阻性的阻值极小的电阻 磁珠浅显的可以看成是一个电感 故很多原理图中磁珠的符号是电感的符号 磁珠的直
  • less命令详解-最好用的文档查看命令

    less命令详解 最好用的文档查看命令 其他文件查看命令 less使用场景 less的日常使用 less快捷键 less参数 其他文件查看命令 小文本查看命令 cat 将文件所有内容打印打控制台 tac 将文件所有内容反向打印打控制台 vi
  • C#线程中使用委托实现textbox显示

    delegate void SetTextCallback string text 后加的 好好想一想 参数是SetText带的参数 From www uzhanbao com private void SetText string tex
  • 《Spring源码深度分析》第3章 默认标签的解析

    目录标题 前言 一 Spring默认的四个标签 二 bean标签的解析及注册 1 BeanDefinition下的三个实现类 2 解析BeanDefinition 1 processBeanDefinition 2 parseBeanDef
  • C/C++预定义宏

    MSVC文档 https learn microsoft com en us cpp preprocessor predefined macros view msvc 170 GCC文档 https gcc gnu org onlinedo
  • APS高级计划排程系统:什么是按库存生产(MTS)计划?

    文章目录 前言 什么是按库存生产 MTS 按库存交货的缺点 MTS MTS的替代产品 按订单生产 MTO 按库存计划 MTS 计划示例 前言 制造企业寻求提高设备利用率 缩短制造周期 寻求降低成本和利润最大化 可以使用许多生产策略 在理想的
  • 蒙特卡洛方法生成随机数_随机股票生成器—财务方面的蒙特卡洛模拟

    蒙特卡洛方法生成随机数 金融 机器学习 Finance Machine Learning In this article I will focus on how to create a procedural stock from nowhe
  • day03 756 蛇形矩阵(偏移量技巧)

    756 蛇形矩阵 输入两个整数n和m 输出一个n行m列的矩阵 将数字 1到 n m按照回字蛇形填充至矩阵中 具体矩阵形式可参考样例 输入格式 输入共一行 包含两个整数n和m 输出格式 输出满足要求的矩阵 矩阵占n行 每行包含m个空格隔开的整
  • Python lxml库的安装和使用

    lxml 是 Python 的第三方解析库 完全使用 Python 语言编写 它对 Xpath 表达式提供了良好的支持 因此能够了高效地解析 HTML XML 文档 本节讲解如何通过 lxml 库解析 HTML 文档 安装lxml库 lxm
  • spring mvc中post、get方法获取参数的几种方式

    get与post两种方式的区别 对于本次主题而言 最显著的区别就是get请求方式参数是在url后 而post请求方式的参数是在request body中 因此两者获取参数的方式也大不一样 Getter Setter AllArgsConst
  • Cocos2d-x学习笔记(二) 永远的HelloWorld

    HelloCpp是Cocos2d x自带的一个工程 它演示了Cocos2d x最基本的使用方法和流程 先看一下它的基本构成 win32目录中包含了对应平台的代码 而Classes目录中包含了我们自己的实现代码 编译运行的结果如下图 main