cocos2dx详解HelloWorld

本文关于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里面的事件监听都差不多)。具体是事件机制可以查阅资料了解

呼~终于是写完了,希望大家能够有所收获

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值