Cocos2d-x虽然很火,但是相关的学习资料还是很少的。在看完《Cocos2d-x权威指南》的基础内容之后,我跟着学习过“老G的博客”,也看过一些“地球人也阻止不了程序猿们学习Cocos2d-x了”之类的帖子。总感觉介绍和讲解的内容是零零散散的。这非常不利于对于Cocos2d-x引擎的整体理解和掌握。(求大爷们不要灭我,只是个人观点额)
我相信,那些写帖子的同学们、老师们…大神们,都有自己的学习方法,必定不是通过看帖子来学习新东西的。
好了,不废话了。希望和大家一起学习,一起进步。为了给自己鼓劲,在此也为自己附上一句话。
有梦想,并坚持,人生就有希望。
—— 北大校长
——————————————————————-美丽的分割线——————————————————————-
任何一个程序都有一个开始执行的入口,这个入口通常叫做“main”。那么Cocos2d-x在Win32平台下的入口是int APIENTRY _tWinMain:
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->setViewName("TestCpp");
eglView->setFrameSize(480, 320);
return CCApplication::sharedApplication()->run();
}
AppDelegate:
程序源码中的注释:The reason for implement as private inheritance is to hide some interface call by CCDirector。(这句话如何理解,看各位自己的功力了,我就不做坑爹的翻译了^ ^)
包含三个方法:
1、virtual bool applicationDidFinishLaunching();
应用程序相关资源加载完成后执行此方法。可以简单的认为,这个就是程序的初始化函数。
功能:
(1)初始化应用程序中唯一的CCDirector导演。
(2)设置OpenGL的相关参数等。
(3)创建场景,创建布景层,并将布景层放入场景中,作为子节点。
(4)pDirector->runWithScene(pScene); 设置运行的第一个场景。
2、void AppDelegate::applicationDidEnterBackground()
原文中的注释:This function will be called when the app is inactive。表示应用程序当前状态是“不活跃”(来电话)时,这个方法将被执行。
功能:
(1)停止游戏当中的动画。
(2)暂停游戏的背景音乐。
(3)暂停游戏的所有音效。
3、void AppDelegate::applicationWillEnterForeground()
应用程序从后台再次被唤醒时执行此方法。
功能:恢复动画、背景音乐、所有音效。
CCEGLView:
由于Cocos2d-x是基于OpenGL ES图形引擎的,所有在图形绘制方面所遵守的规则都是OpenGL的规则,比如坐标系统。
CCEGLView* eglView = CCEGLView::sharedOpenGLView();
上面的代码用来获得OpenGL的视图。关于图形学的视图这个概念可以在网上搜搜。当然,暂时还没太多必要。就把这理解为“获得应用程序的画面”吧。
那么有了视图这个对象指针后,我们就可以设置这个视图的名称和大小。
CCApplication::sharedApplication()->run();
应用程序相关的参数设置完成后,就可以调用run()方法,进入应用程序的消息循环。
——————————————————————-美丽的分割线——————————————————————-
马上进入正题了,这里非常关键,因此我也会非常小心的分析,免得误人子弟,当然,还有自己~
学习Cocos2d-x的各种特性,莫过于学习TestCpp项目了,这是公开的“秘密”。开始我学习这个TestCpp的时候头昏脑胀的,一点头绪没有,因为程序的结构对于我这样的新人来说太复杂,更别说是用C++编写的程序….可恶的宏啊、可恶的高级特性啊。完全没心思找那些示例代码了,因为和这个程序结构是紧紧相连的啊。
但如论怎么说,这个是学习Cocos2d-x最好的资料了,目前还没发现比这个更好的。
既然这样,那就下定决心搞定它吧~!
OK,先来看看项目的整体文件。
文件列表:main.cpp、main.h、AppDelegate.h/AppDelegate.cpp、tests.h、testResource.h、VisibleRect.h/VisbleRect.cpp
testBasic.h/testBasic.cpp、controller.h/controller.cpp
除了上面的文件,Classes包(文件夹)中还有许多继承于CCLayer、CCScene的类,那些都是示例子场景和示例子布景层。
一一击破:
main.h/main.cpp和AppDelegate.h/AppDelegate.cpp 在前面我们已经搞定了。
tests.h:#include所有Classes包中的头文件、一些字符串、枚举的标记变量等。(C/C++头文件,通过包含的方法,就可以使用头文件内的资源)
testResource.h:图片路径的字符串等。
VisibleRect.h/VisbleRect.cpp:作者封装的一个管理坐标的类,可以直接让显示对象居中、靠左、靠右对齐等。(可以Copy下来,以后自己用)
testBasic.h/testBasic.cpp:TestScene,继承于CCScene场景类,是所有示例子场景的爹。
controller.h/controller.cpp:TestController,继承于CCLayer布景层类,是被应用程序第一个创建的布景层,里面有一个CCMenu菜单(主菜单)。
到此,一切文件我们都熟悉了。现在我们深入的调查一下,这个示例程序到底是如何运行这么多东东的。
CCApplication::sharedApplication()->run()
main.cpp中,上面的代码执行完之后就进入了消息循环,程序就正常运行了。
当程序相关资源加载和设置完成之后就开始执行AppDelegate类中的applicationDidFinishLaunching()方法。
bool AppDelegate::applicationDidFinishLaunching()
{
// As an example, load config file
// XXX: This should be loaded before the Director is initialized,
// XXX: but at this point, the director is already initialized
CCConfiguration::sharedConfiguration()->loadConfigFile("configs/config-example.plist");
// 获得导演实例
CCDirector *pDirector = CCDirector::sharedDirector();
// 设置OpenGL视图
pDirector->setOpenGLView(CCEGLView::sharedOpenGLView());
// 获取屏幕的分辨率(在Win32下,屏幕的大小是自己设置的,在main.cpp中看到eglView->setFrameSize(480, 320);)
CCSize screenSize = CCEGLView::sharedOpenGLView()->getFrameSize();
// 理想的分辨率
CCSize designSize = CCSizeMake(480, 320);
// CCFileUtils文件操作类
CCFileUtils* pFileUtils = CCFileUtils::sharedFileUtils();
// 如果屏幕的高度大于理想的高度,执行if内的代码
if (screenSize.height > 320)
{
CCSize resourceSize = CCSizeMake(960, 640);
std::vector<std::string> searchPaths;
searchPaths.push_back("hd");
pFileUtils->setSearchPaths(searchPaths);
pDirector->setContentScaleFactor(resourceSize.height/designSize.height);
}
// 将视图设置为理想的分辨率。
CCEGLView::sharedOpenGLView()->setDesignResolutionSize(designSize.width, designSize.height, kResolutionNoBorder);
// 创建一个场景和一个布景层
CCScene * pScene = CCScene::create();
CCLayer * pLayer = new TestController();
// 设置后,自动管理对象的释放,不用手动释放。
pLayer->autorelease();
// 将布景层添加到场景中
pScene->addChild(pLayer);
// 运行场景
pDirector->runWithScene(pScene);
return true;
}
分辨率的概念:移动平台设备的分辨率总是不统一的,什么大小的分辨率都有,因此Coco有一个处理办法,那就是使用缩放方法。
第一种解决思路,当设备的分辨率改变了,那么读取不同分辨率的资源。(需要准备各种分辨率的资源)
第二种解决思路,直接缩放图片资源的大小,计算公式:X = 设备分辨率/设计分辨率,然后将图片缩放X倍。(这个比较坑爹)
第三种,思考ing。
在上面代码的if()结构中,是读取大分辨率资源(图片等)。TestCpp项目有两个资源文件夹,一个是images,一个是hd。我们可以找到工程的所在的文件夹,看到只有一部分的图片有两个版本的,而且hd文件夹中的资源,很多都在images里面没有。这样做,可能为了一些示例表现得更好才没有小分辨率的吧。暂时不管它。
pFileUtils->setSearchPaths(searchPaths);
本来程序里精灵加载的图片都是搜索默认Resources文件夹下的images文件夹。当if()结构触发之后,就会执行上面的这行代码,改变搜索路径,转到hd文件夹下搜索资源。
我们也可以看到一个技巧,它这里设置的搜索资源的路径可以是多个,searchPaths是一个vector容器。
CCEGLView::sharedOpenGLView()->setDesignResolutionSize(designSize.width, designSize.height, kResolutionNoBorder);
上面代码将设置应用程序分辨率,第三个参数决定了不同尺寸时画面显示的效果。
kResolutionExactFit:不同尺寸时,画面被拉伸。
kResolutionNoBorder:不同尺寸时,没有黑边,画面被裁减。
kResolutionShowAll:不同尺寸时,有黑边,如果设计的分辨率和屏幕分辨率不同,将显示黑边。
(三种效果的说明是从coco源码定义中的注释找到的,我只是把英文通俗的翻译过来了,大致也能想象到这三种都是什么效果,等实际开发的时候再确定)
CCScene * pScene = CCScene::create();
CCLayer * pLayer = new TestController();
细心的童鞋会发现在TestController布景层中有加入了主菜单,和右下角的关闭按钮。
pLayer->autorelease();
我们知道,C++中都是成套使用new/delete的,但是在coco中,有内存自动管理的机制,非常方便,不易出现内存泄露。
在网上一些关于coco的内存管理分析,大神们好像都建议使用这个,除非有特殊情况。
不用时,使用release或autorelease,将来要用时,使用retain。
pDirector->runWithScene(pScene);
Coco 应用程序运行成功!

