您的位置:首页 > 移动开发 > Cocos引擎

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

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



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



main函数变形记

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

int APIENTRY _tWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR    lpCmdLine,
int       nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);

// create the application instance
AppDelegate app;
CCEGLView* eglView = CCEGLView::sharedOpenGLView();
eglView->setFrameSize(960, 640 );
return CCApplication::sharedApplication()->run();
}


1.1 APIENTRY

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

#define APIENTRY    WINAPI


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

#define WINAPI      __stdcall


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

1.2 _tWinMain

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

好吧!但为什么这里对的入口函数是_tWinMain而不是WinMain呢?我们先来看看_tWinMain的真正定义

#ifdef _UNICODE

#define _tWinMain wWinMain

#else

#define _tWinMain WinMain

#endif


为了支持UNICODE,C运行库对WinMain区分了UNICODE版和ANSI版。对UNICODE版的程序,C运行库将调用wWinMain;而对于ANSI版的应用,则调用WinMain。至于WinMain更深入的知识请阅读《Windows程序设计》,这里只需要知道它是Windows程序的入口函数即可。

1.3 UNREFERENCED_PARAMETER

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

UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);


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

#define UNREFERENCED_PARAMETER(P)          (P)


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

AppDelegate的前世今生

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



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

2.1 CCApplicationProtocol

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

class CC_DLL CCApplicationProtocol
{
public:

virtual ~CCApplicationProtocol() {}

/**
@brief    Implement CCDirector and CCScene init code here.
@return true    Initialize success, app continue.
@return false   Initialize failed, app terminate.
*/
virtual bool applicationDidFinishLaunching() = 0;

/**
@brief  The function be called when the application enter background
@param  the pointer of the application
*/
virtual void applicationDidEnterBackground() = 0;

/**
@brief  The function be called when the application enter foreground
@param  the pointer of the application
*/
virtual void applicationWillEnterForeground() = 0;

/**
@brief    Callback by CCDirector for limit FPS.
@interval       The time, expressed in seconds, between current frame and next.
*/
virtual void setAnimationInterval(double interval) = 0;

/**
@brief Get current language config
@return Current language config
*/
virtual ccLanguageType getCurrentLanguage() = 0;

/**
@brief Get target platform
*/
virtual TargetPlatform getTargetPlatform() = 0;
};


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

#if defined(_USRDLL)
#define CC_DLL     __declspec(dllexport)
#else         /* use a DLL library */
#define CC_DLL     __declspec(dllimport)
#endif


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

2.2 CCApplication

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

class CC_DLL CCApplication : public CCApplicationProtocol
{
public:
CCApplication();
virtual ~CCApplication();

/**
@brief    Run the message loop.
*/
int run();

/**
@brief    Get current applicaiton instance.
@return Current application instance pointer.
*/
static CCApplication* sharedApplication();

/* override functions */
virtual void setAnimationInterval(double interval);
virtual ccLanguageType getCurrentLanguage();

/**
@brief Get target platform
*/
virtual TargetPlatform getTargetPlatform();

/* set the Resource root path */
void setResourceRootPath(const std::string& rootResDir);

/* get the Resource root path */
const std::string& getResourceRootPath(void)
{
return m_resourceRootPath;
}

void setStartupScriptFilename(const std::string& startupScriptFile);

const std::string& getStartupScriptFilename(void)
{
return m_startupScriptFilename;
}

protected:
HINSTANCE           m_hInstance;
HACCEL              m_hAccelTable;
LARGE_INTEGER       m_nAnimationInterval;
std::string         m_resourceRootPath;
std::string         m_startupScriptFilename;

static CCApplication * sm_pSharedApplication;
};


虽然CCApplication提供了public的构造函数,但我们却不能直接实例化CCApplication的,因为它没有实现CCApplicationProtocol定义的所有接口函数(它还是一个抽象类)。
就Hello World这个示例而言,我们需要关注的并不多:构造函数、sharedApplication函数和run函数,我们会进一步全面剖析这些函数。

