本文关于HelloWorld的分析希望有利于你的学习,本文难免有所错误,如有问题欢迎留言
目录:
1、main
2、AppDelegate
3、HelloWorld
main
#include "main.h"
#include "AppDelegate.h"
#include "cocos2d.h"
USING_NS_CC;
int APIENTRY _tWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
// create the application instance
AppDelegate app;
return Application::getInstance()->run();
}
AppDelegate app;
创建一个AppDelegate对象,通过getInstance()获取当前应用的实例(该实例为一个静态成员变量,在AppDelegate的构造函数中进行了初始化)。
run()
创建、运行窗口,不停的绘制界面,以及进行消息循环。
问题一:为什么我们声明的是一个AppDelegate的一个对象app,但是我们调用却是使用Application::getInstance()->run();来调用AppDelegate类中的run?
首先,AppDelegate是继承于Application,在声明 AppDelegate对象app时,会先初始化父类的构造函数然后是子类的构造函数,此时我们的Application的静态成员就已经初始化完毕(类的静态成员本质为一个全局变量),通过该实例我们就能调用run()函数。
Application的构造函数
Application * Application::sm_pSharedApplication = 0;
Application::Application()// 该构造函数在AppDelegate app;声明的时候构造的
: _instance(nullptr)
, _accelTable(nullptr)
{
_instance = GetModuleHandle(nullptr);
_animationInterval.QuadPart = 0;
CC_ASSERT(! sm_pSharedApplication);
sm_pSharedApplication = this;//此处给我们的静态成员赋值
}
Application* Application::getInstance()//此方法为一个静态方法
{
CC_ASSERT(sm_pSharedApplication);
return sm_pSharedApplication;
}
sm_pSharedApplication = this;该句是实现的关键
问题二:该句实现的原理是什么呢?
我们先来了解下类继承的内存结构
首先我们需要知道,同一个类,无论产生多少个实例,它们都是公用同一个虚表。
回到我们的程序上,通过内存我们不难发现,this就是AppDelegate的首地址,和虚表指针的首地址相同。sm_pSharedApplication = this后,sm_pSharedApplication获取的就是虚表指针的值,指向虚表的首地址。而sm_pSharedApplication为一个Application的类实例,我们可以理解为它从AppDelegate虚表中截取了Application部分的虚表,而这些虚表我们在AppDelegate中继承实现,调用相关虚函数时其实就是调用AppDelegate实现的虚函数。如果还无法理解,再去了解在接口的原理。
run()函数讲解
int Application::run()
{
//PVRVFrame是仿真库,允许OpenGL ES的应用程序可以在本身不支持OpenGL ES的API的桌面开发机器上运行的集合。
PVRFrameEnableControlWindow(false);
// Main message loop:主消息循环
LARGE_INTEGER nLast;// 声明一个LARGE_INTEGER联合体(64位)变量nLast
LARGE_INTEGER nNow;
// 此函数用于获取精确的性能计数器数值,将计数器数值的地址存放到nLast中
// 该计数器数值,就是CPU运行到现在的时间(微秒级)
QueryPerformanceCounter(&nLast);
// 初始化 OpenGL 上下文(就是初始化OpenGL)
initGLContextAttrs();
// 初始化了场景、适配器尺寸和其他相关的参数。注意了,applicationDidFinishLaunching为继承父类的纯虚函数,具体的实现是在子类AppDelegate中。
// 如果你不够了解多态与虚函数机制,可能还是无法理解为什么在子类中实现,在这里使用。这其实并不难,你也可以看下我关于多态与虚表的博文
if (!applicationDidFinishLaunching())
{
return 1;
}
auto director = Director::getInstance();// 通过单例模式获取Director导演实例
auto glview = director->getOpenGLView();// 获取绘制所有对象的OpenGL视图
// Retain的意思是保持引用,如果我们想保持某个对象的引用,避免它被Cocos2d-x释放,就需要调用retain
// 此时内存管理机制就不会进行自动释放glview,而需要手动调用release释放该资源,否则会造成内存泄漏
glview->retain();
// 如果窗口未关闭,则一直进行消息循环
while(!glview->windowShouldClose())
{
// 获取CPU运行到现在的时间,将该时间保存到nNow变量中
QueryPerformanceCounter(&nNow);
// 计算当前时间与上一帧的时间间隔是否大于设定每帧的时间间隔(默认60帧/秒)
// _animationInterval 帧率的设置在AppDelegate的applicationDidFinishLaunching中
if (nNow.QuadPart - nLast.QuadPart > _animationInterval.QuadPart)
{
// 重置上一帧的时间,nNow.QuadPart % _animationInterval.QuadPart为了方便取整
// 有的写法写成 nLast.QuadPart= nNow.QuadPart; 也是可以的
nLast.QuadPart = nNow.QuadPart - (nNow.QuadPart % _animationInterval.QuadPart);
// 进行主消息循环,进行对消息进行处理(如果你了解win32消息机制,理解这个完全没有问题)
director->mainLoop();
// 处理一些事件
glview->pollEvents();
}
else
{
Sleep(1);
}
}
// 关闭后,清理导演资源
if (glview->isOpenGLReady())
{
director->end();
director->mainLoop();
director = nullptr;
}
glview->release();// 手动释放glview资源,因为我们前面glview->retain();后需要自己进行对该资源的内存管理
return 0;
}
applicationDidFinishLaunching()调用的原理,我们在上面已经讲解。
小结下:run中主要进行了相关资源的初始化,之后进入消息循环,消息循环中处理我们的消息,直到我们发出退出事件消息,然后清理资源,退出程序。
AppDelegate
AppDelegate.h
#include "cocos2d.h"
class AppDelegate : private cocos2d::Application
{
public:
AppDelegate();
virtual ~AppDelegate();
// 继承于父类Application的虚函数,而Application继承于ApplicationProtocol
// 在这里实现后,Application就能调用该实现
virtual void initGLContextAttrs();
virtual bool applicationDidFinishLaunching();
virtual void applicationDidEnterBackground();
virtual void applicationWillEnterForeground();
};
方法:applicationDidFinishLaunching
bool AppDelegate::applicationDidFinishLaunching() {
// 初始化导演类(单例模式,后面再讲解)
auto director = Director::getInstance();
// 进行一个窗口大小(分辨率)适配的相关处理
auto glview = director->getOpenGLView();
if(!glview) {
#if (CC_TARGET_PLATFORM == CC_PLATFORM_WIN32) || (CC_TARGET_PLATFORM == CC_PLATFORM_MAC) || (CC_TARGET_PLATFORM == CC_PLATFORM_LINUX)
glview = GLViewImpl::createWithRect("HelloTestCpp", Rect(0, 0, designResolutionSize.width, designResolutionSize.height));
#else
glview = GLViewImpl::create("HelloTestCpp");
#endif
director->setOpenGLView(glview);
}
// 显示我们的FPS(帧的频率)
director->setDisplayStats(true);
// 设置FPS值,默认值为1.0/60(每秒钟绘制60次)
director->setAnimationInterval(1.0 / 60);
// 分辨率的相关设置
glview->setDesignResolutionSize(designResolutionSize.width, designResolutionSize.height, ResolutionPolicy::NO_BORDER);
Size frameSize = glview->getFrameSize();
if (frameSize.height > mediumResolutionSize.height)
{
director->setContentScaleFactor(MIN(largeResolutionSize.height/designResolutionSize.height, largeResolutionSize.width/designResolutionSize.width));
}
else if (frameSize.height > smallResolutionSize.height)
{
director->setContentScaleFactor(MIN(mediumResolutionSize.height/designResolutionSize.height, mediumResolutionSize.width/designResolutionSize.width));
}
else
{
director->setContentScaleFactor(MIN(smallResolutionSize.height/designResolutionSize.height, smallResolutionSize.width/designResolutionSize.width));
}
// 加载我们额外的包
register_all_packages();
// 创建一个HelloWorld的场景
auto scene = HelloWorld::createScene();
// 通过导演类显示场景
director->runWithScene(scene);
return true;
}
前面我们了解到,applicationDidFinishLaunching的调用是在Application中。也是我们直观的程序启动函数。
通过导演类加载并显示我们游戏的第一个场景,开始游戏。
方法:applicationDidEnterBackground
void AppDelegate::applicationDidEnterBackground() {
Director::getInstance()->stopAnimation();
}
这里写入的是我们游戏进入后台时进行的操作
stopAnimation:停止动画。不进行绘制。主循环不会再被触发。 如果你不想暂停动画,请调用[pause]。
方法:applicationWillEnterForeground
void AppDelegate::applicationWillEnterForeground() {
Director::getInstance()->startAnimation();
}
这里写入的是我们游戏由后台切入到前景时进行的操作
startAnimation:主循环触发一次。 只有之前调用过stopAnimation,才能调用这个函数。
HelloWorld
HelloWorld.h
#ifndef __HELLOWORLD_SCENE_H__
#define __HELLOWORLD_SCENE_H__
#include "cocos2d.h"
class HelloWorld : public cocos2d::Layer
{
public:
static cocos2d::Scene* createScene();
virtual bool init();
// 按钮的回调函数
void menuCloseCallback(cocos2d::Ref* pSender);
// 实现“静态create()”方法
CREATE_FUNC(HelloWorld);
};
#endif // __HELLOWORLD_SCENE_H__
方法:createScene
Scene* HelloWorld::createScene()
{
auto scene = Scene::create();// 创建一个场景
auto layer = HelloWorld::create();// 创建 HelloWord 的图层
scene->addChild(layer);// 将 HelloWord 图层加入到场景中
return scene; // 返回我们创建的场景
}
接下来我们来逐句解释下相关的实现
首先我们来看一下Scene::create()的实现
Scene* Scene::create()
{
Scene *ret = new (std::nothrow) Scene();
if (ret && ret->init())// init 进行初始化场景大小操作
{
ret->autorelease();// 将场景对象加入到内存回收池中(cocos引用计数内存管理)
return ret;
}
else
{
CC_SAFE_DELETE(ret);// 删除对象的宏,就是执行的 delete ret;
return nullptr;
}
}
不难看出,这里进行了场景的创建,并且将创建的场景对象放入cocos自动管理内存释放的内存回收池中进行管理,随后返回我们创建的场景。
关于new (std::nothrow)与标准new的区别
new在分配内存失败时会抛出异常,而”new(std::nothrow)”在分配内存失败时会返回一个空指针(NULL)。具体的std::nothrow实现原理可以查阅了解
我们再来了解下HelloWorld::create()的实现
静态的create方法在头文件通过宏 CREATE_FUNC(HelloWorld); 声明,以下为宏的原型
#define CREATE_FUNC(__TYPE__)
static __TYPE__* create()
{
__TYPE__ *pRet = new(std::nothrow) __TYPE__();
if (pRet && pRet->init())
{
pRet->autorelease();
return pRet;
}
else
{
delete pRet;
pRet = nullptr;
return nullptr;
}
}
__TYPE__就是我们传入的HelloWorld类,我们这里如果把所有的__TYPE__替换成HelloWorld,聪明的你应该一下就理解了,而且不难发现它和场景的Scene::create()方法基本没有什么不同,都是创建一个类的实例,并调用自身的init()进行相应的初始化。然后对该对象进行内存管理。
最后是scene->addChild(layer);的实现,其实就是将我们创建的HelloWorld图层添加到我们的场景中(为什么HelloWorld类是一个图层?因为HelloWorld是继承于Layer的),因为导演播放的是场景,而场景里面是图层,图层里面再是一些精灵以及其他节点等… 这整个就是个树形结构,其实引擎的渲染也是树形渲染(大家也可以去了解下引擎渲染的原理),这里再回顾下各个节点之间的关系图
方法:init
bool HelloWorld::init()
{
//////////////////////////////
// 1. super init first
if ( !Layer::init() )
{
return false;
}
Size visibleSize = Director::getInstance()->getVisibleSize();
Vec2 origin = Director::getInstance()->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
auto closeItem = MenuItemImage::create(
"CloseNormal.png",
"CloseSelected.png",
CC_CALLBACK_1(HelloWorld::menuCloseCallback, this));
closeItem->setPosition(Vec2(origin.x + visibleSize.width - closeItem->getContentSize().width/2 ,
origin.y + closeItem->getContentSize().height/2));
// create menu, it's an autorelease object
auto menu = Menu::create(closeItem, NULL);
menu->setPosition(Vec2::ZERO);
this->addChild(menu, 1);
/////////////////////////////
// 3. add your codes below...
// add a label shows "Hello World"
// create and initialize a label
auto label = Label::createWithTTF("Hello World", "fonts/Marker Felt.ttf", 24);
// position the label on the center of the screen
label->setPosition(Vec2(origin.x + visibleSize.width/2,
origin.y + visibleSize.height - label->getContentSize().height));
// add the label as a child to this layer
this->addChild(label, 1);
// add "HelloWorld" splash screen"
auto sprite = Sprite::create("HelloWorld.png");
// position the sprite on the center of the screen
sprite->setPosition(Vec2(visibleSize.width/2 + origin.x, visibleSize.height/2 + origin.y));
// add the sprite as a child to this layer
this->addChild(sprite, 0);
return true;
}
总的来说,就是创建了一些精灵,然后添加到图层上面…
Layer::init()调用父类Layer的init初始化函数,对屏幕的适配大小初始化
bool Layer::init()
{
Director * director = Director::getInstance();
setContentSize(director->getWinSize());
return true;
}
getVisibleSize:获得可视区域的大小,若是DesignResolutionSize跟屏幕尺寸一样大,则getVisibleSize便是getWinSize。(以像素为单位)
getVisibleOrigin:表示可视区域的起点坐标,这在处理相对位置的时候非常有用,确保节点在不同分辨率下的位置一致。
方法:menuCloseCallback
void HelloWorld::menuCloseCallback(Ref* pSender)
{
Director::getInstance()->end();
#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS)
exit(0);
#endif
}
图片按钮添加的事件处理函数(和win32里面的窗口回调,以及java里面的事件监听都差不多)。具体是事件机制可以查阅资料了解
呼~终于是写完了,希望大家能够有所收获