了解完Cocos2D-x的基本概念和概念类之后,是不是有一种蠢蠢欲动的冲动,想要探究Cocos2D-x是如何完成这一切的。接着我将通过对Cocos2D-x自代的HelloCpp项目进行分析,初步了解Cocos2D-x游戏的基本框架,揭开Cocos2D-x神秘的面纱。
作为一个Hello World程序,HelloCpp的功能非常简单。打开一个OpenGL窗口,在里面显示了一张Cocos2D-x的log图片,在图片的上面写着"Hello World"。在右下角有一个按钮,用来退出程序。在下角显示了当前的帧率。如下图:
HelloCpp项目提供了android、blackberry、ios、linux、mac和win32的工程,体现了Cocos2D-x的跨平台性。这里我们只针对win32工程。
关于Cocos2D-x版本下载和环境配置非常简单,我这里就不再赘述。如有不明白的地方,可以上网搜索,有很多介绍这方面的文章。
整个HelloCpp程序分为两个部分,这也是Cocos2D-x应用程序开发模板程序的共同结构。如下图:
在Classes目录下,放着程序的主要部分,包括场景(CCScene)、布景(CCLayer)、精灵(CCSprite)等游戏元素相关的代码,而在每个工程的目录下(Win32是proj.win32)放着与平台相关的入口程序(proj.win32里就包含一个main.cpp/h)。
先从入口开始,查看main.cpp代码,如下:
int APIENTRY _tWinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPTSTR lpCmdLine,int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);//这是一个编译宏,用于告诉编译器,忽略未使用变量而产生的告警信息。
UNREFERENCED_PARAMETER(lpCmdLine);AppDelegate app; // 创建一个Cocos2D-x程序实例
CCEGLView* eglView = CCEGLView::sharedOpenGLView();
eglView->setFrameSize(960, 640 ); //处理windows窗体注册和创建
return CCApplication::sharedApplication()->run(); //运行程序实例
}
写过win32程序的人,一定对_tWinMain函数不会感到陌生,Cocos2D-x也是从这里作为入口开始的。这个函数简洁到极致,只有寥寥几行代码,甚至看不到Windows的窗口注册和消息机制。因为Cocos2D-x把很多Windows窗口相关程序代码封装到CCEGLView中,该类被统一放到Cocos2D-x的win32平台中(cocos2d-2.0-x-2.0.2\cocos2dx\platform\win32目录)。这样做的目的,无疑是为了方便跨平台。
一般游戏程序都需要一个主循环,那Cocos2D-x的主循环在哪里呢?它就藏在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;
}
这段代码主要在处理windows消息机制,熟悉windows程序开发的应该很眼熟。我们重点关注CCDirector::sharedDirector()->mainLoop(),这行代码是Cocos2D-x的主循环处理,完成场景的渲染。
下面深入每一个类进行探索,看一看Cocos2D-x的运行机制。
首先要查看的类是AppDelegate类,头文件定义如下:
class AppDelegate : private cocos2d::CCApplication
{
public:
AppDelegate();
virtual ~AppDelegate();virtual bool applicationDidFinishLaunching(); //程序启动时调用
virtual void applicationDidEnterBackground(); //程序被切换到后台处于非激活时调用
virtual void applicationWillEnterForeground();//程序被切换到前台处于激活状态时调用
};
当我最初看到这个AppDelegate时,还以为使用类似C#中的代理机制。但查看头文件定义才知道,它是私有继承于CCApplication。而CCApplication就是一个App,AppDelegate可以理解为MyApp。这里采用私有继承的目的是隐藏接口。这里AppDelegate实现了父类CCApplication的三个虚函数。而这三个虚函数是CCApplication从CCApplicationProtocol接口继承而来,但它并未实现这些接口,所以不能直接创建CCApplication实例。
applicationDidFinishLaunching函数非常关键中,因为它将加载Cocos2D-x的概念类和类之间的关联关系,代码如下:
bool AppDelegate::applicationDidFinishLaunching() {
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
if (pDirector->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;
}
当程序启动后,applicationDidFinishLaunching函数将会被调用,函数内将获得一个导演和创建一个场景,然后导演调用runWithScene函数渲染该场景。
这里只有导演和场景,还少了布景层和精灵。它们在哪里创建的呢?一切奥秘就藏在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 recommand to return the exactly class pointer
static cocos2d::CCScene* scene();
// a selector callback
void menuCloseCallback(CCObject* pSender);// implement the "static node()" method manually
CREATE_FUNC(HelloWorld);
};
可以看到HelloWorld类继承自布景层CCLayer。在它的头文件里定义了四个成员函数。前三个成员函数,很容易理解它们的用途。而最后一个有点奇怪,需要解释一下。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; \
} \
}
这个宏为HelloWorld添加一个静态成员函数create,并返回这个类自身的指针。在创建的时候,自动调用了类的init函数对类进行初始化,并且使用autorelease。(autorelease是Cocos2D-x的内存管理机制,欲详细了解请查看Cocos2D-x内存管理专题)来自动释放对象实例。
还是关注HelloWorld实例的创建,答案就在下面这行代码里。
CCScene *pScene = HelloWorld::scene(); //这里获取一个场景
这行代码明明是获取一个场景,HelloWorld实例又在哪里创建的呀。还是看一看HelloWorld的scene函数的实现,如下:
CCScene* HelloWorld::scene()
{
// 'scene' is an autorelease object
CCScene *scene = CCScene::create(); //这里真正创建一个场景CCScene
// 'layer' is an autorelease object
HelloWorld *layer = HelloWorld::create(); //创建一个布景CCLayer// add layer as a child to scene
scene->addChild(layer);//将布景添加到场景里// return the scene
return scene;//返回创建的场景
}
现在一切都清楚了,原来在scene函数内创建了场景和布景,而且将布景添加到场景中并返回了场景。
这里似乎只有场景和布景,还差什么呢?是精灵。那精灵又是在哪里创建的呢?答案是在HelloWorld的初始化init函数里。还是给出init函数的实现代码,如下:
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));
if (CCApplication::sharedApplication()->getTargetPlatform() == kTargetIphone)
{
pCloseItem->setPosition(ccp(visibleSize.width - 20 + origin.x, 20 + origin.y));
}
else
{
pCloseItem->setPosition(ccp(visibleSize.width - 40 + origin.x, 40 + origin.y));
}// 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(visibleSize.width/2 + origin.x, visibleSize.height - 50 + origin.y));// 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);//将精灵添加到布景中
return true;
}
这段代码有点长,它将在HelloWorld类创建时被调用。它将给HelloWorld添加一个图像菜单(关闭按钮)、一个Label标签以及一个精灵。
HelloWorld还有一个函数menuCloseCallback,这是一个事件响应函数。它用来响应关闭按钮被按下时的事件。它的处理很简单就是关闭程序。
现在整个Cocos2D-x的基本框架讲解完了。它虽然很简单,但是却是Cocos2D-x开发的基础。
刚开始接触Cocos2D-x代码时,里面有一些惯用法和宏定义可能不是很适应,不过慢慢就会习惯了。下面例举HelloWorld中使用的一些惯用法和宏,如下:
ccp(visibleSize.width - 20 + origin.x, 20 + origin.y)//这里ccp是辅助宏,用来创建CCPoint。#define ccp(__X__,__Y__) cocos2d::CCPointMake((float)(__X__), (float)(__Y__))
CCSprite* pSprite = CCSprite::create("HelloWorld.png");//使用create而不是new创建对象实例
CCLayer::init();//使用init初始化一个对象
CCDirector *pDirector = CCDirector::sharedDirector();//shared前缀表示一个类的单例(单例设计模式)
-------------------------------------------------------------------------------------------------------------------
注:本人在本博客的原创文章采用创作共用版权协议(http://creativecommons.org/licenses/by-nc-sa/2.5/cn/), 要求署名、非商业用途和保持一致。要求署名包含注明我的网名及文章来源(我的博客地址:http://www.cnblogs.com/binbingg)。