2.2.1 构造函数

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

CCApplication::CCApplication()
: m_hInstance(NULL)
, m_hAccelTable(NULL)
{
m_hInstance    = GetModuleHandle(NULL);
m_nAnimationInterval.QuadPart = 0;
CC_ASSERT(! sm_pSharedApplication);
sm_pSharedApplication = this;
}


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(空指针)

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


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

CCApplication* CCApplication::sharedApplication()
{
CC_ASSERT(sm_pSharedApplication);
return sm_pSharedApplication;
}


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

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



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

case WM_SIZE:
switch (wParam)
{
case SIZE_RESTORED:
CCApplication::sharedApplication()->applicationWillEnterForeground();
break;
case SIZE_MINIMIZED:
CCApplication::sharedApplication()->applicationDidEnterBackground();
break;
}
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程序的框架大体如下

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


消息处理函数的结构如下

LRESULT CALLBACK WindowProc(HWND   hwnd,
UINT   uMsg,
WPARAM wParam,
LPARAM lParam)
{
switch (uMsg)
{
处理各种消息
}
}


对比一下HelloWorld的_tWinMain函数

int APIENTRY _tWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR    lpCmdLine,
int       nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);

// create the application instance
AppDelegate app;

// 以下代码对应Windows程序设计的一般框架
CCEGLView* eglView = CCEGLView::sharedOpenGLView();
eglView->setFrameSize(960, 640 );
return CCApplication::sharedApplication()->run();
}


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

CCEGLView* CCEGLView::sharedOpenGLView()
{
static CCEGLView* s_pEglView = NULL;
if (s_pEglView == NULL)
{
s_pEglView = new CCEGLView();
}
return s_pEglView;
}


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

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

void CCEGLView::setFrameSize(float width, float height)
{
Create((LPCTSTR)m_szViewName, (int)width, (int)height);
CCEGLViewProtocol::setFrameSize(width, height);

resize(width, height); // adjust window size for menubar
centerWindow();
}


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

bool CCEGLView::Create(LPCTSTR pTitle, int w, int h)
{
bool bRet = false;
do
{
CC_BREAK_IF(m_hWnd);
// 创建窗口类
HINSTANCE hInstance = GetModuleHandle( NULL );
WNDCLASS  wc;        // Windows Class Structure

// Redraw On Size, And Own DC For Window.
wc.style          = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
wc.lpfnWndProc    = _WindowProc;                    // WndProc Handles Messages
wc.cbClsExtra     = 0;                              // No Extra Window Data
wc.cbWndExtra     = 0;                              // No Extra Window Data
wc.hInstance      = hInstance;                      // Set The Instance
wc.hIcon          = LoadIcon( NULL, IDI_WINLOGO );  // Load The Default Icon
wc.hCursor        = LoadCursor( NULL, IDC_ARROW );  // Load The Arrow Pointer
wc.hbrBackground  = NULL;                           // No Background Required For GL
wc.lpszMenuName   = m_menu;                         //
wc.lpszClassName  = kWindowClassName;               // Set The Class Name
// 注册窗口类
CC_BREAK_IF(! RegisterClass(&wc) && 1410 != GetLastError());

// center window position
RECT rcDesktop;
GetWindowRect(GetDesktopWindow(), &rcDesktop);

WCHAR wszBuf[50] = {0};
MultiByteToWideChar(CP_UTF8, 0, m_szViewName, -1, wszBuf, sizeof(wszBuf));

// 创建窗口(create window)
m_hWnd = CreateWindowEx(
WS_EX_APPWINDOW | WS_EX_WINDOWEDGE,                  // Extended Style For The Window
kWindowClassName,                                    // Class Name
wszBuf,                                              // Window Title
WS_CAPTION | WS_POPUPWINDOW | WS_MINIMIZEBOX,        // Defined Window Style
0, 0,                                                // Window Position
0,                                                   // Window Width
0,                                                   // Window Height
NULL,                                                // No Parent Window
NULL,                                                // No Menu
hInstance,                                           // Instance
NULL );

CC_BREAK_IF(! m_hWnd);

resize(w, h);

bRet = initGL();
CC_BREAK_IF(!bRet);

s_pMainWindow = this;
bRet = true;
} while (0);

return bRet;
}


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

