cocos2d-x的HelloWorld很简单一行标题、一个图片、一个按钮、一花一世界(⊙_⊙)。
也许有人会说了,左下角还有几行数据呢。额,那个不是HelloWorld本身的部分,只是一些统计数据。
在分析代码前,阅读以下两篇博文,会对理解代码有所帮助:
【玩转cocos2d-x之二】游戏和引擎构成
http://blog.csdn.net/jackystudio/article/details/12432837
【玩转cocos2d-x之十】cocos2d-x坐标系
http://blog.csdn.net/jackystudio/article/details/12830971
【玩转cocos2d-x】系列我也只是看了其中几篇,有时间要系统的研究一下。
那么接下来,我将尝试逐层的分析HelloWorld源码。初学cocos2d-x,难免有理解不准确的地方,望理解与指正。
首先,一切从main()开始。main.cpp中12~13行,告诉编译器hPrevInstance和lpCmdLine这两个变量我不用,就不要再向我报警告了。不过说句题外话,HelloWorld在编译过程中警告真不少,而且第一次编译下来这个速度啊……
16行,定义一个AppDelegate类的实例。17行,稀里糊涂的就run起来了。我第一次看到这里的时候感觉有些奇怪,定义的app看起来没有使用啊。同时,类的成员函数怎么用类名就直接调用起来了,难道不用通过类的实例调用?后来经过一番查阅资料,得知这里使用的是C++单例类的设计思想。通俗的讲就是保证单例类只有一个实例,无论从程序的什么地方获取该类的实例都是那一个。具体的东西请各位网上查阅资料,这里就不再赘述了。
17行虽然稀里糊涂的就让整个程序跑起来了,可分析代码可不能稀里糊涂,还是先从定义实例开始看吧。定义一个类的实例会调用一系列类的构造函数,看看这些构造函数中都做了写什么。AppDelegate类的相关继承关系如下:
ApplicationProtocol 无Construct
↑
Application Application()
↑
AppDelegate AppDelegate()中无内容
可以看到,定义实例app后,只有Application的构造函数做了实际的工作。CCApplication-win32.cpp中49行,Application::_instance获得了当前程序的句柄,这个和windows启动一个程序有关,具体没有研究过。51行,是一个cocos2d-x封装后的断言。如果Application::sm_pSharedApplication非空,即已经被初始化过,则要报错,中止程序。因为接下来马上就要初始化Application::sm_pSharedApplication了,如果之前初始化过,那是在哪儿初始化的★_★?!52行,由于AppDelegate是Application的子类,所以这一this实际上是指向AppDelegate的实例的。(⊙_⊙)终于知道main()中定义的那个app实例是做什么用的了,那么52行这里,Application::_sm_pSharedApplication实际得到的是app的地址。至此,Application的构造函数结束。main.cpp中16行这么一行代码实际上也就做了以上这些事情,总结一下就是对Application::_instance和Application::_sm_pSharedApplication赋值。
接下来回到main()中,该深入了解一下程序是怎么稀里糊涂的跑起来的了。main.cpp,17行,首先是Application::getInstance()。CCApplication-win32.cpp中126行,首先还是个断言,Application::_sm_pSharedApplication必须已被初始化过。这回很踏实,上面已经看到它是怎么被初始化的了。之后127行返回Application::_sm_pSharedApplication,通过上面的分析实际上返回的是app的地址。此时回到main()中继续往后看,->run(),这里又明了了,类的成员函数实际上还是通过类的实例调用起来的,只不过这里隐藏起来的而已,这是单例类的特点。
进入run(),CCApplication-win32.cpp中63行,这个没有仔细研究,往里看看好像是在往注册表里写什么东西,同时在网上查到这么一段资料:
A: 谁知道PVRFrame是什么东西?
B: PVR是ios里面用的opengles硬件加速芯片的厂商名字,PowerVR。在cocos2d-iphone里支持,在cocos2d-x里为了跨平台不支持。
反正设置成false了,那就是不起作用,先不管它了。
66~68行,这3个变量用于存储windows平台内部高精度计数器的值,通过这3个变量监测时间的流逝,从而决定何时应该渲染下一帧。
70行,获取CPU的时钟频率。71行,获取CPU至此时已运行了多长时间。都是windows平台内部高精度计数器的相关操作。
73行,初始化OpenGL的上下文,HelloWorld程序中没有实际内容。不过我现在还不太明白初始化的这个上下文是怎么个意思,看来需要学习OpenGL的相关知识。
76行,这个函数比较重要,HelloWorld中有什么元素,摆放的位置等等都在其中进行了设置。我们暂且先不继续看run(),先从这个函数深入进去,看看HelloWorld中的文字、图片、按钮都是怎么出来的。
AppDelegate.cpp中27行,这里碰到了cocos2d-x的核心类之一,Director。还记得分析代码前熟悉的“【玩转cocos2d-x之二】游戏和引擎构成”这篇博文中的内容吗,这个就是最顶层的那个Director。Director也是个单例类,这里不深入分析,只知道director获得了Director类的实例就可以了。28~32行,glview尝试通过director获得OpenGL的操控权。接下来个人理解是如果系统不支持OpenGL,则使用cocos2d-x自己实现的OpenGL,并将该OpenGL的操控权给glview。这里也暂不深入,因为需要了解更多的OpenGL知识。
35行,开启FPS状态显示。这里与程序运行起来后显示的一块东西关联起来了,就是左下角的那几行数据。这里也暂不深入,涉及到cocos2d-x核心类的相关操作都暂且跳过哈。
38行,设置FPS,每秒60帧。
41行,scene通过HelloWorld类创建一个屏幕。可以想象,HelloWorld类中定义好了屏幕中要显示的元素以及元素的相关属性,通过createScene()成员函数创建好一个可供显示的屏幕,然后将其交给scene。
44行,拿着这个scene给director,说我就要显示这个屏幕了。
AppDelegate::applicationDidFinishLaunching()中将所有准备工作都做好了,一会儿出去就等着让OpenGL一帧一帧的绘制了。在这里我们先不出去,从41行继续深入一下,看看HelloWorld::createScene()是如何将要显示的元素以及其相关属性设置好的。
HelloWorldScene.cpp中9行,调用cocos2d-x核心类Scene创建一个屏幕。12行,又是HelloWorld的create,看来实际创建的源码藏的挺深。15行,拿着创建好的一套元素大类将其加入scene中。18行,返回这个屏幕。返回的这个屏幕就是函数外面要显示的屏幕。至此,还是没有看到HelloWorld设置元素实际的源码,因此从12行继续深入。
HelloWorldScene.h中19行,看到这里会发现HelloWorld::create()实际上是通过CREATE_FUNC这个宏定义实现的,宏CREATE_FUNC定义在CCPlatformMacros.h中39行。我们结合给宏定义传递的参数,将宏定义展开,得到HelloWorld::create()的实现源码:
可以看到,创建了一个HelloWorld类的实例,成功后调用其init()进行初始化。init()也成功之后,调用autorelease()让实例在适当的时候自动释放,这个具体的机制没有研究,暂不深入。HelloWorld类的相关继承关系如下:
Ref
↑
Node
↑
Layer
↑
HelloWorld
HelloWorld类本身没有构造函数,由于其父类是cocos2d-x的核心类之一,所以HelloWorld类的父类们的构造函数就先暂不分析了,只要知道cocos2d-x为我们做好了充分的准备工作就可以了,╰( ̄▽ ̄)╮。
接下来深入HelloWorld::init(),HelloWorldScene.cpp中22行,粗略的看了看函数的实现,发现我们终于看到了真相,一堆create(),setPosition()什么的,一看就知道是在设置要显示的元素极其相关属性。27行,先让它的父类Layer进行初始化,准备工作做充分<( ̄︶ ̄)>。32行,visibleSize获得屏幕可见区域的宽和高。33行,origin获得OpenGL坐标系原点,在屏幕的左下角,这里可以阅读文章开始给出的“【玩转cocos2d-x之十】cocos2d-x坐标系”博文了解相关概念。
40行,closeItem通过MenuItemImage创建一个按钮,正常情况下的图片是“CloseNormal.png”,被点击情况下的图片是“CloseSelected.png”。对了,HelloWorld程序所用到的素材都在Resources/下,程序运行起来后也是从这个目录下寻找素材的。最后,当按钮被点击后调用HelloWorld::menuCloseCallback回调函数,CC_CALLBACK_1就是注册这个回调函数的过程。这里可以去看一眼这个回调函数,里面很简单,只有一句话起作用,436行,让Director说“咔”,然后程序就结束了。
接下来继续,45行,设置这个关闭按钮的位置。origin.x是x轴0点,也就是屏幕最左边;visibleSize.width是屏幕的宽度;closeItem->getContentSize().width是按钮的宽度。由于关闭按钮这个元素的锚点在图片的中心,所以需要减去图片宽度的一半,才能让图片正好贴着屏幕右边沿显示。y轴坐标的设置同理,最终将图片放置在屏幕的右下角。这里设置的实际上是相对坐标,一会儿要将closeItem放在Menu中,还要根据Menu的坐标最终确定这个关闭按钮的绝对坐标。
关于锚点的描述,建议参考cocos2d-x英文官网上的编程手册Chapter 2中的介绍:http://www.cocos2d-x.org/programmersguide/2/
49行,menu获得通过closeItem创建的Menu。50行,设置Menu的坐标为(0, 0),和上面联系起来,关闭按钮的绝对坐标就是屏幕的右下角。51行,将按钮添加进屏幕中(实际上是添加进了Layer中,在函数外面HelloWorldScene.cpp的15行,再将Layer添加进Scene中)。
至此,一个关闭按钮的相关属性设置就完成了。总结一下,其实有3大步,create()、setPosition()、addChild()。接着往下看,你会发现屏幕中其他元素的设置也是这3大步这么个流程。
59行,ttf是一种字体文件格式,这里就是用Marker Felt字体写了一个“Hello World”放在一个标签中给label。62行,设置label的坐标。66行,将label添加进屏幕。看看,是不是还是这3大步的节奏。
下面69~75行同样,是创建图片的过程。
至此,HelloWorld::init()函数结束,屏幕上要显示的东西都准备好了,就差去渲染它们了,让我们一直往前退吧。往前退会发现一只能退到CCApplication-win32.cpp中的76行,还记得之前说过的吗,当时Application::run()就是分析到这里然后深入进去了,现在分析完了,我们继续在run()中往下走。
81行,director获得Director类的实例。82行,glview获得OpenGL的控制权。这段好像在哪儿看到过,没错,就是在AppDelegate::applicationDidFinishLaunching()中看到过。单例类,操控的都是同一个实例。
85行,看注释是说防止glview在主循环中被释放,具体没有研究。主循环的渲染都没有结束,OpenGL的控制权当然不能释放了,不过为什么有可能会被释放呢?
87行这个while,如果程序窗口没有被关闭,则通过windows内部高精度计数器计算时间的流逝,以决定是否要渲染下一帧。如果需要则调用director->mainLoop(),这里就不深入了,深入进去的事儿可大了,⊙﹏⊙。同时95行,还在监听着游戏摇杆时间,不过在这个版本中没有实现。另外,如果不需要渲染下一帧,则等待1s。这里不很理解,FPS是60,等待1s难道要错过60帧的内容?
104行,如果程序窗口被关闭了,也就跳出了上面的while(),那么之后就是要做一些清理工作了。104~110行,导演说:“收工了,大家都回家吃饭了,glview别忘了把OpenGL控制权还给人家!”
好了,至此run()分析完,main()中的代码也就都执行完毕了。额,饿了,回家吃饭了,<( ̄︶ ̄)>。