那么童鞋们,最后我们震一下~看看大致的结构图。
——————————————————————-美丽的分割线——————————————————————-
图x.x TestCpp中ActionTest示例结构图
如果童鞋比较细心的话,可以发现我这个图其实是UML图。
为什么是UML图?UML能很好的将程序结构表现出来。游戏编程除了要学习语言基础、游戏引擎,最重要的还是编码能力和游戏逻辑的思维。
如有哪里绘制错误了,希望童鞋们能马上帮我指出来。好了,废话不讲了,简单分析下这个结构。
TestController被创建时,构造函数中会创建一个CCMenu菜单,用来选择示例场景。在菜单的回调函数(菜单可以指定回调函数,通俗点讲,就是响应函数)中会确定调用哪一个示例场景并显示出来。代码如下:
TestController::TestController()
: m_tBeginPos(CCPointZero)
{
// 右下角的关闭按钮
CCMenuItemImage *pCloseItem = CCMenuItemImage::create(s_pPathClose, s_pPathClose, this, menu_selector(TestController::closeCallback) );
CCMenu* pMenu =CCMenu::create(pCloseItem, NULL);
// 设置关闭按钮的位置
pMenu->setPosition( CCPointZero );
pCloseItem->setPosition(ccp( VisibleRect::right().x-30, VisibleRect::center().y));
// 创建主菜单,添加菜单项
m_pItemMenu = CCMenu::create();
for (int i = 0; i < TESTS_COUNT; ++i)
{
CCLabelTTF* label = CCLabelTTF::create(g_aTestNames[i].c_str(), "Arial", 24);
CCMenuItemLabel* pMenuItem = CCMenuItemLabel::create(label, this, menu_selector(TestController::menuCallback));
m_pItemMenu->addChild(pMenuItem, i + 10000);
pMenuItem->setPosition( ccp( VisibleRect::center().x, (VisibleRect::top().y - (i + 1) * LINE_SPACE) ));
}
// 设置菜单的固定大小,无论视图如何缩放。
m_pItemMenu->setContentSize(CCSizeMake(VisibleRect::getVisibleRect().size.width, (TESTS_COUNT + 1) * (LINE_SPACE)));
m_pItemMenu->setPosition(s_tCurPos);
addChild(m_pItemMenu);
// 允许触摸事件
setTouchEnabled(true);
addChild(pMenu, 1);
}
void TestController::menuCallback(CCObject * pSender)
{
// get the userdata, it's the index of the menu item clicked
CCMenuItem* pMenuItem = (CCMenuItem *)(pSender);
int nIdx = pMenuItem->getZOrder() - 10000;
// create the test scene and run it
TestScene* pScene = CreateTestScene(nIdx);
if (pScene)
{
pScene->runThisTest();
pScene->release();
}
}
当在主场景(TestController)中选择一个菜单项时,就用上面的调用回调函数。pSender指向的就是被选择的菜单项。
接着获取菜单项的z值,通过z值确定创建哪一个示例场景。(z值就是对象在容器对象里的前后的关系)可为什么是获取z值后减去了10000?
我们找到创建菜单的地方(TestController的构造函数),有一个 m_pItemMenu->addChild(pMenuItem, i + 10000); 原来并不是从0开始的,而是从10000开始的。
接着调用TestScene类中的静态方法 CreateTestScene(int nIdx),创建新场景。代码如下:
static TestScene* CreateTestScene(int nIdx)
{
CCDirector::sharedDirector()->purgeCachedData();
TestScene* pScene = NULL;
switch (nIdx)
{
case TEST_ACTIONS:
pScene = new ActionsTestScene(); break;
case TEST_TRANSITIONS:
pScene = new TransitionsTestScene(); break;
case TEST_PROGRESS_ACTIONS:
pScene = new ProgressActionsTestScene(); break;
case TEST_EFFECTS:
pScene = new EffectTestScene(); break;
case TEST_CLICK_AND_MOVE:
pScene = new ClickAndMoveTestScene(); break;
......
return pScene;
}
接着调用场景的runThisTest()方法替换原有的场景,并运行。在Action示例中的代码如下:(其他示例场景也类似)
void ActionsTestScene::runThisTest()
{
sceneIdx = -1;
addChild(nextAction());
// 通过导演的replaceScene方法改变场景
CCDirector::sharedDirector()->replaceScene(this);
}
回到UML图。每个示例子场景都继承于一个基类,都有共同的runThisTest()方法,但内部实现不同。(TestScene其实是一个抽象类,它包含了一个纯虚函数:virtual void runThisTest() = 0;。
从主菜单的场景到示例的场景,切换就完成了。
最后总结一下程序的执行过程。
执行过程已经被我们搞定了,终于可以好好的去学习TestCpp中的示例了~!