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才是入口函数。
1 #ifdef _UNICODE 2 3 #define _tWinMain wWinMain 4 5 #else 6 7 #define _tWinMain WinMain 8 9 #endif
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 };
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 }
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 高级开发教程:制作自己的捕鱼达人
欢迎转载,但请保留原文出处:http://www.cnblogs.com/xieheng/p/3611588.html