static CCEGLView* s_pMainWindow = NULL;

static LRESULT CALLBACK _WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
if (s_pMainWindow && s_pMainWindow->getHWnd() == hWnd)
{
return s_pMainWindow->WindowProc(uMsg, wParam, lParam);
}
else
{
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
}


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

LRESULT CCEGLView::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
BOOL bProcessed = FALSE;

switch (message)
{
case WM_LBUTTONDOWN:
if (m_pDelegate && MK_LBUTTON == wParam)
{
POINT point = {(short)LOWORD(lParam), (short)HIWORD(lParam)};
CCPoint pt(point.x/CC_CONTENT_SCALE_FACTOR(), point.y/CC_CONTENT_SCALE_FACTOR());
CCPoint tmp = ccp(pt.x, m_obScreenSize.height - pt.y);
if (m_obViewPortRect.equals(CCRectZero) || m_obViewPortRect.containsPoint(tmp))
{
m_bCaptured = true;
SetCapture(m_hWnd);
int id = 0;
pt.x *= m_windowTouchScaleX;
pt.y *= m_windowTouchScaleY;
handleTouchesBegin(1, &id, &pt.x, &pt.y);
}
}
break;

case WM_MOUSEMOVE:
if (MK_LBUTTON == wParam && m_bCaptured)
{
POINT point = {(short)LOWORD(lParam), (short)HIWORD(lParam)};
CCPoint pt(point.x/CC_CONTENT_SCALE_FACTOR(), point.y/CC_CONTENT_SCALE_FACTOR());
int id = 0;
pt.x *= m_windowTouchScaleX;
pt.y *= m_windowTouchScaleY;
handleTouchesMove(1, &id, &pt.x, &pt.y);
}
break;

case WM_LBUTTONUP:
if (m_bCaptured)
{
POINT point = {(short)LOWORD(lParam), (short)HIWORD(lParam)};
CCPoint pt(point.x/CC_CONTENT_SCALE_FACTOR(), point.y/CC_CONTENT_SCALE_FACTOR());
int id = 0;
pt.x *= m_windowTouchScaleX;
pt.y *= m_windowTouchScaleY;
handleTouchesEnd(1, &id, &pt.x, &pt.y);

ReleaseCapture();
m_bCaptured = false;
}
break;
case WM_SIZE:
switch (wParam)
{
case SIZE_RESTORED:
CCApplication::sharedApplication()->applicationWillEnterForeground();
break;
case SIZE_MINIMIZED:
CCApplication::sharedApplication()->applicationDidEnterBackground();
break;
}
break;
case WM_KEYDOWN:
if (wParam == VK_F1 || wParam == VK_F2)
{
CCDirector* pDirector = CCDirector::sharedDirector();
if (GetKeyState(VK_LSHIFT) < 0 ||  GetKeyState(VK_RSHIFT) < 0 || GetKeyState(VK_SHIFT) < 0)
pDirector->getKeypadDispatcher()->dispatchKeypadMSG(wParam == VK_F1 ? kTypeBackClicked : kTypeMenuClicked);
}
if ( m_lpfnAccelerometerKeyHook!=NULL )
{
(*m_lpfnAccelerometerKeyHook)( message,wParam,lParam );
}
break;
case WM_KEYUP:
if ( m_lpfnAccelerometerKeyHook!=NULL )
{
(*m_lpfnAccelerometerKeyHook)( message,wParam,lParam );
}
break;
case WM_CHAR:
{
if (wParam < 0x20)
{
if (VK_BACK == wParam)
{
CCIMEDispatcher::sharedDispatcher()->dispatchDeleteBackward();
}
else if (VK_RETURN == wParam)
{
CCIMEDispatcher::sharedDispatcher()->dispatchInsertText("\n", 1);
}
else if (VK_TAB == wParam)
{
// tab input
}
else if (VK_ESCAPE == wParam)
{
// ESC input
//CCDirector::sharedDirector()->end();
}
}
else if (wParam < 128)
{
// ascii char
CCIMEDispatcher::sharedDispatcher()->dispatchInsertText((const char *)&wParam, 1);
}
else
{
char szUtf8[8] = {0};
int nLen = WideCharToMultiByte(CP_UTF8, 0, (LPCWSTR)&wParam, 1, szUtf8, sizeof(szUtf8), NULL, NULL);
CCIMEDispatcher::sharedDispatcher()->dispatchInsertText(szUtf8, nLen);
}
if ( m_lpfnAccelerometerKeyHook!=NULL )
{
(*m_lpfnAccelerometerKeyHook)( message,wParam,lParam );
}
}
break;
case WM_PAINT:
PAINTSTRUCT ps;
BeginPaint(m_hWnd, &ps);
EndPaint(m_hWnd, &ps);
break;

case WM_CLOSE:
CCDirector::sharedDirector()->end();
break;

case WM_DESTROY:
destroyGL();
PostQuitMessage(0);
break;

default:
if (m_wndproc)
{

m_wndproc(message, wParam, lParam, &bProcessed);
if (bProcessed) break;
}
return DefWindowProc(m_hWnd, message, wParam, lParam);
}

