cocos2d-x 系統學習cocos(1)

簡析HelloWorld場景

之前使用cocos2d-x 3.14的時候,HelloWorld並非一個場景類,而是一個圖層類,當時的HelloWorld::createScene()是長這樣的node

Scene* HelloWorld::createScene()
{
    auto scene = Scene::create();
    auto layer = HelloWorld::create();
    scene->addChild(layer);
    return scene;
}

而如今的3.17的HelloWorld::createScene()長這樣c++

Scene* HelloWorld::createScene()
{
    return HelloWorld::create();
}

區別就是HelloWorld自己已是一個場景了,不須要另外生成一個場景再將HelloWorld加到場景中做爲子節點程序員

HelloWorld的佈局

HelloWorld場景中有一個cocos的logo,一個關閉按鈕,一個HelloWorld的字樣,這些小物體都是在HelloWorld::init()中生成的
設計模式

基類的初始化

咱們向HelloWorld場景添加東西以前,須要先調用基類Scene類的初始化函數,而後得到一下visibleSize和origin備用app

bool HelloWorld::init()
{

    if ( !Scene::init() )
    {
        return false;
    }
    auto visibleSize = Director::getInstance()->getVisibleSize();
    Vec2 origin = Director::getInstance()->getVisibleOrigin();
.................
}

關閉按鈕的生成

bool HelloWorld::init()
{
.................
    auto closeItem = MenuItemImage::create(
                                           "CloseNormal.png",
                                           "CloseSelected.png",
                                           CC_CALLBACK_1(HelloWorld::menuCloseCallback, this));

    float x = origin.x + visibleSize.width - closeItem->getContentSize().width/2;
    float y = origin.y + closeItem->getContentSize().height/2;
    closeItem->setPosition(Vec2(x,y));

    auto menu = Menu::create(closeItem, NULL);
    menu->setPosition(Vec2::ZERO);
    this->addChild(menu, 1);
.................
}

這裏的代碼可能看起來會很複雜,其實否則,咱們能夠發現cocos裏不少對象在生成的時候都會使用create這個靜態工廠方法,HelloWorld這個場景也不例外,create將生成遊戲對象所須要的參數填進去構造一個對象的指針返回函數

MenuItemImage的create方法傳入默認狀態的close按鈕的圖片點擊狀態下的close按鈕的圖片以及一個回調,回調指的是程序對按鈕被按下這個事件作出的響應,使用CC_CALLBACK_1宏加上一個void(T::*)(Ref*)類型的成員函數的函數指針以及調用這個成員函數的對象的指針組成一個回調,看不懂不要緊,咱們照着寫就好
而後就是計算出x和y的值,也就是右下角的按鈕的座標,getContentSize()得到對象的尺寸,最後使用setPosition設置按鈕的座標佈局

注意,按鈕是不能夠直接添加到場景中的,按鈕須要依賴菜單,也就是Menu對象,因此咱們建立一個包含了closeItem的菜單,並設置座標爲(0,0),最後才能使用addChild將菜單添加到場景中學習

字體的生成

bool HelloWorld::init()
{
.................
    auto label = Label::createWithTTF("Hello World", "fonts/Marker Felt.ttf", 24);
    label->setPosition(Vec2(origin.x + visibleSize.width/2,
                                origin.y + visibleSize.height - label->getContentSize().height));
    this->addChild(label, 1);
.................
}

這個也很好理解,createWithTTF返回一個Label對象的指針,顯示的字符串字體字體大小做爲函數的參數,也是使用addChild添加到場景中,這裏的1比0高一層,咱們試着把文本的座標設置到場景中央字體

bool HelloWorld::init()
{
.................
    auto label = Label::createWithTTF("Hello World", "fonts/Marker Felt.ttf", 24);
    label->setPosition(Vec2(origin.x + visibleSize.width/2,
                                origin.y + visibleSize.height/2));
    this->addChild(label, 1);
.................
}

文本是在logo上方的,證實圖層越高,渲染得越晚,先渲染的被壓在後渲染的物體下面ui

精靈的生成

bool HelloWorld::init()
{
.................
    auto sprite = Sprite::create("HelloWorld.png");
    sprite->setPosition(Vec2(visibleSize.width / 2 + origin.x, visibleSize.height / 2 + origin.y));
    this->addChild(sprite, 0);
    return true;
.................
}

這個就更簡單了,使用一張圖片生成一個精靈,一樣也是加到場景中,最後要記得return true,若是init函數不返回true的話程序會崩掉的

深刻探索HelloWorld場景

我一直都認爲cocos2dx是學習c++的一個很是好的教材,cocos2dx使用了不少面向對象的特性,c++的特性,還有一些設計模式的思想,對一個新手程序員的綜合性成長有很大的幫助

遊戲開始的地方

首先,遊戲場景的入口是導演類的runWithScene,打開AppDelegate.cpp,找到AppDelegate::applicationDidFinishLaunching()函數,咱們能夠看到這樣的代碼

bool AppDelegate::applicationDidFinishLaunching() {
    auto director = Director::getInstance();
..............
    auto scene = HelloWorld::createScene();

    director->runWithScene(scene);

    return true;
}

