Cocos2d-x学习笔记(二) 永远的HelloWorld
2014-03-20 15:12
501 查看
HelloCpp是Cocos2d-x自带的一个工程,它演示了Cocos2d-x最基本的使用方法和流程。先看一下它的基本构成
![](http://images.cnitblog.com/i/302573/201403/191332462569412.png)
win32目录中包含了对应平台的代码,而Classes目录中包含了我们自己的实现代码。编译运行的结果如下图
![](http://images.cnitblog.com/i/302573/201403/191334113026764.png)
main函数变形记
看到main命名的文件就会想到著名的main函数一定在这个文件里面,那么就让我们先看看这个文件吧。main.h里面主要是include了各种各样的头文件,main.cpp是我们真正需要关注的
1.1 APIENTRY
首先出现的不明物体是APIENTRY。我们先看看它的定义
APIENTRY是WINAPI的一个替身,而WINAPI是微软定义的宏,实际就是__stdcall
众所周知,__stdcall声明了函数从右至左将参数压栈并由被调用者清理堆栈的调用约定。
1.2 _tWinMain
如果我说_tWinMain是程序的入口函数,你会不会发飙:我去,入口函数不应该是main吗?是的!对于常规的C/C++程序而言main是入口函数的归宿,但在Windows程序中WinMain才是入口函数。
好吧!但为什么这里对的入口函数是_tWinMain而不是WinMain呢?我们先来看看_tWinMain的真正定义
为了支持UNICODE,C运行库对WinMain区分了UNICODE版和ANSI版。对UNICODE版的程序,C运行库将调用wWinMain;而对于ANSI版的应用,则调用WinMain。至于WinMain更深入的知识请阅读《Windows程序设计》,这里只需要知道它是Windows程序的入口函数即可。
1.3 UNREFERENCED_PARAMETER
进入_tWinMain函数后就是两句很神奇的语句
要理解这句话首先是要搞清楚UNREFERENCED_PARAMETER是神马玩意儿。如果我告诉你它神马都不是,你信吗?
承认吧骚年,它真的神马都不是啊~(你逗我玩儿呐?)
AppDelegate的前世今生
_tWinMain函数的真正主体是从AppDelegate app开始的,所以我们就首先从AppDelegate说起。我们先看看AppDelegate的宗族关系
![](http://images.cnitblog.com/i/302573/201403/191355281933699.png)
要把AppDelegate弄清楚搞明白,我们还得从源头开始。
2.1 CCApplicationProtocol
作为Cocos2d-x Application的源头,CCApplicationProtocal定义如下
可以看到,CCApplicationProtocol是一个抽象类,它定义并导出作为DLL的接口。这其中有一个陌生CC_DLL,它定义了在DLL中的符号是导出还是导入
整个CCApplicationProtocol除了析构函数以外的其他所有函数都是纯虚函数,这也就是它为什么叫Protocol的原因。每个函数的含义和作用在注释里有简要的说明,但具体的实现何其作用需要进一步才能理解。
2.2 CCApplication
作为对Cocos2d-x Application的抽象,CCApplication所扮演的角色是非常重要的。它的定义如下
虽然CCApplication提供了public的构造函数,但我们却不能直接实例化CCApplication的,因为它没有实现CCApplicationProtocol定义的所有接口函数(它还是一个抽象类)。
就Hello World这个示例而言,我们需要关注的并不多:构造函数、sharedApplication函数和run函数,我们会进一步全面剖析这些函数。
2.2.1 构造函数
CCApplication的构造函数所完成的工作就是对成员变量的初始化
m_hInstance保存了当前模块的句柄,而sm_pSharedApplicaton则保存了当前对象的指针。值得注意的是,sm_pSharedApplication是一个static的CCApplication对象指针。
2.2.2 Application是唯一的
我们再把_tWinMain函数请出来仔细看看(这里去掉了几句无用的代码)
是否对AppDelegate app这句感到疑惑:在定义了app以后却从未使用过它。那么我们是不是可以理解为这句是多余的呢?好吧!我们把它注释掉,结果程序崩溃了!崩溃了!!溃了!!!
![](http://images.cnitblog.com/i/302573/201403/191422506152164.png)
这是由sm_pSharedApplication引发的断言异常。sm_pSharedApplication是CCApplication类的一个protected的static成员,虽然在构造函数中将它赋值为this,但它的初始化却是0(空指针)
同时CCApplication提供一个public的static函数来访问它
若果你熟悉设计模式就能一眼看出来这个实现实际上是单例模式,它保证了CCApplication只有一个实例。
看到这里,你可能会惊呼:一定是_tWinMain函数的最后一句 return CCApplication::sharedApplication()->run() 引入了错误。哎,事实总是残酷的!实际上异常早在这之前就发生了。我们在CCApplication::sharedApplication函数中下断点,F5运行程序,从CallStack可以看到异常在CCEGLView::WindowProc函数中就发生了
![](http://images.cnitblog.com/i/302573/201403/191447183491431.png)
进入CCEGLView::WindowProc函数,很快我们就能发现引发异常的代码段
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程序的框架大体如下
消息处理函数的结构如下
对比一下HelloWorld的_tWinMain函数
首先,我们看一下CCEGLView::sharedOpenGLView函数的实现细节
这是单例模式的一种变形,通过CCEGLView::sharedOpenGLView函数始终获取同一个CCEGLView对象的指针。同时它也通过new操作符调用CCEGLView的构造函数实例化了一个CCEGLView对象,而CCEGLView的构造函数只不过是完成了成员变量的初始化。可见,“注册窗口类并同时注册消息处理函数”并非通过CCEGLView::sharedOpenGLView函数完成的。
接下来,我们分析CCEGLView::setFrameSize函数。其实现如下
哈哈,居然有一个CCEGLView::Create函数
在CCEGLView::Create函数中完成了注册窗口类和注册消息处理函数_WindowProc,并且完成了窗口的创建。作为Windows程序的核心函数,我们有必要看看_WindowProc看看
_WindowProc函数是一个全局函数,当满足一定条件时就将消息转给s_pMainWindow->WindowProc函数处理。s_pMainWindow是一个全局变量,在CCEGLView::Create中赋值为this指针。那么,我们就得进入CCEGLView::WindowProc看看
如果我们抛开具体的消息及其处理过程,CCEGLView::WindowProc函数可以简化为
当然,如果不满足指定的条件就会使用Windows默认的DefWindowProc方法处理消息。
最后,就剩下“循环获取消息”这部分了。这个时候你不是想起了_tWinMain函数的最后一句代码了吗?让我们再看他一眼
锁定目标,我们进入CCApplication::run函数探究一番,毕竟这个函数也是我们之前列出的重要函数之一。
2.2.4 run出来的消息
CCApplication::run函数的实现如下
注意了,这里面居然有一个循环!而且是条件为“真”的循环!我们先把函数简化后再来分析
这下整个CCApplication::run函数就清晰了,总体来说主要完成了两个功能:
1)显示窗口
2)进入消息循环
到此,典型的Windows程序框架就完整了!如果你还是晕的,请从头再看一遍吧。
2.3 AppDelegate
作为对HelloWorld应用程序的抽象,AppDelegate从CCApplication派生而来,重载了纯虚函数
在AppDelegate类中最主要是实现了applicationDidFinishLaunching函数。在程序启动后,执行CCApplication::run函数的过程中就会调用这个函数
这个函数主要是完成CCDirector类和CCScene类对象的初始化,设置资源路径、分辨率大小和帧率(FPS:Frames Per Second);最后通过CCDirector::runWithScene函数开始场景。
对于另外两个函数,他们的实现就相对简单的多(至少代码量就少很多):把所有事情都交给CCDirector去完成
如注释所陈述的,applicationDidEnterBackground函数会在程序进入“非活跃”状态(即失去窗口焦点)时被调用,而applicationWillEnterForeground函数会在程序进入“活跃”状态(即获得窗口焦点)时被调用(可以自己在这个函数里面下断点看看具体执行的流程)。
舞台需要场景
演员站在舞台上,却表演于场景中
—— by 我挂科了
赋词一句略显文艺范儿,求勿喷!言归正传,首先我们来看看HelloWorld的继承关系
![](http://images.cnitblog.com/i/302573/201403/201138545847207.png)
HelloWorld从CCLayer继承,而CCLayer又是一个非常复杂的(至少它的father太多了)。你一定觉得HelloWorld很复杂吧,其实它没有传说中那么复杂
多么单纯的类啊!你也许会说:CREATE_FUNC是神马东东?它一点都不单纯啊~
3.1、CREATE_FUNC
其实HelloWorld还是很单纯的,因为CREATE_FUNC定义很简单
CREATE_FUNC是一个宏,它定义了一个名为create的static函数,该函数完成下面几个事情:
1)创建__TYPE__类型的对象指针
2)如果创建成功,则调用该对象的init函数
a)如果init函数执行成功,则调用该对象的autorelease函数并返回该对象指针
b)如果init函数执行失败,则释放该对象并返回NULL
将这个宏在HelloWorld类中展开,HelloWorld就露出了它的真面目了
我比较奇怪的是,为什么注释上写的是“static node()”而不是“static create()”呢?隐约听到有人在喊:这一定是笔误!好吧,不要在意这些细节。
3.2 梦回AppDelegate
还记得AppDelegate::applicationDidFinishLauching函数吗?它的实现中有这么一句
这是我们的HelloWorld第一次在程序中被使用,那我们就从HelloWorld::scene函数入手吧
这个函数的实现完成了3个事情:
1)创建了一个空的CCScene对象scene
2)通过上面分析过的HelloWorld::create函数创建了一个HelloWorld对象layer,并将layer作为scene的一个子节点添加到scene中
3)将scene返回
3.3 初始化
在前面我们已经展示了HelloWorld::create函数,它创建了HelloWorld对象后会调用该对象的init函数来初始化对象。HelloWorld::init函数实现如下
不要看这个函数很长,实际上主要完成了4个事情
1)调用父类的初始化函数(CCLayer::init())完成对继承自父类成员的初始化
2)创建一个菜单(CCMenu)并在其中添加一个菜单项(CCMenuItem),将菜单显示在HelloWorld中。点击这个菜单项可以关闭程序
3)创建一个标签(CCLabel),让它在HelloWorld中显示“HelloWorld”字串
4)创建一个精灵(CCSprite),让它在HelloWorld中显示图片“HelloWorld.png”
HelloWorld::init函数中所创建的都是我们在运行起来的界面中所能看到的东西,这其中涉及到一些类将在后面学习,这里不深究。我比较好奇的是菜单项所实现的功能:点击后关闭程序(不信你亲自点一下试试)。这是怎么实现的呢?仔细分析一下菜单项的创建过程
最后一个参数形式很特别啊~ 我们先看看menu_selector是神马东西
又是一个宏!它所完成的事情是将_SELECTOR取址并强制转换为SEL_MenuHandler类型。那么SEL_MenuHandler又是什么类型呢?
它是一个函数指针,这类函数有一个CCObject*类型的参数。此时我们可以将创建菜单项的代码展开,看看其真实的原貌
HelloWorld::menuCloseCallback函数以回调函数的方式传入菜单项,在点击菜单项时被触发。也就是说实现关闭程序功能的是HelloWorld::menuCloseCallback函数
最终,由CCDirector::end函数完成程序的关闭(在IOS中还需要调用exit函数)。
参考文献
Cocos2d-x 高级开发教程:制作自己的捕鱼达人
欢迎转载,但请保留原文出处:/article/5359466.html
![](http://images.cnitblog.com/i/302573/201403/191332462569412.png)
win32目录中包含了对应平台的代码,而Classes目录中包含了我们自己的实现代码。编译运行的结果如下图
![](http://images.cnitblog.com/i/302573/201403/191334113026764.png)
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的宗族关系
![](http://images.cnitblog.com/i/302573/201403/191355281933699.png)
要把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以后却从未使用过它。那么我们是不是可以理解为这句是多余的呢?好吧!我们把它注释掉,结果程序崩溃了!崩溃了!!溃了!!!
![](http://images.cnitblog.com/i/302573/201403/191422506152164.png)
这是由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函数中就发生了
![](http://images.cnitblog.com/i/302573/201403/191447183491431.png)
进入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的继承关系
![](http://images.cnitblog.com/i/302573/201403/201138545847207.png)
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
相关文章推荐
- Cocos2d-x学习笔记(二) 永远的HelloWorld
- Cocos2d-x学习笔记(二) 永远的HelloWorld
- cocos2d-x学习笔记之cocos2d-x的安装与helloworld测试
- cocos2d-x 2.1.4学习笔记之HelloWorld分析
- Cocos2d-x 学习笔记一 HelloWorld
- cocos2d-x学习笔记(一)--HelloWorld
- 我的Cocos2d-x学习笔记(三)游戏逻辑架构与HelloWorld分析
- 我的Cocos2d-x学习笔记(三)游戏逻辑架构与HelloWorld分析
- [转载]cocos2d-x学习笔记3:更改HelloWorld,建立自己的小项目
- cocos2d-x android开发学习笔记(一) 开发环境的配置与HelloWorld
- Cocos2d-x学习笔记(一)HelloWorld
- Selenium 学习笔记(Java版)(一)永远的HelloWorld
- 【cocos2d-x 3.x 学习笔记】 HelloWorld —— 小程序大道理
- [Cocos2d-x for WP8学习笔记] HelloWorld
- Cocos2d-x学习笔记(二)------HelloWorld
- 【cocos2d-x 3.x 学习笔记】 HelloWorld —— 小程序大道理
- Cocos2d-x学习笔记-- 深入剖析HelloWorld
- [Cocos2d-x for WP8学习笔记] HelloWorld结构分析
- Cocos2d-x学习笔记(一)HelloWorld
- cocos2d-x NDK7r eclipse visual2010 HELLOWORLD 学习笔记