if (m_wndproc && !bProcessed)
{
m_wndproc(message, wParam, lParam, &bProcessed);
}
return 0;
}


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

LRESULT CCEGLView::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
// 处理各种消息
}
// 调用自定义的处理函数
if (m_wndproc && !bProcessed)
{
m_wndproc(message, wParam, lParam, &bProcessed);
}

return 0;
}


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

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

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


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

2.2.4 run出来的消息

CCApplication::run函数的实现如下

int CCApplication::run()
{
PVRFrameEnableControlWindow(false);

// Main message loop:
MSG msg;
LARGE_INTEGER nFreq;
LARGE_INTEGER nLast;
LARGE_INTEGER nNow;

QueryPerformanceFrequency(&nFreq);
QueryPerformanceCounter(&nLast);

// Initialize instance and cocos2d.
if (!applicationDidFinishLaunching())
{
return 0;
}

CCEGLView* pMainWnd = CCEGLView::sharedOpenGLView();
pMainWnd->centerWindow();
ShowWindow(pMainWnd->getHWnd(), SW_SHOW);

while (1)
{
if (! PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
// Get current time tick.
QueryPerformanceCounter(&nNow);

// If it's the time to draw next frame, draw it, else sleep a while.
if (nNow.QuadPart - nLast.QuadPart > m_nAnimationInterval.QuadPart)
{
nLast.QuadPart = nNow.QuadPart;
CCDirector::sharedDirector()->mainLoop();
}
else
{
Sleep(0);
}
continue;
}

if (WM_QUIT == msg.message)
{
// Quit message loop.
break;
}

// Deal with windows message.
if (! m_hAccelTable || ! TranslateAccelerator(msg.hwnd, m_hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}

return (int) msg.wParam;
}


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

int CCApplication::run()
{
// 显示窗口
CCEGLView* pMainWnd = CCEGLView::sharedOpenGLView();
pMainWnd->centerWindow();
ShowWindow(pMainWnd->getHWnd(), SW_SHOW);

while (1)
{
if (! PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
// 处理一些数据
continue;
}

if (WM_QUIT == msg.message)
{
// 退出消息循环(Quit message loop)
break;
}

// 处理快捷键
}

return (int) msg.wParam;
}


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

1)显示窗口

2)进入消息循环

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

2.3 AppDelegate

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