Director類是一個單例類,使用getInstance能夠得到它的實例,單例其實是經過把構造函數私有化,把對象的訪問權限交給一個靜態函數實現的,通常咱們會使用懶加載來使用這種單例,扯遠了,也就是說Director使用了單例模式
Director經過runWithScene運行HelloWorld場景,並讓HelloWorld以及HelloWorld的子節點工做

Node類

Node類是HelloWorld場景裏咱們使用的大部分類的基類,事實上Scene類也是一個Node,很好理解,遊戲世界中的對象實際上大部分都是Node,Node和Node經過父子關係聯繫起來,遊戲裏的對象的模型是一棵樹,父節點使用addChild將子節點加到本身管理的子節點隊列中,遊戲運行的時候,導演就會遍歷這些Node讓他們進行工做,好比說HelloWorld場景,HelloWorld場景是根節點,精靈sprite,文本label,菜單menu是HelloWorld的子節點,按鈕closeItem是菜單menu的子節點

Ref類

Ref類是用於引用計數的類,負責對象的引用計數,Ref類是Node類的基類,也就是說全部的Node都是使用cocos2dx的引用計數內存管理系統進行內存管理的,這也是爲何咱們生成對象不是用new和delete,而是用create生成對象的緣由。這裏涉及到了GC的知識。簡單來講,引用計數法的理論是,當對象被引用的時候,對象的引用計數會+1,取消引用的時候就-1,當計數爲0的時候就將對象銷燬,感興趣能夠了解一下智能指針RAII

create

這個函數咱們能夠認爲它是一個工廠,這個工廠把咱們生成對象以前須要作的工做先作好了,在文章達到最開頭有這樣一段代碼

Scene* HelloWorld::createScene()
{
    return HelloWorld::create();
}

而後HelloWorldScene.h是這樣的

#ifndef __HELLOWORLD_SCENE_H__
#define __HELLOWORLD_SCENE_H__

#include "cocos2d.h"

class HelloWorld : public cocos2d::Scene
{
public:
    static cocos2d::Scene* createScene();

    virtual bool init();
    
    void menuCloseCallback(cocos2d::Ref* pSender);
    
    CREATE_FUNC(HelloWorld);
};

#endif

誒,奇怪了,爲何沒有看到create函數,難道是在基類裏?嗯呣,靜態成員函數是不能繼承的,因此問題出如今CREATE_FUNC上,沒錯!咱們看看CREATE_FUNC的定義

#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; \
    } \
}

能夠看出來,CREATE_FUNC是一個可讓你偷懶不用手動編寫create函數的宏
固然有的類須要客製化create,好比說Sprite的create

Sprite* Sprite::create()
{
    Sprite *sprite = new (std::nothrow) Sprite();
    if (sprite && sprite->init())
    {
        sprite->autorelease();
        return sprite;
    }
    CC_SAFE_DELETE(sprite);
    return nullptr;
}

create裏進行了什麼操做呢?
1.使用new生成對象
2.使用init初始化對象
3.使用autorelease將這個Ref類交給引用計數系統管理內存
看到這個init咱們是否是想到了什麼,HelloWorld場景的佈局就是在init中實現的,而init由create調用,也就是說,在HelloWorld進行create的時候就已經將文本,按鈕,精靈等物件建立並加入到場景中,而這些物件也是經過create建立的,也就是說,場景建立的時候會調用全部物件的init,這樣咱們對cocos2dx的遊戲流程是否是有了更深的理解
autorelease是Ref類的方法,查看一下它的定義

Ref* Ref::autorelease()
{
    PoolManager::getInstance()->getCurrentPool()->addObject(this);
    return this;
}

又看到了getInstance,說明PoolManager也是一個單例類,這段代碼的意思很明顯,將Ref加入到當前內存池中管理
咱們在後續的開發中常常須要客製化create,只要咱們的create能知足上面三個功能便可

總結

這節咱們經過研究cocos2dx新工程自帶的HelloWorld代碼瞭解到了不少東西,設計模式,GC,遊戲對象結構的設計思路,還有c++的各類小知識,用宏偷懶啦,宏保護避免重複編譯啦
嗯?宏保護是什麼?

宏保護

這裏順便講一下宏保護這個小知識點,宏保護使用來避免.h文件被重複編譯的,這裏以HelloWorldScene.h爲例

#ifndef __HELLOWORLD_SCENE_H__
#define __HELLOWORLD_SCENE_H__
............
#endif

#這個符號開頭的代碼是預處理命令,在程序編譯以前會進行預處理工做,這幾行代碼的意思是,若是沒有定義__HELLOWORLD_SCENE_H__這個符號就定義__HELLOWORLD_SCENE_H__而且編譯到endif爲止的內容,當__HELLOWORLD_SCENE_H__被定義過一次,預處理器下一次遇到這條預處理命令的時候就不會再把下面的代碼做爲編譯目標,固然如今vs有一條#pragma once的命令,保證該文件只編譯一次,也能夠達到咱們的目的

相關文章
相關標籤/搜索