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裏面的事件監聽都差不多)。具體是事件機制可以查閱資料瞭解

呼~終於是寫完了,希望大家能夠有所收穫

發佈了54 篇原創文章 · 獲贊 38 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章