class  AppDelegate : private cocos2d::CCApplication
{
public:
AppDelegate();
virtual ~AppDelegate();

/**
@brief    Implement CCDirector and CCScene init code here.
@return true    Initialize success, app continue.
@return false   Initialize failed, app terminate.
*/
virtual bool applicationDidFinishLaunching();

/**
@brief  The function be called when the application enter background
@param  the pointer of the application
*/
virtual void applicationDidEnterBackground();

/**
@brief  The function be called when the application enter foreground
@param  the pointer of the application
*/
virtual void applicationWillEnterForeground();
};


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

bool AppDelegate::applicationDidFinishLaunching()
{
// initialize director
CCDirector *pDirector = CCDirector::sharedDirector();

pDirector->setOpenGLView(CCEGLView::sharedOpenGLView());

TargetPlatform target = getTargetPlatform();

if (target == kTargetIpad)
{
// ipad
CCFileUtils::sharedFileUtils()->setResourceDirectory("iphonehd");

// don't enable retina because we don't have ipad hd resource
CCEGLView::sharedOpenGLView()->setDesignResolutionSize(960, 640, kResolutionNoBorder);
}
else if (target == kTargetIphone)
{
// iphone

// try to enable retina on device
if (true == CCDirector::sharedDirector()->enableRetinaDisplay(true))
{
// iphone hd
CCFileUtils::sharedFileUtils()->setResourceDirectory("iphonehd");
}
else
{
CCFileUtils::sharedFileUtils()->setResourceDirectory("iphone");
}
}
else
{
// android, windows, blackberry, linux or mac
// use 960*640 resources as design resolution size
CCFileUtils::sharedFileUtils()->setResourceDirectory("iphonehd");
CCEGLView::sharedOpenGLView()->setDesignResolutionSize(960, 640, kResolutionNoBorder);
}

// turn on display FPS
pDirector->setDisplayStats(true);

// set FPS. the default value is 1.0/60 if you don't call this
pDirector->setAnimationInterval(1.0 / 60);

// create a scene. it's an autorelease object
CCScene *pScene = HelloWorld::scene();

// run
pDirector->runWithScene(pScene);

return true;
}


这个函数主要是完成CCDirector类和CCScene类对象的初始化,设置资源路径、分辨率大小和帧率(FPS:Frames Per Second);最后通过CCDirector::runWithScene函数开始场景。
对于另外两个函数,他们的实现就相对简单的多(至少代码量就少很多):把所有事情都交给CCDirector去完成

// This function will be called when the app is inactive. When comes a phone call,it's be invoked too
void AppDelegate::applicationDidEnterBackground()
{
CCDirector::sharedDirector()->stopAnimation();

// if you use SimpleAudioEngine, it must be pause
// SimpleAudioEngine::sharedEngine()->pauseBackgroundMusic();
}

// this function will be called when the app is active again
void AppDelegate::applicationWillEnterForeground()
{
CCDirector::sharedDirector()->startAnimation();

// if you use SimpleAudioEngine, it must resume here
// SimpleAudioEngine::sharedEngine()->resumeBackgroundMusic();
}


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

舞台需要场景

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

—— by 我挂科了

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



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

class HelloWorld : public cocos2d::CCLayer
{
public:
// Here's a difference. Method 'init' in cocos2d-x returns bool, instead of returning 'id' in cocos2d-iphone
virtual bool init();

// there's no 'id' in cpp, so we recommend returning the class instance pointer
static cocos2d::CCScene* scene();

// a selector callback
void menuCloseCallback(CCObject* pSender);

// touch callback
void ccTouchesBegan(cocos2d::CCSet *pTouches, cocos2d::CCEvent *pEvent);

// implement the "static node()" method manually
CREATE_FUNC(HelloWorld);
};


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

3.1、CREATE_FUNC

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

#define CREATE_FUNC(__TYPE__) \
static __TYPE__* create() \
{ \
__TYPE__ *pRet = new __TYPE__(); \
if (pRet && pRet->init()) \
{ \
pRet->autorelease(); \
return pRet; \
} \
else \
{ \
delete pRet; \
pRet = NULL; \
return NULL; \
} \
}


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

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

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

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

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

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

class HelloWorld : public cocos2d::CCLayer
{
public:
// Here's a difference. Method 'init' in cocos2d-x returns bool, instead of returning 'id' in cocos2d-iphone
virtual bool init();

// there's no 'id' in cpp, so we recommend returning the class instance pointer
static cocos2d::CCScene* scene();

// a selector callback
void menuCloseCallback(CCObject* pSender);

// touch callback
void ccTouchesBegan(cocos2d::CCSet *pTouches, cocos2d::CCEvent *pEvent);

// implement the "static node()" method manually
static HelloWorld* create()
{
HelloWorld *pRet = new HelloWorld();
if (pRet && pRet->init())
{
pRet->autorelease();
return pRet;
}
else
{
delete pRet;
pRet = NULL;
return NULL;
}
}
};


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

3.2 梦回AppDelegate

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

bool AppDelegate::applicationDidFinishLaunching()
{
// 其他操作

// create a scene. it's an autorelease object
CCScene *pScene = HelloWorld::scene();
// run
pDirector->runWithScene(pScene);

// 其他操作
}


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

CCScene* HelloWorld::scene()
{
// 'scene' is an autorelease object
CCScene *scene = CCScene::create();

// 'layer' is an autorelease object
HelloWorld *layer = HelloWorld::create();

// add layer as a child to scene
scene->addChild(layer);

// return the scene
return scene;
}


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

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

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

3)将scene返回

3.3 初始化

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

// on "init" you need to initialize your instance
bool HelloWorld::init()
{
//////////////////////////////
// 1. super init first
if ( !CCLayer::init() )
{
return false;
}

CCSize visibleSize = CCDirector::sharedDirector()->getVisibleSize();
CCPoint origin = CCDirector::sharedDirector()->getVisibleOrigin();

/////////////////////////////
// 2. add a menu item with "X" image, which is clicked to quit the program
//    you may modify it.

// add a "close" icon to exit the progress. it's an autorelease object
CCMenuItemImage *pCloseItem = CCMenuItemImage::create(
"CloseNormal.png",
"CloseSelected.png",
this,
menu_selector(HelloWorld::menuCloseCallback));
pCloseItem->setPosition(ccp(origin.x + visibleSize.width - pCloseItem->getContentSize().width/2 ,
origin.y + pCloseItem->getContentSize().height/2));
// create menu, it's an autorelease object
CCMenu* pMenu = CCMenu::create(pCloseItem, NULL);
pMenu->setPosition(CCPointZero);
this->addChild(pMenu, 1);

/////////////////////////////
// 3. add your codes below...

// add a label shows "Hello World"
// create and initialize a label
CCLabelTTF* pLabel = CCLabelTTF::create("Hello World", "Arial", 24);
// position the label on the center of the screen
pLabel->setPosition(ccp(origin.x + visibleSize.width/2,
origin.y + visibleSize.height - pLabel->getContentSize().height));
// add the label as a child to this layer
this->addChild(pLabel, 1);

// add "HelloWorld" splash screen"
CCSprite* pSprite = CCSprite::create("HelloWorld.png");
// position the sprite on the center of the screen
pSprite->setPosition(ccp(visibleSize.width/2 + origin.x, visibleSize.height/2 + origin.y));
// add the sprite as a child to this layer
this->addChild(pSprite, 0);

// enable standard touch
this->setTouchEnabled(true);

return true;
}


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

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

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

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

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

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

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


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

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


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

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


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

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


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

void HelloWorld::menuCloseCallback(CCObject* pSender)
{
CCDirector::sharedDirector()->end();

#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS)
exit(0);
#endif
}


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

参考文献

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

欢迎转载,但请保留原文出处:/article/5359466.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: