cocos2d高級開發

爲何咱們要在一個實例方法中初始化類,而不在構造函數中初始化呢?在C++中,通常習慣在構造函數中初始化類,然而因爲Cocos2d-x的來源特殊,因此纔沒有採用C++的編程風格node

—————————————————————————————————————————————————————————程序員

一個複雜場景會擁有多個層,一個層會顯示一部分視覺元素,空白部分爲透明或半透明,以實現多個層的重疊顯示。層與層之間按照順序疊放在一塊兒,就組成了一個複雜的場景。web

————clorlayer 設置透明正則表達式

—————————————————————————————————————————————————————————算法

4條腿相對整個海龜在必定角度內旋轉;軀幹相對於整個海龜靜止不動;整個海龜在魚層中游動,位置和方向在不斷改變。
若是沒有樹型結構,組織一個稍微複雜的遊動都會成爲一個巨大的工程。編程

—————————————————————————————————————————————————————————canvas

構造函數與初始化
在C++中,咱們只須要調用類的構造函數便可建立一個對象,既可直接建立一個棧上的值對象,也能夠使用new操做符建立一個指針,指向堆上的對象。
Cocos2d-x不使用傳統的值類型(由類型的實際值表示的數據類型指針類型指向數據類型),全部的對象都建立在堆上,而後經過指針引用。數組

在Objective-C中並無構造函數,建立一個對象須要先爲對象分配內存,而後調用初始化方法來初始化對象,這個過程就等價於C++中的構造函數。與Objective-C同樣,Cocos2d-x也採用了這個步驟。Cocos2d-x類的構造函數一般沒有參數,建立對象所需的參數經過init開頭的一系列初始化方法傳遞給對象。
—————————————————————————————————————————————————————————
? bool init()
? {
? if(CCLayer::init())   //規範。 父類的init調用 不然會出問題的
? {
? //在此處寫入初始化這個類所需的代碼
? return true;
? }
? return false;
? }
—————————————————————————————————————————————————————————瀏覽器

現有的智能內存管理技術 ? 目前,主要有兩種實現智能管理內存的技術,一是引用計數,一是垃圾回收。引用計數:它是一種頗有效的機制,經過給每一個對象維護一個引用計數器,記錄該對象當前被引用的次數。當對象增長一次引用時,計數器加1;而對象失去一次引用時,計數器減1;當引用計數爲0時,標誌着該對象的生命週期結束,自動觸發對象的回收釋放。引用計數的重要規則是每個程序片斷必須負責任地維護引用計數,在須要維持對象生存的程序段的開始和結束分別增長和減小一次引用計數,這樣咱們就能夠實現十分靈活的智能內存管理了。實際上,這與new和delete的配對使用十分相似,可是很巧妙地將生成和回收的事件轉換成了使用和使用結束的事件。對於程序員來講,維護引用計數比維護生命週期信息輕鬆了許多。引用計數解決了對象的生命週期管理問題,但堆碎片化和管理煩瑣的問題仍然存在。垃圾回收:它經過引入一種自動的內存回收器,試圖將程序員從複雜的內存管理任務中徹底解放出來。它會自動跟蹤每個對象的全部引用,以便找到全部正在使用的對象,而後釋放其他再也不須要的對象。垃圾回收器還能夠壓縮使用中的內存,以縮小堆所須要的工做空間。垃圾回收能夠防止內存泄露,有效地使用可用內存。可是,垃圾回收器一般是做爲一個單獨的低級別的線程運行的,在不可預知的狀況下對內存堆中已經死亡的或者長時間沒有使用過的對象進行清除和回收,程序員不能手動指派垃圾回收器回收某個對象。回收機制包括分代複製垃圾回收、標記垃圾回收和增量垃圾回收。緩存

Cocos2d-x的內存管理機制

爲了實現對象的引用計數記錄,Cocos2d-x實現了本身的根類CCObject,引擎中的全部類都派生自CCObject。在"CCObject.h"頭文件中咱們能夠看到CCObject的定義
class CC_DLL CCObject : public CCCopying
? {
? public:
//對象id,在腳本引擎中使用
? unsigned int m_uID;
? //Lua中的引用ID,一樣被腳本引擎使用
? int m_nLuaID;
? protected:
? //引用數量
? unsigned int m_uReference;
? //標識此對象是否已設置爲autorelease
? bool m_bManaged;
? public:
? CCObject(void);
? virtual ~CCObject(void);
? void release(void);
? void retain(void);
? CCObject* autorelease(void);
? CCObject* copy(void);
? bool isSingleRefrence(void);
? unsigned int retainCount(void);
? virtual bool isEqual(const CCObject* pObject);
? virtual void update(ccTime dt) {CC_UNUSED_PARAM(dt);};
? friend class CCAutoreleasePool;
? };

每一個對象包含一個用來控制生命週期的引用計數器,它就是CCObject的成員變量m_u- Reference。咱們能夠經過retainCount()方法得到對象當前的引用計數值。在對象經過構造函數建立的時候,該引用值被賦爲1,表示對象由建立者所引用。在其餘地方須要引用對象時,咱們會調用retain()方法,令其引用計數增1,表示獲取該對象的引用權;在引用結束的時候調用release()方法,令其引用計數值減1,表示釋放該對象的引用權。
另外一個頗有趣的方法是autorelease(),其做用是將對象放入自動回收池(CCAutore- leasePool)。當回收池自身被釋放的時候,它就會對池中的全部對象執行一次release()方法,實現靈活的垃圾回收。回收池能夠手動建立和釋放。除此以外,引擎在每次遊戲循環開始以前也會建立一個【新的】回收池,在循環結束後釋放回收池。所以,即便咱們沒有手工建立和釋放回收池,每一幀結束的時候,自動回收池中的對象也都會被執行一次release()方法。咱們立刻就會領略到autorelease()的方便之處。
下面是一個簡單的例子。能夠看到,對象建立後,引用計數爲1;執行一次retain()後,引用計數爲2;執行一次release()後,引用計數回到1;執行一次autorelease()後,對象的引用計數值並無當即減1,可是在下一幀開始前,對象會被釋放掉。

雖然,Cocos2d-x已經保證每一幀結束後釋放一次回收池,並在下一幀開始前建立一個新的回收池,可是咱們也應該考慮到回收池自己維護着一個將要執行釋放操做的對象列表,若是在一幀以內生成了大量的autorelease對象,將會致使回收池性能降低。所以,在生成autorelease對象密集的區域(一般是循環中)的先後,咱們最好能夠手動建立並釋放一個回收池。

咱們能夠經過回收池管理器CCPoolManager的push()或pop()方法來建立或釋放回收池,其中的CCPoolManager也是一個單例對象

CCPoolManager::sharedPoolManager()->push();
? for(int i=0; i<n; i++)
? {
? CCString* dataItem = CCString::createWithFormat("%d", Data[i]);
? stringArray->addObject(dataItem);
? }
? CCPoolManager::sharedPoolManager()->pop();


一般,引擎維護着一個回收池,全部的autorelease對象都添加到了這個池中。多個自動回收池排列成棧結構,當咱們手動建立了回收池後,回收池會壓入棧的頂端,autorelease對象僅添加到頂端的池中。當頂層的回收池被彈出釋放時,它內部全部的對象都會被釋放一次,此後出現的autorelease對象則會添加到下一個池中
(仍是蠻好用的)


————————————
工廠方法
? CCObject* factoryMethod() {
? CCObject* ret = new CCObject();
? //這裏對ret對象進行必要的初始化操做
? ret->autorelease();              ——————建立後加入垃圾池 因此不addChild在下一幀就沒了!!!!!!!
? return ret;
? }
///////////////////////////////////用new+init+autorelease也能夠 的  吧 

若是須要繼續使用 調用者有足夠的時間來對它進行retain操做以便接管ret對象的引用權 

所以,咱們建議在開發過程當中應該避免濫用autorelease(),只在工廠方法等不得不用的狀況下使用,儘可能以release()來釋放對象引用。
————————————

關於對象傳值 ?

將一個對象賦值給某一指針做爲引用的時候,爲了遵循內存管理的原則,咱們須要得到新對象的引用權,釋放舊對象的引用權。此時,release()和retain()的順序是尤其重要的首先來看下面一段代碼:
void SomeClass::setObject(CCObject* other)
{
? this->object->release();
? other->retain();
? this->object = other;
} ?
 這裏存在的隱患是,當other和object實際上指向同一個對象時,第一個release()可能會觸發該對象的回收,這顯然不是咱們想看到的局面,因此應該先執行retain()來保證other對象有效,而後再釋放舊對象:
void SomeClass::setObject(CCObject* other) {
? other->retain();
? this->object->release();
   this->object = other;
}

——————————

容器

使用Cocos2d-x容器的一個重要緣由在於Cocos2d-x的內存管理。通常來講,被存入容器的對象在移除以前都應該保證是有效的,回顧一下引用計數的管理原則,對象的存入和移除必須對應一組retain()和release()或者對應autorelease()。直接使用STL容器,開發者勢必進行煩瑣重複的內存管理操做,而Cocos2d-x容器對這一過程進行了封裝,保證了容器對對象的存取過程老是符合引用計數的內存管理原則。

存入容器的對象必須是CCObject或其派生類。同時,Cocos2d-x的容器自己也是CCObject的派生類,當容器被釋放時,它保存的全部元素都會被釋放一次引用

容器存在的意義不只僅侷限於內存管理方面,所以咱們應該儘可能採用Cocos2d-x提供的容器類。(跨語言移植遊戲 因此不用stl)

內存管理原則總結——————
程序段必須成對執行retain()和release()或者執行autorelease()來聲明開始和結束對象的引用;工廠方法返回前,應經過autorelease()結束對該對象的引用;對象傳值時,應考慮到新舊對象相同的特殊狀況;儘可能使用release()而不是autorelease()來釋放對象引用,以確保性能最優;保存CCObject的子類對象時,應嚴格使用Cocos2d-x提供的容器,避免使用STL容器,對象必須以指針形式存入。

若是但願自定義的類也擁有Cocos2d-x的內存管理功能,能夠把CCObject做爲自定義類的基類,並在實現類時嚴格遵照Cocos2d-x的內存管理原則

—————————————————————————————————————————————————————————

導演

popScene:釋放當前場景,再從代執行場景棧中彈出棧頂的場景,並將其設置爲當前運行場景。若是棧爲空,則直接結束應用。與pushScene成對使用,能夠達到形如由主界面進入設置界面,而後回到主界面的效果。

值得注意的一點是,以上三種切換場景的方法(replaceScene、pushScene、popScene)均是先將待切換的場景徹底加載完畢後,纔將當前運行的場景釋放掉。因此,在新場景剛好徹底加載完畢的瞬間,系統中同時存在着兩個場景

—————————————————————————————————————————————————————————

void addChild(CCNode* child);
void addChild(CCNode* child, int zOrder);
void addChild(CCNode* child, int zOrder, int tag);

若是爲子節點設置了tag值,就能夠在它的父節點中利用tag值找到它了

—————————————————————————————————————————————————————————
精靈
bool isFrameDisplayed(CCSpriteFrame *pFrame):返回一個值,表示pFrame是不是正在顯示中的紋理框幀。

顏色相關的屬性 ?
CCSprite提供瞭如下與顏色相關的屬性。 ?
ccColor3 Color:獲取或設置疊加在精靈上的顏色。
ccColor3由三個顏色份量(紅色、綠色和藍色份量)組成。默認爲純白色,表示不改變精靈的顏色,若是設置爲其餘值,則會改變精靈的顏色。 ?
GLubyte Opacity:獲取或設置精靈的不透明度。GLubyte爲OpenGL的內置類型,表示一個無符號8位整數,取值範圍從最小值0到最大值255。 ?
bool OpacityModifyRGB:獲取或設置精靈所使用的紋理數據是否已經預乘Alpha通道。當包含Alpha通道的圖片顯示錯誤時,能夠嘗試修改這個屬性。


對於精靈來講,ContentSize是它的紋理顯示部分的大小;對於層或場景等全屏的大型節點來講,ContentSize則是屏幕大小。

對於場景或層等大型節點,它們的IgnoreAnchorPointForPosition屬性爲true,此時引擎會認爲AnchorPoint永遠爲(0,0);而其餘節點的該屬性爲flase,它們的錨點不會被忽略。

Tag能夠用於定位子節點,所以添加到同一節點的全部CCNode之中,不能有兩個節點的Tag相同,不然就給定位帶來了麻煩。與Tag相關的方法有getChildByTag、removeChildByTag等。

void* UserData:獲取或設置與節點相關的額外信息。UserData爲void*類型,咱們能夠利用這個屬性來保存任何數據。

—————————————————————————————————————————————————————————

定時器

很顯然,定時器就是使遊戲動態變化所需的工具。Cocos2d-x爲咱們提供了兩種方式實現定時機制--使用update方法以及使用schedule方法,下面簡要介紹這兩種方式。

update定時器 ? 第一種定時機制是CCNode的刷新事件update方法,該方法在每幀繪製以前都會被觸發一次

schedule定時器 ? 另外一種定時機制是CCNode提供的schedule方法,能夠實現以必定的時間間隔連續調用某個函數。因爲引擎的調度機制,這裏的時間間隔必須大於兩幀的間隔,不然兩幀期間的屢次調用會被合併成一次調用。
實際開發中,許多定時操做都經過schedule定時器實現,例如魚羣的定時生成、免費金幣的定時刷新等(由於update的調用間隔是不可控的)

。咱們把處理魚羣移動和碰撞監測的代碼放置在主遊戲場景GameScene的updateGame方法中。在GameScene的init初始化方法中添加如下代碼來啓用定時器:
? this->schedule(schedule_selector(GameScene::updateGame)

通常是 不用update的 直接自定義一個update函數

scheduleUpdateWithPriority(int priority)  啓用update定時器,並設定定時器的優先級
schedules the "update" selector with a custom priority. This selector will be called every frame.
     Scheduled selectors with a lower priority will be called before the ones that have a higher value.
     Only one "update" selector could be scheduled per node (You can't have 2 'update' selectors)

unscheduleUpdate      取消update定時器

scheduleOnce(SEL_SCHEDULE selector, float delay)CONTROL     添加一個schedule定時器,但定時器只觸發一次

unschedule(SEL_SCHEDULE selector)ESC       取消selector所對應函數的定時器

unscheduleAllSelectors          取消此節點所關聯的所有定時器

pauseSchedulerAndActions     暫停此節點所關聯的所有定時器與動做

resumeSchedulerAndActions     繼續執行此節點所關聯的定時器與動做


定時器機制是Cocos2d-x調度機制的基礎,第4章將介紹的動做機制實際上也依賴定時器實現。因爲Cocos2d-x的調度是純粹的串行機制,所以全部函數都運行在同一個線程,不會存在並行程序的種種麻煩,這大大簡化了編程的複雜性。

其餘與流程控制相關的事件————

onEnter()——
當此節點所在場景即將呈現時,會調用此方法

onEnterTransitionDidFinish()——  
當此節點所在場景的入場動做結束後,會調用此方法。若是所在場景沒有入場動做,則此方法會緊接着onEnter()後被調用

onExit()——
當此節點所在場景即將退出時,會調用此方法

onExitTransitionDidStart()——
當此節點所在場景的出場動做結束後,會調用此方法。若是所在場景沒有出場動做,則此方法會緊接着onExit()後被調用

這些事件的默認實現一般負責處理定時器和動做的啓用與暫停,所以必須在重載方法中調用父類的方法

—————————————————————————————————————————————————————————

Cocos2d-x內置的經常使用層(1)

CCLayerColor:一個單純的實心色塊。 ?

CCLayerGradient:一個色塊,但能夠設置兩種顏色的漸變效果。 ?

CCMenu:十分經常使用的遊戲菜單。

static CCLayerColor * create(const ccColor4B& color);
static CCLayerColor * create(const ccColor4B& color, GLfloat width, GLfloat height);

static CCLayerGradient* create(const ccColor4B& start, const ccColor4B& end,const CCPoint& v);


在色塊建立後,也能夠經過下面列舉的方法來修改色塊大小:
void changeWidth(GLfloat w);
void changeHeight(GLfloat h);
void changeWidthAndHeight(GLfloat w ,GLfloat h);

—————————————————————————————————————————————————————————

Cocos2d-x調度原理 ?

Cocos2d的一大特點就是提供了事件驅動的遊戲框架,引擎會在合適的時候調用事件處理函數,咱們只須要在函數中添加對各類遊戲事件的處理,就能夠完成一個完整的遊戲了。例如,爲了實現遊戲的動態變化,Cocos2d提供了兩種定時器事件;爲了響應用戶輸入,Cocos2d提供了觸摸事件和傳感器事件;此外,Cocos2d還提供了一系列控制程序生命週期的事件。

遊戲主循環(1)

遊戲乃至圖形界面的本質是不斷地繪圖,然而繪圖並非隨意的,任何遊戲都須要遵循必定的規則來呈現出來,這些規則就體現爲遊戲邏輯。遊戲邏輯會控制遊戲內容,使其根據用戶輸入和時間流逝而改變。所以,遊戲能夠抽象爲不斷地重複如下動做————? 處理用戶輸入 ?
 處理定時事件 ?
 繪圖 ?
一、遊戲主循環就是這樣的一個循環,它會反覆執行以上動做,保持遊戲進行下去,直到玩家退出遊戲

在Cocos2d中,以上的動做包含在CCDirector的某個方法之中,而引擎會根據不一樣的平臺設法使系統不斷地調用這個方法,從而完成了遊戲主循環。 ?

二、如今咱們回到Cocos2d-x遊戲主循環的話題上來。上面介紹了CCDirector包含一個管理引擎邏輯的方法,它就是CCDirector::mainLoop()方法,這個方法負責調用定時器,繪圖,發送全局通知,並處理內存回收池。該方法按幀調用,每幀調用一次,而幀間間隔取決於兩個因素,一個是預設的幀率,默認爲60幀每秒;另外一個是每幀的計算量大小。當邏輯處理與繪圖計算量過大時,設備沒法完成每秒60次繪製,此時幀率就會下降。

三、mainLoop()方法會被定時調用,然而在不一樣的平臺下它的調用者不一樣。一般CCApplication類負責處理平臺相關的任務,其中就包含了對mainLoop()的調用。有興趣的讀者能夠對比Android、iOS與Windows Phone三個平臺下不一樣的實現,平臺相關的代碼位於引擎的"platform"目錄。

 

mainLoop()方法是定義在CCDirector中的抽象方法,它的實現位於同一個文件中的CCDisplayLinkDirector類。如今咱們來看一下它的代碼:


void CCDisplayLinkDirector::mainLoop()
? {
? if (m_bPurgeDirecotorInNextLoop)
? {
? m_bPurgeDirecotorInNextLoop = false;
? purgeDirector();
? }
? else if (! m_bInvalid)
? {
? drawScene();
? //釋放對象
? CCPoolManager::sharedPoolManager()->pop();
? }
? }

上述代碼主要包含以下3個步驟。 ?
一、判斷是否須要釋放CCDirector,若是須要,則刪除CCDirector佔用的資源。一般,遊戲結束時纔會執行這個步驟。 ?
二、調用drawScene()方法,繪製當前場景並進行其餘必要的處理。 ?
三、彈出自動回收池,使得這一幀被放入自動回收池的對象所有釋放。

因而可知,mainLoop()把內存管理之外的操做都交給了drawScene()方法,所以關鍵的步驟都在drawScene()方法之中。下面是drawScene()方法的實現:

void CCDirector::drawScene()
? {
? //計算全局幀間時間差dt
? calculateDeltaTime();
?
? if (! m_bPaused)
? {
? m_pScheduler->update(m_fDeltaTime);
? }
?
? glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);


if (m_pNextScene)
? {
? setNextScene();
? }
?
? kmGLPushMatrix();

//繪製場景
? if (m_pRunningScene)
? {
? m_pRunningScene->visit();
? }
?
? //處理通知節點
? if (m_pNotificationNode)
? {
? m_pNotificationNode->visit();
? }

if (m_bDisplayStats)
? {
? showStats();
? }
?
? if (m_pWatcherFun && m_pWatcherSender)
? {
? (*m_pWatcherFun)(m_pWatcherSender);
? }

kmGLPopMatrix();
?
? m_uTotalFrames++;
?
? //交換緩衝區
? if (m_pobOpenGLView)
? {
? m_pobOpenGLView->swapBuffers();
? }

if (m_bDisplayStats)
? {
? calculateMPF();
? }
? }


—————————————————————————————————————————————————————————
遊戲主循環(2)

咱們看到drawScene()方法內進行了許多操做,甚至包含了少許OpenGL函數。這是因爲Cocos2d-x在遊戲主循環中對引擎的細節進行了許多處理,咱們並不關心這些細節,所以咱們首先剔除掉細枝末節,整理出一個精簡版本的drawScene()方法:

void CCDirector::drawSceneSimplified()

{
? _calculate_time();
?
? if (! m_bPaused)
? m_pScheduler->update(m_fDeltaTime);
?
? if (m_pNextScene)
? setNextScene();
?
? _deal_with_opengl();

if (m_pRunningScene)
? m_pRunningScene->visit();
?
? _do_other_things();
? }
對比一下drawSceneSimplified()與drawScene()的代碼,能夠發現咱們省略掉的代碼主要用於處理OpenGL和一些細節,如計算FPS、幀間時間差等。在主循環中,咱們主要進行了如下3個操做。 ?

一、調用了定時調度器的update方法,引起定時器事件。 ?
二、若是場景須要被切換,則調用setNextStage方法,在顯示場景前切換場景。 ?
三、調用當前場景的visit方法,繪製當前場景。


—————————————————————————————————————————————————————————

CCScheduler成員 ?

通過上面的分析,咱們已經知道CCNode提供的定時器不是由它自己而是由CCScheduler管理的。所以,咱們把注意力轉移到定時調度器上。顯而易見,定時調度器應該對每個節點維護一個定時器列表,在恰當的時候就會觸發其定時事件。

打開CCScheduler類的頭文件,能夠看到它的成員。 ?

scheduleSelector  爲指定目標設置一個定時器
unscheduleSelector  取消指定目標的定時器
unscheduleAllSelectorsForTarget 取消指定目標的全部定時器(包含普通定時器與update定時器)
unscheduleAllSelectors  取消全部被CCScheduler管理的定時器,包括update定時器
scheduleUpdateForTarget  啓用指定目標的update定時器
unscheduleUpdateForTarget 取消指定目標的update定時器
pauseTarge   暫停指定目標的所有定時器
resumeTarget   恢復指定目標的所有定時器
isTargetPaused   返回一個值,表示目標是否被暫停
pauseAllTargets   暫停全部被CCScheduler管理的目標 私有字段
m_pUpdatesNegList  一個鏈表,記錄優先值小於0的update定時器
m_pUpdates0List   一個鏈表,記錄優先值爲0的update定時器
m_pUpdatesPosList  一個鏈表,記錄優先值大於0的update定時器
m_pHashForUpdates  記錄所有update定時器的散列表,便於調度器檢索定時器
m_pHashForSelectors  記錄普通定時器的散列表
調度器能夠隨時增刪或修改被註冊的定時器。具體來看,調度器將update定時器與普通定時器分別處理:當某個節點註冊update定時器時,調度器就會把節點添加到Updates容器中,爲了提升調度器效率,Cocos2d-x使用了散列表與鏈表結合的方式來保存定時器信息;當某個節點註冊普通定時器時,調度器會把回調函數和其餘信息保存到Selectors散列表中。

update方法
?
在遊戲主循環中,咱們已經見到了update方法。能夠看到,遊戲主循環會不停地調用update方法。該方法包含一個實型參數,表示兩次調用的時間間隔。在該方法中,引擎會利用兩次調用的間隔來計算什麼時候觸發定時器。 ?

update方法的實現看起來較爲複雜,而實際上它的內部可能是重複的代碼片斷,邏輯並不複雜。咱們能夠利用Cocos2d-x中精心編寫的註釋來幫助理解update方法的工做流程,相關代碼以下:


void CCScheduler::update(float dt)
? {
? m_bUpdateHashLocked = true;
?
? //a.預處理
? if (m_fTimeScale != 1.0f)
? dt *= m_fTimeScale;
?
? //b.枚舉全部的update定時器
? tListEntry *pEntry, *pTmp;

//優先級小於0的定時器
? DL_FOREACH_SAFE(m_pUpdatesNegList, pEntry, pTmp)
? if ((! pEntry->paused) && (! pEntry->markedForDeletion))
? pEntry->target->update(dt);
?
? //優先級等於0的定時器
? DL_FOREACH_SAFE(m_pUpdates0List, pEntry, pTmp)

if ((! pEntry->paused) && (! pEntry->markedForDeletion))
? pEntry->target->update(dt);

//優先級大於0的定時器
? DL_FOREACH_SAFE(m_pUpdatesPosList, pEntry, pTmp)
? if ((! pEntry->paused) && (! pEntry->markedForDeletion))
? pEntry->target->update(dt);
?
? //c.枚舉全部的普通定時器

for (tHashSelectorEntry *elt = m_pHashForSelectors; elt != NULL; )
? {
? m_pCurrentTarget = elt;
? m_bCurrentTargetSalvaged = false;
?
? if (! m_pCurrentTarget->paused)
? {
? //枚舉此節點中的全部定時器
? //timers數組可能在循環中改變,所以在此處須要當心處理
? for (elt->timerIndex = 0; elt->timerIndex < elt->timers->num;

++(elt->timerIndex))
? {

elt->currentTimer = (CCTimer*)(elt->timers->arr[elt->timerIndex]);
? elt->currentTimerSalvaged = false;
?
? elt->currentTimer->update(dt);
?
? if (elt->currentTimerSalvaged)
? {
? elt->currentTimer->release();
? }
?
? elt->currentTimer = NULL;
? }
? }
?
elt = (tHashSelectorEntry *)elt->hh.next;
?
? if (m_bCurrentTargetSalvaged && m_pCurrentTarget->timers->num == 0)
? {
? removeHashElement(m_pCurrentTarget);
? }
? }

//d.處理腳本引擎相關的事件
? if (m_pScriptHandlerEntries)
? {
? for (int i = m_pScriptHandlerEntries->count() - 1; i >= 0; i--)
? {
? CCSchedulerScriptHandlerEntry* pEntry =
? static_cast<CCSchedulerScriptHandlerEntry*>(m_pScriptHandlerEntries
? ->objectAtIndex(i));
? if (pEntry->isMarkedForDeletion())
? {
? m_pScriptHandlerEntries->removeObjectAtIndex(i);

}
? else if (!pEntry->isPaused())
? {
? pEntry->getTimer()->update(dt);
? }
? }
? }

//e.清理全部被標記了刪除記號的update方法
? //優先級小於0的定時器
? DL_FOREACH_SAFE(m_pUpdatesNegList, pEntry, pTmp)
? {
? if (pEntry->markedForDeletion)
? {
? this->removeUpdateFromHash(pEntry);
? }
? }


//優先級等於0的定時器
? DL_FOREACH_SAFE(m_pUpdates0List, pEntry, pTmp)
? {
? if (pEntry->markedForDeletion)
? {
? this->removeUpdateFromHash(pEntry);
? }
? }


//優先級大於0的定時器
? DL_FOREACH_SAFE(m_pUpdatesPosList, pEntry, pTmp)
? {
? if (pEntry->markedForDeletion)

{
? this->removeUpdateFromHash(pEntry);
? }
? }
?
? m_bUpdateHashLocked = false;
?
? m_pCurrentTarget = NULL;
}

—————————————————————————————————————————————————————————
定時調度器(3)

藉助註釋,可以看出update方法的流程大體以下所示。
?
一、參數dt乘以一個縮放係數,以改變遊戲全局的速度,其中縮放係數能夠由CCScheduler的TimeScale屬性設置。 ?
二、分別枚舉優先級小於0、等於0、大於0的update定時器。若是定時器沒有暫停,也沒有被標記爲即將刪除,則觸發定時器。 ?
三、枚舉全部註冊過普通定時器的節點,再枚舉該節點的定時器,調用定時器的更新方法,從而決定是否觸發該定時器。 ?
四、咱們暫不關心腳本引擎相關的處理。 ?
五、再次枚舉優先級小於0、等於0、大於0的update定時器,移除前幾個步驟中被標記了刪除記號的定時器。
六、對於update定時器來講,每一節點只可能註冊一個定時器,所以調度器中存儲定時器數據的結構體_listEntry主要保存了註冊者與優先級。對於普通定時器來講,每個節點能夠註冊多個定時器,引擎使用回調函數(選擇器)來區分同一節點下注冊的不一樣定時器。調度器爲每個定時器建立了一個CCTimer對象,它記錄了定時器的目標、回調函數、觸發週期、重複觸發仍是僅觸發一次等屬性。 ?
七、CCTimer也提供了update方法,它的名字和參數都與CCScheduler的update方法同樣,並且它們也都須要被定時調用。不一樣的是,CCTimer的update方法會把每一次調用時接收的時間間隔dt積累下來,若是經歷的時間達到了週期,就會引起定時器的定時事件。第一次引起了定時事件後,若是是僅觸發一次的定時器,則update方法會停止,不然定時器會從新計時,從而反覆地觸發定時事件。
八、回到CCScheduler的update方法上來。在步驟c中,程序首先枚舉了每個註冊過定時器的對象,而後再枚舉對象中定時器對應的CCTimer對象,調用CCTimer對象的update方法來更新定時器狀態,以便觸發定時事件。 ?
九、至此,咱們能夠看到事件驅動的普通定時器調用順序爲:系統的時間事件驅動遊戲主循環,遊戲主循環調用CCScheduler的update方法,CCScheduler調用普通定時器對應的CCTimer對象的update方法,CCTimer類的update方法調用定時器對應的回調函數。對於update定時器,調用順序更爲簡單,所以前面僅列出了普通定時器的調用順序。 ?
十、同時,咱們也能夠看到,在定時器被觸發的時刻,CCScheduler類的update方法正在迭代之中,開發者徹底可能在定時器事件中啓用或中止其餘定時器(如圖3-8所示)。不過,這麼作會致使update方法中的迭代被破壞。Cocos2d-x的設計已經考慮到了這個問題,採用了一些技巧避免迭代被破壞。例如,update定時器被刪除時,不會直接刪除,而是標記爲將要刪除,在定時器迭代完畢後再清理被標記的定時器,這樣便可保證迭代的正確性。
CCSchedule::update(float dt)
         --------
for each CCTimer t in |timers|<__________
         --------          |
{     |
 CCTime::update(float dt)        |
 _____________________________ |
 |GameScene::tick(float dt)  |   |
 |self->unschedule(selector1)|___|
 -----------------------------
在迭代中修改容器

Cocos2d-x的設計使得不少離散在各處的代碼經過事件聯繫起來,在每一幀中起做用。基於事件驅動的遊戲框架易於掌握,使用靈活,並且全部事件串行地在同一線程中執行,不會出現線程同步的問題。在後面更深刻的討論中,讀者將會看到更多有趣的現象。


定時器:
分爲update定時器與schedule定時器,前者每一幀觸發一次,然後者能夠指定觸發間隔。定時器由定時調度器(CCScheduler)控制,每一個定時器互不干擾,串行執行。


動做———————————————————————————————————————————————————————
CCEaseBounceOut

http://blog.csdn.net/dayuqi/article/details/8121813貝塞爾曲線動做

值得注意的是,一個CCAction只能使用一次,這是由於動做對象不只描述了動做,還保存了這個動做持續過程當中不斷改變的一些中間參數。對於須要反覆使用的動做對象,能夠經過copy方法複製使用。

由CCFiniteTimeAction派生出的兩個主要類分別是瞬時動做(CCActionInstant)和持續性動做(CCActionInterval)

瞬時動做
更準確地說,這類動做是在下一幀會馬上執行並完成的動做,如設定位置、設定縮放等。這些動做本來能夠經過簡單地對CCNode賦值完成,可是把它們包裝爲動做後,能夠方便地與其餘動做類組合爲複雜動做。(如回調函數動做)

持續動做—————————————————————
貝塞爾曲線CCBezierTo和CCBezierBy:

使節點進行曲線運動,運動的軌跡由貝塞爾曲線描述。貝塞爾曲線是描述任意曲線的有力工具,在許多軟件(如Adobe Photoshop)中,鋼筆工具就是貝塞爾曲線的應用。實際上,在《捕魚達人》遊戲中,爲了控制魚的遊動,咱們就用到了貝塞爾曲線。

每一條貝塞爾曲線都包含一個起點和一個終點。在一條曲線中,起點和終點都各自包含一個控制點,而控制點到端點的連線稱做控制線。控制線決定了從端點發出的曲線的形狀,包含角度和長度兩個參數:角度決定了它所控制的曲線的方向,即這段曲線在這一控制點的切線方向;長度控制曲線的曲率。控制線越長,它所控制的曲線離控制線越近。

任意一段曲線均可以由一段或幾段相連的貝塞爾曲線組成,所以咱們只需考慮一段貝塞爾曲線應該如何描述便可
使用時咱們要先建立ccBezierConfig結構體,設置好終點endPosition以及兩個控制點controlPoint_1和controlPoint_2後,再把結構體傳入CCBezierTo或CCBezierBy的初始化方法中:
ccBezierConfig bezier;
bezier.controlPoint_1 = ccp(20, 150);
bezier.controlPoint_2 = ccp(200, 30);
bezier.endPosition = ccp(160, 30);
CFiniteTimeAction * beizerAction = CCBezierTo::create(actualDuration / 4, bezier);


CCTintTo和CCTintBy:設置色調變化。這個動做較爲少用


CCSequence提供了一個動做隊列,它會順序執行一系列動做,例如魚游出屏幕外後須要調用回調函數,捕到魚後顯示金幣數量,通過一段時間再讓金幣數量消失,等等。


在實現CCSequence和CCSpawn兩個組合動做類時,有一個很是有趣的細節:成員變量中並無定義一個可變長的容器來容納每個動做系列,而是定義了m_pOne和m_pTwo兩個動做成員變量。採用這種遞歸的方式,而不是直接使用容器來定義組合動做


延時(CCDelayTime) ?
CCDelayTime是一個"什麼都不作"的動做,相似於音樂中的休止符,用來表示動做序列裏一段空白期,經過佔位的方式將不一樣的動做段串接在一塊兒。實際上,這與一個定時期實現的延遲沒有區別,但相比之下,使用CCDelayTime動做來延時就能夠方便地利用動做序列把一套動做鏈接在一塊兒。

CCSpeed ?
CCSpeed用於線性地改變某個動做的速度,所以,能夠實現成倍地快放或慢放功能。爲了改變一個動做的速度,首先須要將目標動做包裝到CCSpeed動做中

使用repeat動做建立了一個CCSpeed變速動做。create初始化方法中的兩個參數分別爲目標動做與變速比率。設置變速比率爲1,目標動做的速度將不會改變。
下面的代碼將會把上面設置的動畫速度變爲原來的兩倍:
speed->setSpeed(2.0f);

CCActionEase ?
雖然使用CCSpeed可以改變更做的速度,然而它只能按比例改變目標動做的速度。若是咱們要實現動做由快到慢、速度隨時間改變的變速運動,須要不停地修改它的speed屬性才能實現,顯然這是一個很煩瑣的方法。下面將要介紹的CCActionEase系列動做經過使用內置的多種自動速度變化來解決這一問題。
CCActionEase系列包含15個動做,它們能夠被歸納爲5類動做:指數緩衝、Sine緩衝、彈性緩衝、跳躍緩衝和回震緩衝。每一類動做都有3個不一樣時期的變換:In、Out和InOut

對於曲線運動來講,魚的方向並無精確地吻合遊動軌跡。
若是物體沿着一條曲線移動,那麼物體在某一點的瞬時速度方向必定是該點切線的正方向
所以,咱們只須要根據兩幀中魚的位置差計算出魚前進的方向


自定義動做————————————————————————————————————————————————————

CCAction包含兩個重要的方法:step與update。step方法會在每一幀動做更新時觸發,該方法接受一個表示調用時間間隔的參數dt,dt的積累即爲動做運行的總時間。引擎利用積累時間來計算動做運行的進度(一個從0到1的實數),並調用update方法更新動做。update方法是CCAction的核心,它由step方法調用,接受一個表示動做進度的參數,每個動做都須要利用進度值改變目標節點的屬性或執行其餘指令。自定義動做只須要從這兩個方法入手便可,咱們一般只須要修改update方法就能夠實現簡單的動做。


下面咱們編寫一個繼承於CCAction的CCRotateAction動做。如同複合動做與變速動做同樣,它會把另外一個動做包裝起來,在執行被包裝動做的同時,設置精靈的方向。爲此,咱們須要在每一幀?記錄上一幀精靈的位置,而後再根據精靈兩幀的位移肯定精靈的方向。因爲咱們必須在CCRotateAction執行的同時運行被包含的目標動做,因此咱們須要在step方法中調用目標動做的step方法。下面咱們來看CCRotateAction的實現。

"RotateWithAction.h"中的定義以下:
?
class RotateWithAction : public CCActionInterval
{
? public:
? CCObject* copyWithZone(CCZone* pZone);
? ~RotateWithAction();
? static RotateWithAction* create(CCActionInterval * action);
? virtual void startWithTarget(CCNode* pTarget);
? bool initWithAction(CCActionInterval* pAction);
? bool isDone();
? void step(ccTime dt);

protected:
? void RotateWithAction::setInnerAction(CCActionInterval* pAction);
?
? CCNode* pInnerTarget;
? CCActionInterval* pInnerAction;
?
};

"RotateWithAction.cpp"中的實現以下:

RotateWithAction::~RotateWithAction()
{

CC_SAFE_RELEASE(pInnerAction);
? }
?
? RotateWithAction* RotateWithAction::create(CCActionInterval* pAction)
? {
RotateWithAction* action = new RotateWithAction();
? if (action && action->initWithAction(pAction))
? {
? action->autorelease();
? return action;
? }
? CC_SAFE_DELETE(action);
? return NULL;
? }

bool RotateWithAction::initWithAction(CCActionInterval* pAction)
? {
? pAction->retain();
? pInnerAction = pAction;
? return true;

}
?
? void RotateWithAction::startWithTarget(CCNode* pTarget)
? {
? pInnerTarget = pTarget;
? CCAction::startWithTarget(pTarget);
? pInnerAction->startWithTarget(pTarget);
? }

bool RotateWithAction::isDone()
? {
? return pInnerAction->isDone();
? }
?
? void RotateWithAction::step(ccTime dt)
? {
? CCPoint prePos = pInnerTarget->getPosition();
? pInnerAction->step(dt);
CCPoint curPos = pInnerTarget->getPosition();
?
? float tan = -(curPos.y - prePos.y) / (curPos.x - prePos.x);

float degree = atan(tan);
? degreedegree = degree / 3.14159f * 180;
?
? pInnerTarget->setRotation(degree);
? }
?
? void RotateWithAction::setInnerAction(CCActionInterval* pAction)
{
? if (pInnerAction != pAction)
? {
? CC_SAFE_RELEASE(pInnerAction);
? pInnerAction = pAction;
? CC_SAFE_RETAIN(pInnerAction);
? }
? }


CCObject* RotateWithAction::copyWithZone(CCZone* pZone)
? {
? CCZone* pNewZone = NULL;
? RotateWithAction* pCopy = NULL;
? if(pZone && pZone->m_pCopyObject)

{
? pCopy = (RotateWithAction*)(pZone->m_pCopyObject);
? }
? else
? {
? pCopy = new RotateWithAction();
? pZone = pNewZone = new CCZone(pCopy);
? }

CCActionInterval::copyWithZone(pZone);
?
? pCopy->initWithAction(dynamic_cast<CCActionInterval*>
? (pInnerAction->copy()->autorelease()));
?
? CC_SAFE_DELETE(pNewZone);
? return pCopy;
? }

也許有的讀者已經有了疑問,step方法與update方法均可以作到每一幀判斷一次方向,爲何選擇重載step方法而不是update方法呢?這是由於引擎在step方法中對動做對象的內部成員進行了更新,更新後纔會由此方法調用update方法來更新目標節點。在方向追蹤的動做中,咱們除了在每一幀判斷方向,還必須同步執行被包裝的動做。這就須要咱們調用被包裝動做的step方法,以保證對象可以被完整地更新。

如今,咱們已經不須要使用4.6節介紹的CCSpawn來實現蹩腳的方向追蹤效果了,只要把須要追蹤方向的動做傳遞給CCRotateAction,便可獲得一個自動改變魚方向的智能動做。

 

咱們的菜單收放效果就很好地印證了這個結論。菜單的所有收放動做效果造成了一個比較長且單一的運行軌跡,因此咱們不妨爲動做添加一些變速效果,將玩家有限的注意力集中到咱們但願玩家關注的效果上。 ?
進場動做:由快到慢,快速進入後緩慢停下,在中止前給玩家足夠的視覺時間分辨清楚進入的圖像。 ?
出場動做:先慢後快,展現了出場趨勢和方向後快速移出屏幕,不拖泥帶水。 ?
這個變速效果就很天然地交給前面提到的CCEase系列動做實現了。針對具體的需求,咱們選擇了CCEaseExponential動做來實現變速效果。


Cocos2d-x動做原理——————————

繼承自CCAction的CCFiniteTimeAction主要新增了一個用於保存該動做總的完成時間的成員變量:ccTime m_fDuration。 ? 對於CCFiniteTimeAction的兩個子類CCActionInstant和CCActionInterval,前者沒有新增任何函數和變量,然後者增長了兩個成員變量--ccTime m_elapsed和bool m_bFirstTick,其中m_elapsed是從動做開始起逝去的時間,而m_bFirstTick是一個控制變量,在後面的分析中,咱們將看到它的做用。

動做的更新————

一、CCNode調用runAction  —— CCActionManager會將新的CCAction和對應的目標節點添加到其管理的動做表中
(startWithTarget(CCNode* pTarget)來綁定
如 CCActionInterval CCFiniteTimeAction::startWithTarget(pTarget);
? m_elapsed = 0.0f;
? m_bFirstTick = true;

二、每一幀刷新屏幕時,系統都會在CCActionManager中遍歷其動做表中的每個動做,並調用該動做的step(ccTimedt)方法。step方法主要負責計算m_elapsed的值,並調用update(float time)方法
void CCActionInterval::step(float dt)
? {
? if (m_bFirstTick)
? {
? m_bFirstTick = false;
? m_elapsed = 0;
? }
? else
? {
? m_elapsed += dt;
? }
?
? this->update(MAX (0,MIN(1, m_elapsed / MAX(m_fDuration, FLT_EPSILON))));
? }

傳入update方法的time參數表示逝去的時間與動做完成須要的時間的比值,是介於0和1之間的一個數,即動做完成的百分比。 ?


CCActionInterval並無進一步實現update方法。下面咱們繼續以繼承自CCAction- Interval的CCRotateTo動做的update方法爲例,分析update函數是如何實現的,其實現代碼以下:

void CCRotateTo::update(float time)
? {
? if (m_pTarget)
? {
? m_pTarget->setRotation(m_fStartAngle + m_fDiffAngle * time);
? }
? }

看到這裏,咱們已經能看出Cocos2d-x的動做機制的整個工做流程了。在CCRotateTo中,最終完成的操做是修改目標節點的Rotation屬性值,更新該目標節點的旋轉屬性值。

三、最後,在每一幀刷新結束後,在CCActionManager類的update方法中都會檢查動做隊列中每個動做的isDone函數是否返回true。若是返回true,則動做已完成,將其從隊列中刪除。isDone函數的代碼以下:
? bool CCActionInterval::isDone(void)
? {
? return m_elapsed >= m_fDuration;
? }
對於不一樣的動做類,雖然總體流程大體都是先調用step方法,而後按照各個動做的具體定義來更新目標節點的屬性,可是不一樣動做的具體實現會有所不一樣。例如,CCRepeatForever動做的isDone函數始終返回false,由於它是永遠在執行的動做;又如CCActionInstant及其子類的step函數中,向update傳遞的參數值始終是1,由於瞬時動做會在下一幀刷新後完成,不須要屢次執行update。


CCActionManager的工做原理————


下面的代碼是CCDirector::init()方法中的一部分:
? //動做管理器
? m_pActionManager = new CCActionManager();
   m_pScheduler->scheduleUpdateForTarget(m_pActionManager, kCCPrioritySystem, false);

CCScheduler在每一幀更新時,都會觸發CCActionManager註冊的update方法。與調度器CCScheduler相似的一點是,爲了防止動做調度過程當中所遍歷的表被修改,Cocos2d-x對動做的刪除進行了仔細地處理,保證任何狀況下均可以安全地刪除動做:
void CCActionManager::update(float dt)
? {
? //枚舉動做表中的每個目標節點
? for (tHashElement *elt = m_pTargets; elt != NULL; )
? {
? m_pCurrentTarget = elt;
? m_bCurrentTargetSalvaged = false;
if (! m_pCurrentTarget->paused)
? {
? //枚舉目標節點對應的每個動做
? //actions數組可能會在循環中被修改,所以須要謹慎處理
? for (m_pCurrentTarget->actionIndex = 0;
? m_pCurrentTarget->actionIndex < m_pCurrentTarget->actions->num;
m_pCurrentTarget->actionIndex++)
? {
? m_pCurrentTarget->currentAction =
? (CCAction*)m_pCurrentTarget->actions
? ->arr[m_pCurrentTarget->actionIndex];
if (m_pCurrentTarget->currentAction == NULL)
? {
? continue;
? }
m_pCurrentTarget->currentActionSalvaged = false;
?
? m_pCurrentTarget->currentAction->step(dt); //觸發動做更新
?
? if (m_pCurrentTarget->currentActionSalvaged)
{
? m_pCurrentTarget->currentAction->release();
? }
? else if (m_pCurrentTarget->currentAction->isDone())
{
? m_pCurrentTarget->currentAction->stop();
?
? CCAction *pAction = m_pCurrentTarget->currentAction;

m_pCurrentTarget->currentAction = NULL;

removeAction(pAction);
? }
? m_pCurrentTarget->currentAction = NULL;
? }
? }
elt = (tHashElement*)(elt->hh.next);
?
? if (m_bCurrentTargetSalvaged && m_pCurrentTarget->actions->num == 0)
? {
? deleteHashElement(m_pCurrentTarget);
? }?
}
?
? m_pCurrentTarget = NULL;
? }

 

動畫——————

一般,較爲簡單的動畫能夠利用Flash工具製做出來,而更爲複雜與細膩的動畫能夠利用三維建模軟件逐幀渲染,或徹底手動繪製。

考慮到製做成本以及回放成本,若是沒有必要,咱們通常不在遊戲中大規模使用動畫。


動畫幀類CCAni- mationFrame一樣包含兩個屬性,其一是對一個框幀的引用,其二是幀的延時。一個Cocos2d-x的動畫CCAnimation是對一個動畫的描述,它包含顯示動畫所須要的動畫幀。對於勻速播放的幀動畫,只需設置全部幀的延時相同便可。 ?

咱們使用CCAnimation描述一個動畫,而精靈顯示動畫的動做則是一個CCAnimate對象

動畫與動畫動做的關係就如同CD光盤與CD播放機的關係同樣--前者記錄了動畫的內容,然後者是播放動畫的工具


觸摸———————————————————————————————————————————————————————

利用層來實現觸摸十分簡便,然而只要玩家觸摸了屏幕,全部響應觸摸事件的層都會被觸發。當層的數量不少時,維護多個層的觸摸事件就成了一件複雜的事情。所以,在實際開發中,咱們一般單獨創建一個觸摸層。用觸摸層來接收用戶輸入事件,並根據須要通知遊戲中的其餘部件來響應觸摸事件。

兩種Cocos2d-x觸摸事件(1)——————————

CCTouchDelegate  觸摸事件委託,就是系統捕捉到觸摸事件後交由它或者它的子類處理,因此咱們在處理觸屏事件時,必須得繼承它。它封裝了下面這些處理觸屏事件的函數:
子類:
CCStandardTouchDelegate用於處理多點觸摸;CCTargetedTouchDelegate用於處理單點觸摸。

CCTouchDelegate能夠處理多觸摸 和 單觸摸

CCStandardTouchDelegate
virtual void ccTouchesBegan(CCSet *pTouches, CCEvent *pEvent) //多

CCTargetedTouchDelegate
virtual bool ccTouchBegan(CCTouch *pTouch, CCEvent *pEvent)   //單

 

CCTouchDispatcher——觸摸調度器(分發器),

該類用單例模式處理全部的觸摸事件(此單例能夠經過導演得到)
class CC_DLL CCTouchDispatcher 類內函數:

//熟悉 布爾類型,用於獲取或設置事件分發器是否工做
DispatchEvents
?

bool isDispatchEvents(void);//dispatch 調度
   
void setDispatchEvents(bool bDispatchEvents);

//Adds a standard touch delegate to the dispatcher's list.
//對應地存入m_pStandardHandlers或m_pTargetedHandlers容器中
void addStandardDelegate(CCTouchDelegate *pDelegate, int nPriority);//delegate 協議
void addTargetedDelegate(CCTouchDelegate *pDelegate, int nPriority, bool bSwallowsTouches);//優先級

Swallow 吞 只要前面一層touchbegan 返回true 則截取 不然不截取

優先級數值越低,越先響應
//CCMenu的默認優先級是-128,CCScrollView的默認優先級是0


void removeDelegate(CCTouchDelegate *pDelegate);

   
void removeAllDelegates(void);

void setPriority(int nPriority, CCTouchDelegate *pDelegate);

virtual void touchesBegan(CCSet* touches, CCEvent* pEvent);
  
virtual void touchesMoved(CCSet* touches, CCEvent* pEvent);
   
virtual void touchesEnded(CCSet* touches, CCEvent* pEvent);
   
virtual void touchesCancelled(CCSet* touches, CCEvent* pEvent);

一、該調度器將事件(經過4個touch函數得到)分發給註冊過的TouchHandlers

二、首先,調度器發送接收到的觸摸給指定的目標觸摸句柄,這些觸摸事件能夠被該目標觸摸句柄截獲。若是有剩餘的觸摸事件,這些觸摸事件將被髮送給標準觸摸句柄

使用方法1 :
一、經過導演獲得分發器
二、加入分調度方法 addStandardDelegate  addTargetedDelegate 加入觸摸協議

層封裝了 能夠用如下方法2:
一、註冊觸摸句柄
void registerScriptTouchHandler(int nHandler, bool bIsMultiTouches = false, int nPriority = INT_MIN, bool bSwallowsTouches = false);  //id  是否多觸電(默認單) 優先級(默認0)是否吞噬(默認否)

二、 void setTouchEnabled(bool value);方法

——其實也沒變得簡單 能夠一直用方法一

兩種Cocos2d-x觸摸事件(2) ?——————
只要事件分發器接收到用戶的觸摸事件,就會分發給全部的訂閱者

觸摸分發器原理————

觸摸分發器會利用系統的API獲取觸摸事件,而後把事件分發給遊戲中接收觸摸事件的對象。

事件分發器從系統接收到了觸摸事件以後還須要逐一分發。分發事件的相關代碼主要集中在touches方法之中。

分發過程遵循如下的規則——————
對於觸摸集合中的每一個觸摸點,按照優先級詢問每個註冊到分發器的對象。

被吞噬的點將當即移出觸摸集合,再也不分發給後續目標(包括註冊了標準觸摸事件的目標)

觸摸中的陷阱——————
一、觸摸分發器和引擎中的繪圖是相互獨立的,因此並不關心觸摸代理是否處於屏幕上。而CCLayer也僅僅會在切換場景時將本身從分發器中移除,因此同場景內手動切換CCLayer的時候,也須要注意禁用觸摸來從分發器移除本身。

二、另外一個陷阱出自CCTargetedTouchDelegate。儘管每次只傳入一個觸摸點,也只有在開始階段被聲明過的觸摸點後續纔會傳入,可是這並不意味着只會接收一個觸摸點:只要被聲明過的觸摸點都會傳入,並且多是亂序的。所以,一個良好的習慣是,若是使用CCTargeted- TouchDelegate,那麼只聲明一個觸摸,針對一個觸摸做處理便可。(沒懂)


CCMenuItemToggle
能夠將任意的
CCMenuItem
封裝進去,做爲一個按鈕式的開關


文本輸入框———————————————————————————————————————————————————

 


Cocos2d-x中的粒子系統(1)———————————————————————————————————————————

暫時拋開粒子效果文件Plist不談,若是咱們已經擁有一個粒子效果文件,就能夠利用CCParticleSystem的初始化方法直接從文件中導入一個粒子效果,相關代碼以下:

bool initWithFile(const char *plistFile)
static CCParticleSystem* create(const char *plistFile)

Plist文件實質上是一個XML文件,咱們能夠利用任何文本編輯器來建立或修改。

Cocos2d-x中的粒子系統看似簡單,實際上倒是一個十分強大的特效工具。使用得當的粒子系統能夠實現許多夢幻般的特效(漫天雪花,雨天 用編輯器嘗試作出以上兩種或者更多效果)。


瓦片地圖———————————————————————————————————————————

大型地圖——————————————
超過屏幕大小的地圖,玩家能夠像在即時戰略遊戲(如《魔獸爭霸》)中同樣在地圖中滾動遊戲畫面

不管是即時戰略、角色扮演,仍是模擬經營,一般都須要一張很是大的地圖來展示一個靈活多變的世界。

TileMap要求每一個瓦片佔據地圖上一個四邊形或六邊形的區域。


TileMap地圖支持3種不一樣的視圖:正交視圖(orthogonal view,瓦片水平垂直排列)、六邊形視圖(hexagonal view,六邊形瓦片緊密鏈接)和等軸視圖(isometric view,45度斜視排列)。

TileMap中的層級關係和Cocos2d-x中的是相似的,地圖能夠包含多個不一樣的圖層,每一個圖層內都放置瓦片,同層內的瓦片間平鋪排列,而高一層的瓦片能夠遮蓋低一層的瓦片。與Cocos2d-x不一樣的是,TileMap的座標系的原點位於左上角,以一個瓦片爲單位,

導入遊戲——————————————
Cocos2d-x爲咱們提供了CCTMXTileMap和CCTMXLayer兩個類來處理瓦片地圖。其中,CCTMXTileMap表明一個完整的瓦片地圖,它負責地圖文件的載入、管理以及呈現。與其餘遊戲元素相同,CCTMXTileMap也繼承自CCNode,所以可像層同樣把它添加到遊戲場景中。CCTMXLayer表明一個瓦片地圖中的圖層,能夠從圖層對象獲取圖層信息,如某一點是否存在對象組或屬性。CCTMXLayer隸屬於CCTMXTileMap,所以一般不須要咱們手動管理圖層。

此外,CCTMXTileMap還提供了一些操做地圖的圖層或地圖對象的方法,能夠經過關鍵字獲取層、對象組或屬性,這也爲咱們操做地圖提供了便利。

魚在水草間穿梭的層次效果——————————————
咱們把靠近屏幕的水草置於glass1圖層中,而遠離屏幕的水草置於glass0圖層中,它們的Z軸順序值分別設置爲3與4。此時,當魚的Z軸順序值爲2時,它會被全部水草遮擋;爲3時,會被部分水草遮擋;爲4時,不會被任何水草遮擋。經過改變魚羣的Z軸順序值,咱們實現了魚在水草中層次感地穿梭的效果,所以咱們能夠實時根據魚的位置來設置魚的Z軸順序值。


OPENGL——————————————————————————————————————————————————————

狀態機————————————
OpenGL是一個基於狀態的繪圖模型,咱們把這種模型稱爲狀態機

在此模型下,OpenGL時刻維護着一組狀態,這組狀態涵蓋了一切繪圖參數,如即將繪製的多邊形、填充顏色、紋理、混合模式和當前的座標系等。爲了正確地繪製圖形,咱們須要把OpenGL設置到合適的狀態,而後調用繪圖指令。

狀態機優點:
一、其中許多參數並不頻繁改變,所以也沒有必要每次都從新設置。OpenGL把全部的參數做爲狀態來保存,若是沒有設置新的參數,則會一直採用當前的狀態來繪圖
二、另外一個優點在於,咱們能夠把繪圖設備人爲地分爲兩個部分:"服務器端",負責具體的繪製渲染;"客戶端",負責向服務器端發送繪圖指令。(cpu gpu 遊戲服務器 遊戲客戶端)所以咱們也須要盡力避免在客戶端與服務器端傳遞沒必要要的數據。 ?

 

OpenGL提供了許多改變繪圖狀態的函數
GLenum類型用來表示OpenGL的狀態量。後面咱們將會看到,所有狀態的列表定義在"gl2.h"頭文件中。不一樣的繪圖效果須要不一樣的支持狀態,默認狀況下,Cocos2d-x只會開啓固定的幾種狀態,必要的時候必須本身主動開啓所需狀態,使用完畢後主動禁止
爲了裁剪渲染區域,就須要設置GL_SCISSOR_TEST狀態
void Spin888::visit()
{
 glEnable(GL_SCISSOR_TEST);
 glScissor(x,y,w,h);//x, y, w, h   左下0 0點
 CCLayer::visit();
 glDisable(GL_SCISSOR_TEST);
}

實際上,從"gl2.h"頭文件中就能夠看出,OpenGL是一個很是接近底層的接口標準,核心部分只包括了約170個函數和約300個常量

可編程着色器——————————
利用可編程着色器,開發者能夠在渲染過程當中自由控制頂點和片斷處理採用的算法,以便實現更加炫麗的渲染效果。可編程着色器主要包含頂點着色器和片斷着色器,其中前者負責對頂點進行幾何變換以及光照計算,後者負責處理光柵化獲得的像素以及紋理。

繪圖————————————
void HelloWorld::draw()
? {
? //頂點數據
? static GLfloat vertex[] = { //頂點座標:x,y,z
? 0.0f, 0.0f, 0.0f, //左下
? 200.0f, 0.0f, 0.0f, //右下
? 0.0f, 200.0f, 0.0f, //左上
? 200.0f, 200.0f, 0.0f, //右上
? };
? static GLfloat coord[] = { //紋理座標:s,t
? 0.0f, 1.0f,
? 1.0f, 1.0f,
? 0.0f, 0.0f,
? 1.0f, 0.0f,
? };

static GLfloat color[] = { //顏色:紅色、藍色、綠色、不透明度
? 1.0f, 1.0f, 1.0f, 1.0f,
? 1.0f, 1.0f, 1.0f, 1.0f,
? 1.0f, 1.0f, 1.0f, 1.0f,
? 1.0f, 1.0f, 1.0f, 1.0f,
? };
?
? //初始化紋理
? static CCTexture2D* texture2d = NULL;
? if(!texture2d) {
? texture2d = CCTextureCache::sharedTextureCache()->addImage("HelloWorld.png");
? coord[2] = coord[6] = texture2d->getMaxS();
? coord[1] = coord[3] = texture2d->getMaxT();
? }
?
? //設置着色器
? ccGLEnableVertexAttribs(kCCVertexAttribFlag_PosColorTex);
? texture2d->getShaderProgram()->use();
? texture2d->getShaderProgram()->setUniformForModelViewProjectionMatrix();

//綁定紋理
? glBindTexture(GL_TEXTURE_2D, texture2d->getName());
?
? //設置頂點數組
? glVertexAttribPointer(kCCVertexAttrib_Position, 3, GL_FLOAT, GL_FALSE, 0, vertex);
? glVertexAttribPointer(kCCVertexAttrib_TexCoords, 2, GL_FLOAT, GL_FALSE, 0, coord);
? glVertexAttribPointer(kCCVertexAttrib_Color, 4, GL_FLOAT, GL_FALSE, 0, color);
?
? //繪圖
? glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}

draw大體上能夠分爲3個部分--數據部分、初始化紋理和繪圖,它繪製了一個帶紋理的矩形。事實上,咱們也能夠經過繪製一個"三角形帶(triangle stripe)"來繪製。

代碼的第一部分是數據部分,在這一部分中咱們聲明瞭3個靜態數組,它們分別是vertex、coord和color,對應了三角形帶中共4個頂點的頂點座標、紋理座標和頂點顏色。每一個數組均按照左下、右下、左上、右上的順序來存儲。

vertex:共4個頂點,每一個頂點包含x、y和z三個份量,所以頂點座標數組共有12個值。在本例中,矩形位於屏幕左下角,大小爲200×200。

coord:包含s和t(橫座標和縱座標)兩個份量,所以共有8個值,每一個份量的取值範圍是0到1,須要根據紋理的屬性肯定取值。

color:包含r、g、b和a(紅色、綠色、藍色和不透明度)4個份量,所以共有16個值,每一個份量的取值範圍是0~1。把顏色值設爲純白(1, 1, 1, 1),則會顯示紋理原來的顏色。

第二部分是初始化紋理。利用CCTextureCache類能夠方便地從文件中載入一個紋理,獲取紋理尺寸,以及獲取紋理在OpenGL中的編號。在紋理沒有被初始化時,咱們首先使用CCTextureCache::addImage方法載入一個圖片,把返回的CCTexture2D對象保存下來,並使用紋理的屬性設置4個頂點的紋理座標。對於單個紋理的圖片,只須要按照上面代碼中的方法設置紋理座標便可

最後一部分是繪製圖片。繪製圖片的步驟能夠簡述爲:綁定紋理、設置頂點數組和繪圖。綁定紋理是指把一個曾經載入的紋理當作當前紋理,今後繪製出來的多邊形都使用此紋理。設置頂點數組是指爲OpenGL指定第一步準備好的頂點座標數組、紋理座標數組以及頂點顏色數組。繪圖則是最終通知OpenGL如何利用剛纔提供的信息進行繪圖,並實際把圖形繪製出來。在這個過程當中,咱們能夠看到最重要的一個函數爲glDrawArrays(GLenum mode, GLint first, GLsizei count),其中mode指定將要繪製何種圖形,first表示前面數組中起始頂點的下標,count表示即將繪製的圖形頂點數量。


矩陣與變換————————
在計算機中,座標變換是經過矩陣乘法實現的,用向量表示座標,矩陣表示變換形式,則變換後的頂點座標能夠用向量與矩陣的乘法來表示。使用矩陣乘法的優勢在於,計算機(包括移動設備)的圖形硬件一般對矩陣乘法進行了大量優化,從而大大提升了運算效率。


點、向量與矩陣 ?

在計算機中,一般不直接使用與點維度數量同樣的向量來表示一個點,由於這樣就沒法利用矩陣乘法來對點進行平移等操做了。所以,在計算機圖形學中,一般採用齊次座標來表示一個頂點。具體地說,齊次座標系中每個點的維度比頂點維度多1,多出的一個維度值爲1。對於任何三維中的頂點(x, y, z),它在齊次座標系中的向量爲[x, y, z, 1],例如,空間中的(1.2, 5, 10)對應的向量爲[1.2, 5, 10, 1]。
具體使用 283p


//Cocos2d-x 2.0(OpenGL ES 2.0)
kmGLScalef(0.8f, 0.8f, 0.8f); //乘上縮放矩陣
kmGLTranslatef(1.0f, 2.0f, 3.0f); //乘上平移矩陣
kmGLScalef(2.5f, 2.5f, 2.5f); //乘上縮放矩陣
DrawObject(); //繪製任意圖形

Cocos2d-x 2.0中矩陣函數的替代函數
OpenGL ES 1.0函數      替代函數        描述
glPushMatrix  kmGLPushMatrix  把矩陣壓棧
glPopMatrix  kmGLPopMatrix  從矩陣棧中彈出
glMatrixMode  kmGLMatrixMode  設置當前矩陣模式
glLoadIdentity  kmGLLoadIdentity 把當前矩陣置爲單位矩陣
glLoadMatrix  kmGLLoadMatrix  設置當前矩陣的值
glMultMatrix  kmGLMultMatrix  右乘一個矩陣
glTranslatef  kmGLTranslatef  右乘一個平移矩陣
glRotatef  kmGLRotatef  右乘一個旋轉矩陣
glScalef  kmGLScalef  右乘一個縮放矩陣


重寫精靈的draw 中畫邊框部分
//畫邊框
void MyImage::drawBorder(){
    ccDrawColor4B(0,255,0,255);
    CCSize s = this->getTextureRect().size;
    CCPoint offsetPix = this->getOffsetPosition();
    CCPoint vertices[4] = {
        ccp(offsetPix.x,offsetPix.y), ccp(offsetPix.x+s.width,offsetPix.y),
        ccp(offsetPix.x+s.width,offsetPix.y+s.height), ccp(offsetPix.x,offsetPix.y+s.height)
    };
    ccDrawPoly(vertices, 4, true);
}

void CCSprite::draw(void)
? {
? //1. 初始準備
?
? CC_PROFILER_START_CATEGORY(kCCProfilerCategorySprite, "CCSprite - draw");
? CCAssert(!m_pobBatchNode, "If CCSprite is being rendered by CCSpriteBatchNode,
? CCSprite#draw SHOULD NOT be called");
? CC_NODE_DRAW_SETUP();//CC_NODE_DRAW_SETUP宏函數用於準備繪製相關環境;
?
? //2. 顏色混合函數
?
? ccGLBlendFunc(m_sBlendFunc.src, m_sBlendFunc.dst);
/***************************************************
OpenGL 會把源顏色和目標顏色各自取出,並乘以一個係數(源顏色乘以的係數稱爲「源因子」,目標顏色乘以的係數稱爲「目標因子」),而後相加,這樣就獲得了新的顏 色。glBlendFunc有兩個參數,前者表示源因子,後者表示目標因子。
GL_ZERO: 表示使用0.0做爲因子,實際上至關於不使用這種顏色參與混合運算。

GL_ONE: 表示使用1.0做爲因子,實際上至關於徹底的使用了這種顏色參與混合運算。

GL_SRC_ALPHA:表示使用源顏色的alpha值來做爲因子。

GL_DST_ALPHA:表示使用目標顏色的alpha值來做爲因子。

GL_ONE_MINUS_SRC_ALPHA:表示用1.0減去源顏色的alpha值來做爲因子。

GL_ONE_MINUS_DST_ALPHA 表示用1.0減去目標顏色的alpha值來做爲因子。

除此之外,還有GL_SRC_COLOR(把源顏色的四個份量分別做爲因子的四個份量)、GL_ONE_MINUS_SRC_COLOR、 GL_DST_COLOR、GL_ONE_MINUS_DST_COLOR等,前兩個在OpenGL舊版本中只能用於設置目標因子,後兩個在OpenGL 舊版本中只能用於設置源因子。新版本的OpenGL則沒有這個限制,而且支持新的GL_CONST_COLOR(設定一種常數顏色,將其四個份量分別做爲 因子的四個份量)、GL_ONE_MINUS_CONST_COLOR、GL_CONST_ALPHA、 GL_ONE_MINUS_CONST_ALPHA。另外還有GL_SRC_ALPHA_SATURATE

這些宏cocos2d 兼容的
***************************************************/ 
?
? //3. 綁定紋理

if (m_pobTexture != NULL)
? {
? ccGLBindTexture2D(m_pobTexture->getName());
? }
? else
? {
? ccGLBindTexture2D(0);
? }
?
? //4. 繪圖
?
? ccGLEnableVertexAttribs(kCCVertexAttribFlag_PosColorTex);////設置使用相應的頂點格式爲:位置+顏色+紋理座標 
?
? #define kQuadSize sizeof(m_sQuad.bl)
? long offset = (long)&m_sQuad;
?
? //頂點座標
? int diff = offsetof(ccV3F_C4B_T2F, vertices);
? glVertexAttribPointer(kCCVertexAttrib_Position, 3, GL_FLOAT, GL_FALSE,  
? kQuadSize, (void*) (offset + diff));//設置頂點緩衝中紋理座標數據的描述

//紋理座標
? diff = offsetof(ccV3F_C4B_T2F, texCoords);
? glVertexAttribPointer(kCCVertexAttrib_TexCoords, 2, GL_FLOAT, GL_FALSE,
? kQuadSize, (void*)(offset + diff));//設置頂點緩衝中紋理座標數據的描述  
?
? //頂點顏色
? diff = offsetof(ccV3F_C4B_T2F, colors);
? glVertexAttribPointer(kCCVertexAttrib_Color, 4, GL_UNSIGNED_BYTE, GL_TRUE,
? kQuadSize, (void*)(offset + diff));//設置頂點緩衝中顏色數據的描述 
?
? //繪製圖形
? glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
?
? CHECK_GL_ERROR_DEBUG();
/***************************************************
  //繪製邊框 能夠自定義繪製邊框   應用:顯示boundingBox
?CCSize s = this->getTextureRect().size;
  CCPoint offsetPix = this->getOffsetPosition();
  CCPoint vertices[4] =
 {
  ccp(offsetPix.x,offsetPix.y), ccp(offsetPix.x+s.width,offsetPix.y),
  ccp(offsetPix.x+s.width,offsetPix.y+s.height), ccp(offsetPix.x,offsetPix.y+s.height)
  };
  ccDrawPoly(vertices, 4, true);
***********************************************/

? //5. 調試相關的處理
?
? #if CC_SPRITE_DEBUG_DRAW == 1  //經過修改這個宏值 執行下面相應代碼
? //調試模式1:繪製邊框
? CCPoint vertices[4]={
ccp(m_sQuad.tl.vertices.x,m_sQuad.tl.vertices.y),
? ccp(m_sQuad.bl.vertices.x,m_sQuad.bl.vertices.y),
? ccp(m_sQuad.br.vertices.x,m_sQuad.br.vertices.y),
? ccp(m_sQuad.tr.vertices.x,m_sQuad.tr.vertices.y),
? };
? ccDrawPoly(vertices, 4, true);
? #elif CC_SPRITE_DEBUG_DRAW == 2
? //調試模式2:繪製紋理邊緣
? CCSize s = this->getTextureRect().size;
? CCPoint offsetPix = this->getOffsetPosition();
? CCPoint vertices[4] = {
? ccp(offsetPix.x,offsetPix.y), ccp(offsetPix.x+s.width,offsetPix.y),
? ccp(offsetPix.x+s.width,offsetPix.y+s.height), ccp(offsetPix.x,offsetPix.y+
? s.height)
? };
? ccDrawPoly(vertices, 4, true);
? #endif

? CC_INCREMENT_GL_DRAWS(1);
?
? CC_PROFILER_STOP_CATEGORY(kCCProfilerCategorySprite, "CCSprite - draw");
? }

 

渲染樹的繪製(1)————————————————


void CCNode::visit()

{
? //1. 先行處理
? if (!m_bIsVisible)//當此節點被設置爲不可見時,則直接返回不進行繪製
? {
? return;
? }
? kmGLPushMatrix(); //矩陣壓棧  
/***********************************************
保存當前的繪圖矩陣
繪圖矩陣保存好以後,就能夠根據須要對矩陣進行任意的操做了,直到操做結束後再經過"矩陣出棧"來恢復保存的矩陣。因爲全部對繪圖矩陣的操做都在恢復矩陣以前進行,所以咱們的改動不會影響到之後的繪製。
***********************************************/
?
? //處理Grid特效
? if (m_pGrid && m_pGrid->isActive())
? {
? m_pGrid->beforeDraw();
? }
?
? //2. 應用變換
? this->transform();

/***********************************************
transform方法進行一系列變換,以便把本身以及子節點繪製到正確的位置上
draw方法負責把圖形繪製出來,可是從上一節的學習可知,draw方法並不關心紋理繪製的位置,實際上它僅把紋理繪製到當前座標系中的原點
***********************************************/?
? //3. 遞歸繪圖
? CCNode* pNode = NULL;
? unsigned int i = 0;
?
? if(m_pChildren && m_pChildren->count() > 0)
//存在子節點
? sortAllChildren();
? //繪製zOrder < 0的子節點
? ccArray *arrayData = m_pChildren->data;
? for( ; i < arrayData->num; i++ )
? {
? pNode = (CCNode*) arrayData->arr[i];
?
? if ( pNode && pNode->m_nZOrder < 0 )
? {
? pNode->visit();
? }
? else
? {
? break;
? }
? }
? //繪製自身
? this->draw();
?
? //繪製剩餘的子節點
? for( ; i < arrayData->num; i++ )
? {
pNode = (CCNode*) arrayData->arr[i];
? if (pNode)
? {
? pNode->visit();
? }
? }
? }
? else
? {
? //沒有子節點:直接繪製自身
? this->draw();
? }
?
? //4. 恢復工做
? m_nOrderOfArrival = 0;
?
? if (m_pGrid && m_pGrid->isActive())
? {
? m_pGrid->afterDraw(this);
? }
?
? kmGLPopMatrix(); //矩陣出棧

}

繪圖瓶頸———————————————————————————————————————————
影響遊戲性能的瓶頸——————
一、紋理太小:
OpenGL在顯存中保存的紋理的長寬像素數必定是2的冪,對於大小不足的紋理,則在其他部分填充空白,這無疑是對顯存極大的浪費;
同一個紋理能夠容納多個精靈,把內容相近的精靈拼合到一塊兒是一個很好的選擇。(小圖拼大圖)

二、紋理切換次數過多當咱們連續使用兩個不一樣的紋理繪圖時,GPU不得不進行一次紋理切換,這是開銷很大的操做,然而當咱們不斷地使用同一個紋理進行繪圖時,GPU工做在同一個狀態,額外開銷就小了不少,所以,若是咱們須要批量繪製一些內容相近的精靈,就能夠考慮利用這個特色來減小紋理切換的次數。

三、紋理過大:顯存是有限的,若是在遊戲中不加節制地使用很大的紋理,則必然會致使顯存緊張,所以要儘量減小紋理的尺寸以及色深。

優化方式——————————————————————————————————

一、碎圖壓縮與精靈框幀:——TexturePacker
到目前爲止,咱們都是使用各自的紋理來建立精靈,由此致使的紋理太小和紋理切換次數過可能是產生瓶頸的根源。針對這個問題,一個簡單的解決方案是碎圖合併與精靈框幀。碎圖合併能夠將許多零碎的小圖片合併到一張大圖裏,而且這張大圖的大小剛好符合OpenGL的紋理規範,從空間上減小無謂的浪費。框幀是紋理中的一部分,當咱們把小紋理合並好以後就能夠利用精靈框幀來建立精靈了。

二、批量渲染:————————
有了足夠大的紋理圖後,就能夠考慮從渲染次數上進一步優化了。若是不須要切換綁定紋理,那麼幾個OpenGL的渲染請求是能夠批量提交的,也就是說,在同一紋理下的繪製均可以一次提交完成。在Cocos2d-x中,咱們提供了CCSpriteBatchNode來實現這一優化。
CCSpriteBatchNode能夠一次批量提交全部子節點的繪圖請求,以減小提交次數,提升繪圖性能。這個優化要求每一個子節點都使用同一張紋理,例如咱們能夠把全部的魚的圖片放在一個紋理之中,每一個精靈顯示出本身存在於紋理中的那一部分。CCSpriteBatchNode的使用方法也很簡單,它是一個特殊的節點,咱們只要把須要繪製的精靈添加爲它的子節點,而後再把CCSpriteBatchNode添加到層或場景之中便可。固然,這些精靈必須使用統一個紋理。能夠認爲CCSpriteBatchNode所扮演的角色是精靈與繪圖層間的一箇中間層,只須要把須要繪製的精靈加入爲它的子節點,就能夠提升繪製效率。

自定義繪圖————————
void CCNode::draw()
? {
? //CCAssert(0);

? //能夠重載此方法
? //最好僅在這個方法中繪製自定義的內容
? }

Cocos2d-x提供了一些簡單的快捷繪圖接口實現最簡單的功能,咱們能夠使用這些接口,並從中找到很好的OpenGL編程規範。這些接口由"CCDrawingPrimitives.h"和對應的cpp文件提供,包括了點、線、多邊形、圓形和貝塞爾曲線等最基本的幾何圖形的繪製,還包括了一些基本的設置,如設置點的大小、繪製的顏色等。


咱們不妨給炮臺加上瞄準線功能。考慮到針對炮臺的功能已經足夠多、足夠複雜了,咱們將炮臺抽象爲一個類,集中封裝相關操做。根據引擎的接口規範,應該在draw函數中繪製咱們的自定義效果
——————不要濫用類 只有足夠複雜時在作類
void CannonSprite::draw()
? {
? CCSprite::draw(); //調用CCSprite的繪圖,保證紋理被正確繪製
CCPoint origin = CCPointZero;
? CCPoint direction = ccp(0, 1);
? direction = ccpMult(direction, 1024);
? CCPoint target = ccpAdd(origin, direction)
?
? ccDrawColor4B(255, 225, 255, 255); //設置繪圖顏色爲白色
? ccDrawLine(origin, target); //繪製線段
? }
炮臺旋轉時引發的整個座標系的變換能保證咱們的瞄準線也隨着炮臺一塊兒旋轉相應的角度


數據交流——————
p325 - p332  截圖實現 —— 小地圖


可編程着色器—————————————————————————————————————————

在渲染流水線上,存在着兩個對開發者可見的可編程着色器,具體以下所示。

一、頂點着色器(vertex shader)。對每一個頂點調用一次,完成頂點變換(投影變換和視圖模型變換)、法線變換與規格化、紋理座標生成、紋理座標變換、光照、顏色材質應用等操做,並最終肯定渲染區域。在Cocos2d-x的世界中,精靈和層等都是矩形,它們的一次渲染會調用4次頂點着色器。

二、段着色器(fragment shader,又稱片斷着色器)。這個着色器會在每一個像素被渲染的時候調用,也就是說,若是咱們在屏幕上顯示一張320×480的圖片,那麼像素着色器就會被調用153 600次。所幸,在顯卡中一般存在不止一個圖形處理單元,渲染的過程是並行化的,其渲染效率會比用串行的CPU執行高得多。

這兩個着色器不能單獨使用,必須成對出現,這是由於頂點着色器會首先肯定每個顯示到屏幕上的頂點的屬性,而後這些頂點組成的區域被化分紅一系列像素,這些像素的每個都會調用一次段着色器,最後這些通過處理的像素顯示在屏幕上,兩者是協同工做的。

咱們能夠找到足夠多的開源的着色器,可以提供各類豐富的效果。

如何在Cocos2d-x遊戲中導入自定義的着色器效果————————
p334 - p353
着色器 能夠實現水紋等效果

CCGrid3D  能夠代替着色器實現水紋

與自定義着色器相比,CCActionGrid3D侷限於表現一些使畫面變形的效果,其本質是將目標節點所在區域劃分爲網格,對每個小網格進行座標變換從而造成畫面的特殊扭曲。
正由於此,它沒法改變    光照  與   顏色  的渲染方式。

————————————————————
小結:
紋理圖片:CCImage與CCTexture分別表明一張圖片和一個可載入到顯存的紋理。CCTexture可由CCImage建立,而CCImage能夠載入或保存PNG、TIFF、JPG等格式的文件。

着色器:着色器是用於代替渲染流水線的一段程序,能夠用來實現對頂點和像素的操做。在這一章中,咱們使用着色器實現了水紋效果。 ?

CCGrid3D:Cocos2d-x提供一套網格變換的功能,經過CCActionGrid3D動做類能夠實現一些簡單畫面的3D變換,例如水紋效果。 ?

物理引擎—————————————————————————————————————————————————————
p358 - 385

數據處理 存儲 xml————————————————————————————————————————————————

下面將由淺入深地介紹幾種數據持久化的方法:

CCUserDefault————

CCUserDefault是Cocos2d-x引擎提供的持久化方案,其做用是存儲全部遊戲通用的用戶配置信息,例如音樂和音效配置等。爲了方便起見,有時咱們也能夠用CCUserDefault來存儲金幣數目這種簡單的數據項。 ? CCUserDefault能夠看作一個永久存儲的字典,本質是一個XML文件,將每一個鍵及其對應的值以節點的形式存儲到外存中。值只支持int和float等基本類型。使用接口很是簡單,只須要一行代碼:
CCUserDefault::sharedUserDefault()->setIntegerForKey("coin", coin - 1);

因爲每次設置和讀取都會遍歷整棵XML樹,效率不高,且值類型具備侷限性,所以CCUserDefault只適合小規模使用,對於複雜的持久化場景就會顯得很無力。

格式化存儲————

對於稍微複雜的持久化情景,仍是能夠藉助CCUserDefault來知足咱們的需求的。因爲CCUserDefault是容許存儲字符串值的,因此只要將須要保存的數據類型先轉化爲字符串,就能夠寫入外存中。

咱們先將用戶記錄封裝爲類,由用戶ID標識,在一個ID下存放金幣、經驗值和音樂3個值,這樣遊戲中就容許存在多個用戶的記錄了。咱們建立了一個UserRecord類來讀寫用戶記錄,其定義以下:

class UserRecord : public CCObject
? {
? CC_SYNTHESIZE_PASS_BY_REF(string, m_userID, UserID);
? CC_SYNTHESIZE_PASS_BY_REF(int, m_coin, Coin);
? CC_SYNTHESIZE_PASS_BY_REF(int, m_exp, Exp);
? CC_SYNTHESIZE_PASS_BY_REF(bool, m_isMusicOn, IsMusicOn);
?
? public:

UserRecord(const string& userID);
? void saveToCCUserDefault();
? void readFromCCUserDefault();
? };

咱們把須要存檔的數據經過sprintf函數格式化成一個字符串,並把字符串保存到CCUserDefault之中。注意,這裏咱們作了一點小小的處理,存儲的關鍵字除了用戶ID以外,還添加了一個前綴"UserRecord",這樣能夠保證,即便在存儲時其餘類型對象用了一樣的用戶ID,也能夠被區分開。具體代碼以下:

void UserRecord::saveToCCUserDefault()
? {
? char buff[100];
? sprintf(buff, "%d %d %d",
? this->getCoin(),
? this->getExp(),
? this->getIsMusicOn() ? 1 : 0
? );

   const char* key = ("UserRecord." + this->getUserID()).c_str();
? CCUserDefault::sharedUserDefault()->setStringForKey(key, buff);
? }

有了寫入存檔的功能,咱們還須要一個逆向的從存檔讀取的過程。讀取過程與此過程恰好相反。咱們從CCUserDefault來獲取保存的字符串,再使用sscanf函數來獲得每一個數據的值,相關代碼以下:

void UserRecord::readFromCCUserDefault()
? {
? string buff = CCUserDefault::sharedUserDef;
? ault()->getStringForKey(("UserRecord." + this->getUserID()).c_str());
?
? int coin = 0;
? int experience = 0;
? int music = 0;
?
? sscanf(buff.c_str(), "%d %d %d", &coin, &experience, &music);
? this->setCoin(coin);
? this->setExp(experience);
? this->setIsMusicOn(music!=0);

這一寫一讀的過程能夠稱爲序列化與反序列化,是立體的內存數據與一維的字符串間的相互轉換。實際上,咱們只完成了從數據到CCUserDefault的標準化存儲間的轉換,從標準化存儲到實際存儲在文件中的字符串間的轉換是交由引擎封裝完成的。

本地文件存儲————

如今咱們已經能夠將複雜的數據類型存儲到配置文件中了,但把所有數據集中在一個文件中顯然不是一個明智的作法。若是將不一樣類別的數據(例如,NPC的狀態和玩家完成的成就)存儲到不一樣的文件中,既能夠提升效率,也方便咱們查找。下面咱們來看看如何實現它。

不一樣平臺間的文件系統不盡相同,爲了簡化操做、方便開發,Cocos2d-x引擎爲咱們提供了CCFileUtil類,用於實現獲取路徑和讀取內容等功能,其中兩個最重要的接口以下

static unsigned char* getFileData(const char* pszFileName,
const char* pszMode, unsigned long * pSize);//裝載文件內容
static std::string getWriteablePath(); //得到可讀寫路徑

藉助這兩個接口,咱們能夠得到一個路徑,而後對文件進行相應的讀寫。文件讀寫在實際開發中應用得比較直接,通常是批量集中寫入和讀出,在此再也不贅述。對於稍微靈活的場景,尤爲是須要在大量數據中隨機讀寫一小部分的時候,直接的文件存儲因爲缺乏尋址支持,會變得很是麻煩。咱們能夠藉助XML和SQL這兩種方式,來更好地解決這個問題。

XML與JSON————————————————————————————————

XML與JSON(1)

XML和JSON都是當下流行的數據存儲格式,它們的共同特色就是數據明文,十分易於閱讀。XML源自於SGML,是一種標記性數據描述語言,而JSON則是一種輕量級數據交換格式,比XML更爲簡潔。鑑於C++對XML的支持更爲完善,Cocos2d-x選擇了XML做爲主要的文件存儲格式。

下面咱們看看用戶記錄是如何存儲到XML文件中的,相關代碼以下所示:

<?xml version="1.0" encoding="utf-8"?>
<userDefaultRoot>
? <UserRecord.201208012221>41 -8589934601 1</UserRecord.201208012221>
</userDefaultRoot>

與直接的無格式存儲相比,這樣的文件雖然會耗費稍大的空間,但可讀性更強,程序解析起來也更方便一些。


XML文檔的語法很是簡潔。文檔由節點組成,節點的定義是遞歸的,節點內能夠是一個字符串,也能夠是由一組<tag></tag>包圍的若干節點,其中tag能夠是任意符合命名規則的標識符。這樣的遞歸嵌套結構很是靈活,特別適合以鍵值對形式存儲的數據,好比數組和字典等。對於遊戲開發中的大部分情景,XML文檔均可以遊刃有餘地處理它們。 ?

隨Cocos2d-x一塊兒分發的還有一個處理XML的開源庫LibXML2,它用純C語言的接口封裝了對XML的建立、尋址、讀和寫等操做,極大地方便了開發。這裏咱們能夠仿照CCUserDefault的作法,將對象存儲到指定的XML文件中。

和XML語言的規範相對應,LibXML2庫一樣十分簡潔,只有兩個核心的概念,如表13-1所示。
LibXML2

核心類類名   含義          涵蓋功能
xmlDocPtr   指向XML文檔的指針        XML文檔的建立、保存、文檔基本信息存取、根節點存取等 xmlNodePrt   指向XML文檔中一個節點的指針    節點內容存取、子節點的增、刪、改等 ?

下面咱們開始之外部XML文件的方式存儲UserRecord對象,並從中看到XML文檔的操做和LibXML的具體用法。 ?
在UserRecord類中,咱們添加以下兩個接口,分別負責將對象從XML文件中讀出和寫入:

void saveToXMLFile(const char* filename="default.xml");

void readFromXMLFile(const char* filename="default.xml");

在開始以前,咱們能夠進一步抽象出兩個函數,完成對象和字符串間的序列化和反序列化,以便在XML的讀寫接口和CCUserDefault的讀寫接口間共享,相關代碼以下:

void UserRecord::readFromString(const string& str)
? {
? int coin = 0;
? int experience = 0;
? int music = 0;
?
? sscanf(str.c_str(), "%d %d %d", &coin, &experience, &music);
? this->setCoin(coin);
? this->setExp(experience);
? this->setIsMusicOn(music != 0);

void UserRecord::writeToString(string& str)
? {
? char buff[100] = "";
? sprintf(buff,"%d %d %d",
? this->getCoin(),

this->getExp(),
? this->getIsMusicOn() ? 1 : 0
? );
? str = buff;


XML與JSON(2)————

完成了序列化與反序列化的功能後,經過CCUserDefault讀寫UserRecord的實現就十分簡潔了。下面是相關的代碼:

void UserRecord::readFromCCUserDefault()
? {
? string key("UserRecord.");
? key += this->getUserID();
?
? string buff = CCUserDefault::sharedUserDefault()->getStringForKey(key.c_str());
? this->readFromString(buff);
xmlFreeDoc(node->doc);
? }
? void UserRecord::saveToCCUserDefault()
? {
string buff;
? this->writeToString(buff);
?
? string key("UserRecord.");
? key += this->getUserID();
?
? CCUserDefault::sharedUserDefault()->setStringForKey(key.c_str(),buff);
? xmlFreeDoc(node->doc);
? }

有了對字符的序列化和反序列化,實際上咱們只須要關心如何正確地在XML文檔中讀寫鍵值對。咱們暫且將對象都寫到文檔的根節點下,不考慮存儲數組等複合數據結構的情景,儘管這些情景在操做上是相似的。首先,咱們在一個指定的文檔的根節點下找到一個鍵值,若是根節點下不存在指定的鍵值,將根據參數指定來建立,相關代碼以下:
?
xmlNodePtr getXMLNodeForKey(const char* pKey, const char* filename,
? bool creatIfNotExists = true)
? {
? xmlNodePtr curNode = NULL,rootNode = NULL;
? if (! pKey) {
? return NULL;
}
? do {
? //獲得根節點
?
? xmlDocPtr doc = getXMLDocument(filename);
? rootNode = xmlDocGetRootElement(doc);
? if (NULL == rootNode) {
? CCLOG("read root node error");
? break;
? }
? //在根節點下找到目標節點
? curNode = (rootNode)->xmlChildrenNode;
? while (NULL != curNode) {
? if (!xmlStrcmp(curNode->name, BAD_CAST pKey)){
? break;
? }
? curNodecurNode = curNode->next;
? }
? //若是沒找到且須要建立,則建立該節點
? if(NULL == curNode && creatIfNotExists) {
? curNode = xmlNewNode(NULL, BAD_CAST pKey);

xmlAddChild(rootNode, curNode);
?
? }
? } while (0);
?
? return curNode;
? }

在上述代碼中,咱們首先根據文件名得到了對應的XML文檔指針,而後經過xmlDocGet- ?

RootElement函數得到了該文檔的根節點rootNode。一個節點的子節點是以鏈表形式存儲的,經過xmlChildrenNode得到第一個子節點指針,再經過next函數迭代整個子節點列表。若是沒有找到指定節點,且函數參數指定了必須建立對應鍵值的子節點,則函數會根據給定的鍵值key建立並添加到根節點中。 ?

接下來,則是根據文件名得到XML文檔指針的方法,相關代碼以下:

bool createXMLFile(const char* filename, const char* rootNodeName = "root")
? {
? bool bRet = false;
? xmlDocPtr doc = NULL;
? do {
? //建立XML文檔
doc = xmlNewDoc(BAD_CAST"1.0");
? if (doc == NULL) {
? CCLOG("can not create xml doc");
? break;
? }
?
? //建立根節點
? xmlNodePtr rootNode = xmlNewNode(NULL, BAD_CAST rootNodeName);
? if (rootNode == NULL) {
? CCLOG("can not create root node");
? break;
? }
?
? xmlDocSetRootElement(doc, rootNode);
?
? //保存文檔
? xmlSaveFile(filename, doc);
? bRet = true;
? } while (0);
?
? //釋放文檔
? if (doc) {
xmlFreeDoc(doc);
? }
?
? return bRet;
? }
? xmlDocPtr getXMLDocument(const char* filename)
? {
? if(!isFileExists(filename) && !createXMLFile(filename)) {
? return NULL;
? }
? return xmlReadFile(filename, "utf-8", XML_PARSE_RECOVER);
? }
? bool isFileExists(const char *filename)
? {
? FILE *fp = fopen(filename, "r");
? bool bRet = false;
? if (fp) {
? bRet = true;
? fclose(fp);
? }
? return bRet;
}

這3段代碼分別作了3件事情:建立一個具備特定根節點的XML文檔,獲取一個特定文件名的XML文件,測試文件是否存在。 ?

集成以上的代碼,咱們再次保存UserRecord對象,能夠成功地將其存入一個指定的XML文檔中,相關代碼以下:
?
<?xml version="1.0" encoding="utf-8"?>
? <userDefaultRoot>
<UserRecord.201208012221>41 -8589934601 1</UserRecord.201208012221>
? </userDefaultRoot>

加密與解密————
p399 - 411


網絡傳輸架構———————————————————————————————————————————————————

p412-430 (第一輪不看網絡)

移動設備昂貴的CPU與內存————————————————————————————————————————————

\*************************************************************************

許多人認爲,「緩存」是內存的一部分 許多技術文章都是這樣教授的
可是仍是有不少人不知道緩存在什麼地方,緩存是作什麼用的

其實,緩存是CPU的一部分,它存在於CPU中 CPU存取數據的速度很是的快,一秒鐘可以存取、處理十億條指令和數據(術語:CPU主頻1G),而內存就慢不少,快的內存可以達到幾十兆就不錯了,可見二者的速度差別是多麼的大

緩存是爲了解決CPU速度和內存速度的速度差別問題
內存中被CPU訪問最頻繁的數據和指令被複制入CPU中的緩存,這樣CPU就能夠不常常到象「蝸牛」同樣慢的內存中去取數據了,CPU只要到緩存中去取就好了,而緩存的速度要比內存快不少

這裏要特別指出的是:
1.由於緩存只是內存中少部分數據的複製品,因此CPU到緩存中尋找數據時,也會出現找不到的狀況(由於這些數據沒有從內存複製到緩存中去),這時CPU仍是會到內存中去找數據,這樣系統的速度就慢下來了,不過CPU會把這些數據複製到緩存中去,以便下一次不要再到內存中去取。

2.由於隨着時間的變化,被訪問得最頻繁的數據不是一成不變的,也就是說,剛纔還不頻繁的數據,此時已經須要被頻繁的訪問,剛纔仍是最頻繁的數據,如今又不頻繁了,因此說緩存中的數據要常常按照必定的算法來更換,這樣才能保證緩存中的數據是被訪問最頻繁的

3.關於一級緩存和二級緩存 爲了分清這兩個概念,咱們先了解一下RAM ram和ROM相對的,RAM是掉電之後,其中才信息就消失那一種,ROM在掉電之後信息也不會消失那一種 RAM又分兩種, 一種是靜態RAM,SRAM;一種是動態RAM,DRAM。前者的存儲速度要比後者快得多,咱們如今使用的內存通常都是動態RAM。

有的菜鳥就說了,爲了增長系統的速度,把緩存擴大不就好了嗎,擴大的越大,緩存的數據越多,系統不就越快了嗎 緩存一般都是靜態RAM,速度是很是的快, 可是靜態RAM集成度低(存儲相同的數據,靜態RAM的體積是動態RAM的6倍), 價格高(同容量的靜態RAM是動態RAM的四倍), 因而可知,擴大靜態RAM做爲緩存是一個很是愚蠢的行爲, 可是爲了提升系統的性能和速度,咱們必需要擴大緩存, 這樣就有了一個折中的方法,不擴大原來的靜態RAM緩存,而是增長一些高速動態RAM作爲緩存, 這些高速動態RAM速度要比常規動態RAM快,但比原來的靜態RAM緩存慢,

咱們把原來的靜態ram緩存叫一級緩存,而把後來增長的動態RAM叫二級緩存。 一級緩存和二級緩存中的內容都是內存中訪問頻率高的數據的複製品(映射),它們的存在都是爲了減小高速CPU對慢速內存的訪問。 一般CPU找數據或指令的順序是:先到一級緩存中找,找不到再到二級緩存中找,若是還找不到就只有到內存中找了


(千萬不能把緩存理解成一個東西,它是一種處理方式的統稱!   緩存只是一種策略)
*******************************************************************/

CCTextureCache—————————————

Cocos2d-x中的緩存 ?
幸運的是,咱們不須要本身實現緩存,由於Cocos2d-x已經爲咱們提供了足夠強大的實現。引擎中存在3個緩存類,都是全局單例模式。

一、CCTextureCache
其原理是對加入緩存的紋理資源進行一次引用,使其引用計數加一,保持不被清除,而Cocos2d-x的渲染機制是能夠重複使用同一份紋理在不一樣的場合進行繪製,從而達到重複使用,下降內存和GPU運算資源的開銷的目的

接口:
static CCTextureCache* sharedTextureCache(); //返回紋理緩存的全局單例
CCTexture2D* addImage(const char* fileimage); //添加一張紋理圖片到緩存中
void removeUnusedTextures(); //清除不使用的紋理  ————釋放當前全部引用計數爲1的紋理,即目前沒有被使用的紋理

引擎會在設備出現內存警告時自動清理緩存,可是這顯然在不少狀況下已經爲時過晚了。通常狀況下,咱們應該在切換場景時清理緩存中的無用紋理,由於不一樣場景間使用的紋理是不一樣的。
若是確實存在着共享的紋理,將其加入一個標記數組來保持其引用計數,以免被清理了

二、CCSpriteFrameCache
第二個則是精靈框幀緩存。顧名思義,這裏緩存的是精靈框幀CCSpriteFrame,它主要服務於多張碎圖合併出來的紋理圖片

static CCSpriteFrameCache* sharedSpriteFrameCache(void); //全局共享的緩存單例
void addSpriteFramesWithFile(const char *pszPlist); //經過plist配置文件添加一組精靈幀
void removeUnusedSpriteFrames(void); //清理無用緩存

三、CCAnimationCache
對於一個精靈動畫,每次建立時都須要加載精靈幀,按順序添加到數組,再建立對應動做類,這是一個很是煩瑣的計算過程。對於使用頻率高的動畫,好比魚的遊動,將其加入緩存能夠有效下降每次建立的巨大消耗

static CCAnimationCache* sharedAnimationCache(void);//全局共享的緩存單例
void addAnimation(CCAnimation *animation, const char * name);//添加一個動畫到緩存
void removeAnimationByName(const char* name);//移除一個指定的動畫
CCAnimation* animationByName(const char* name);//得到事先存入的動畫


實際上,若是考慮到兩個場景間使用的動畫基本不會重複,能夠直接清理整個動畫緩存。

因此,在場景切換時咱們應該加入以下的清理緩存操做:
? void releaseCaches()
? {
? CCAnimationCache::purgeSharedAnimationCache();
? CCSpriteFrameCache::sharedSpriteFrameCache()->removeUnusedSpriteFrames();
? CCTextureCache::sharedTextureCache()->removeUnusedTextures();?
}
值得注意的是清理的順序,應該先清理動畫緩存,而後清理精靈幀,最後是紋理。按照引用層級由高到低,以保證釋放引用有效。


對象池機制:可回收與重複使用————

另外一個能有效提升內存和計算效率的是對象池機制。其本質與緩存相似,即但願能減小那些頻繁使用的對象的重複建立和銷燬

使用對象池機制能帶來兩方面的收益,首先是減小對象初始化階段的重複計算,其次是避免反覆地向操做系統申請歸還內存。一個很好的例子就是捕魚遊戲中的魚,魚和魚之間的屬性是相似的,不同的僅僅是當前的座標位置及正在播放的動畫幀。那麼,當魚游出屏幕後,能夠不對其進行銷燬,而是暫存起來。某一時刻須要從新建立魚時,咱們能夠將其從對象池中取出,從新申請內存並初始化,這樣就大大減輕了CPU的負擔。

對象池和緩存很像,但比緩存更抽象,也更簡單一些,由於咱們不須要考慮從哪裏加載的問題:都已經被抽象爲初始化函數了。並且更簡化的是,加入對象池的每個對象都是無差異的,咱們不須要對每個對象進行特定的標記,直接取出任意一個未使用的對象便可。

看完上面的描述,讀者應該有了初步的認識:緩存是一個字典,而對象池則是一個數組。得益於引用計數的內存管理機制,只須要在數組上作適當封裝就能夠提供一個對象池的功能了。儘管如此,一個高效實現的對象池還要考慮如何有效地處理對象的生成和歸還,以及佔用內存的動態增加等問題。所以,咱們不妨藉助前人成果,在已有對象池的基礎上搭建適合咱們遊戲使用的對象池。

對象池實現(1)

Boost是一個可移植、免費開源的C++庫,提供了大量實用的開發組件,並且因爲對跨平臺和C++標準的強調,其實現的功能幾乎不依賴於操做系統和標準庫外的其餘組件,所以能夠在任何支持C++的平臺上運做良好。

oost提供了一個對象池object_pool,它位於boost庫的"boost/pool/object/_pool.hpp"中。這是一個泛型的對象池,可以針對指定類型的對象進行分配。一個對象池的聲明和使用規範爲以下結構:

object_pool<CCSprite> spritePool; //爲CCSprite聲明一個對象池
CCSprite* sp =spritePool.construct(); //從對象池獲得一個對象,並調用默認構造函數
spritePool.destroy(sp); //對從對象池獲得的對象調用析構函數,並返還到對象池中備用

object_pool的一大特點是能夠針對不一樣的參數調用被分配對象的構造函數。惋惜在Cocos2d-x對象生命週期管理中,對象的建立和初始化是分離的,大部分類的初始化都不在構造函數中完成,構造函數中僅僅做引用計數的初始化。這裏也引入了一個新的問題,Cocos2d-x對象在引用計數爲零的時候會自動觸發delete。對於從對象池分配的對象來講,不能經過delete而必須經過destroy來刪除。所以,在不修改引擎源碼的前提下,咱們須要在object_pool的基礎上做一點小小的包裝使其能夠配合引擎的內存管理使用,相關代碼以下:

template<class T>
class MTPoolFromBoost : public ObjectPoolProtocol
{
? object_pool<T> pool;
? CCArray* objects;
? MTPoolFromBoost() : pool(256){
? objects = CCArray::create();
? objects->retain();
? }
? public:
? ~MTPoolFromBoost()
? {
? objects->removeAllObjects();
? objects->release();
? }
? static MTPoolFromBoost<T>* sharedPool()
? {
? static MTPoolFromBoost<T> __sharedPool;
? return &__sharedPool;
? }
? T* getObject()
? {
? T* pObj = pool.construct();
? objects->addObject(pObj);
? pObj->release();

return pObj;
? }
? void freeObjects(int maxScan = 100)
? {
? static int lastIndex =0;
?
? int count = objects->count();
? if(lastIndex >= count)
? lastIndex = 0;
? if(maxScan > count) maxScan = count;
?
? CCArray* toRemove = CCArray::create();
? for(int i = 0; i < maxScan; i++) {
? CCObject* obj = objects->objectAtIndex((i + lastIndex) % count);
? if(obj->retainCount() == 1) {
? toRemove->addObject(obj);
? }
? }
?
? objects->removeObjectsInArray(toRemove);
? for(int i=0; i < toRemove->count(); i++) {

T* obj = dynamic_cast<T*>(toRemove->lastObject());
? obj->retain();
? toRemove->removeLastObject();
? pool.destroy(obj);
? }
? CCLOG("%s ends. Obj now = %d", __FUNCTION__, objects->count());
? }
? };

因爲作成了模板類的形式,類的實現就所有存在於頭文件中了。在這個包裝類中,咱們僅僅作了一件事情--在分配對象的時候,同時將對象添加到一個數組中,數組會增長對象的一次引用計數,所以能夠保證在正常使用的狀況下,不會有對象會被觸發delete操做。由此引出的即是,須要在合適的時候回收對象,不然對象池將持續增加直到耗盡內存。

在提供的內存釋放函數freeObjects中,咱們檢查當前緩衝數組中每一個元素的引用計數,對於引用計數爲1的對象,表示已經沒有其餘對象在引用這個對象,將其回收歸還到對象池中。值得注意的是,在釋放對象的循環中,咱們將一個待回收的對象retain後並無release,這是對引用計數內存管理的一個小小破例,保證了該對象在從數組清理以後仍然不會觸發delete操做

另外,這裏設計了一個回收的掃描步長,每次回收僅在數組中掃描必定數量的對象就返回。這樣作的好處在於,咱們能夠將整個對象池的回收掃描分散到每一幀中,隱性地完成併發。這個步長能夠根據工程規模和所需的清理頻率進行調整,對於遊戲中對象生成和銷燬並不頻繁的狀況,能夠設置一個較長的清理週期,在每次清理時設置一個較大的掃描步長以回收更多的對象,同時減輕計算壓力。

模板化以後,實際上每一個類對應了一個對象池,以硬編碼的形式清理這些對象池是十分費勁的,所以咱們再在此基礎上擴展一個管理器,管理這些對象池的清理。

對象池實現(2)

首先,須要作的是將回收操做分離抽象。咱們定義一個接口並讓MTPoolFromBoost繼承,這樣就可以在運行時用統一接口調用內存池回收對象:
class ObjectPoolProtocol : public CCObject{
? public:
? virtual void freeObjects(int maxScan = 100) = 0;
? };

這樣抽象的目的是將全部用到的對象池添加到數組內,以便統一管理。首先,爲管理器封裝一個獲取對象指針的函數:
? class MTPoolManager : public CCObject{

CCArray* pools;
? class PoolCounter {
? public:
? PoolCounter(ObjectPoolProtocol* pool,CCArray* pools)
? {
? pools->addObject(pool);
? }
? };
?
? MTPoolManager()
? {
? pools = CCArray::create();
? pools->retain();
? }
? ~MTPoolManager()
? {
? pools->release();
? }
?
? public:
? static MTPoolManager* sharedManager()

{
static MTPoolManager __sharedManager;
?
? return &__sharedManager;
? }
? void freeObjects(ccTime dt)
? {
? for(int i = 0; i < pools->count(); i++) {
? ObjectPoolProtocol* pool = dynamic_cast<ObjectPoolProtocol*>(pools->
? objectAtIndex(i));
? pool->freeObjects();
? }
? }
?
? template<class T>
? T* getObject(T*& pObj)
? {
? static PoolCounter ___poolCounter(MTPoolFromBoost<T>::sharedPool(), pools);
? return pObj = MTPoolFromBoost<T>::sharedPool()->getObject();
? }

在管理器中咱們設計了一個獲取對象的接口函數getObject,能夠根據傳入的指針類型調用相應類型的對象池得到對象。這裏咱們設置一個靜態變量,使用這個變量的構造函數添加當前對象池到類的對象池數組中。因爲這個函數是模板化的,最終將把每種調用到的對象池添加到管理器的對象池數組中。這樣設計的另外一個好處是,管理器調用某一類型的對象池以前,不會在管理器的清理函數中觸發該對象池的清理。

而在管理器的清理函數中,能夠獲取每個曾經使用過的管理器,調用其清理接口清理對象。 最後,咱們只須要在程序初始化完畢後添加該管理器到引擎的定時觸發器中:

CCDirector::sharedDirector()->getScheduler()->scheduleSelector(
? schedule_selector(MTPoolManager::freeObjects),
? MTPoolManager::sharedManager(),
? 1,
? false
? );

落實到工廠方法

通過上面的封裝,顯式地使用對象池已經很方便了,但咱們還能夠作得更好。藉助於工廠方法,咱們能夠將對象池隱藏起來,透明化其使用:
FishSprite* FishSprite::spriteWithSpriteFrameName(const char* file)
? {
? FishSprite *pRet = MTPoolManager::sharedManager()->getObject(pRet);
? if(pRet && !pRet->initWithSpriteFrameName(file)) {
? CC_SAFE_RELEASE_NULL(pRet);
?
? }
? return pRet;
? } ?

這樣作的好處是,遊戲中的邏輯代碼是沒有改動的,直接無縫引入了對象池加強內存管理。

能夠看到,使用對象池後,分配對象的耗時下降到了單純使用new的1/10左右,這個提高是很是可觀的。


使用時機———————————

最後須要強調的是,緩存和對象池都不是萬金油,咱們須要把握好它們的使用時機。緩存和對象池的使用動機都是爲頻繁使用的資源做優化(這裏的資源能夠是外存的紋理,也能夠是一個對象),避免大量的重複計算。緩存和對象池內都作了一些額外的小計算量的標記來知足這一需求。對於遊戲中那些使用頻率並不高的部分,加入緩存或者對象池反而極可能由於額外的標記計算而下降性能。這也是引擎只爲咱們提供了3個緩存器的緣由

值得一提的是,緩存和對象池不只僅適用於C++這類偏底層的開發語言,在C#和JavaScript等語言中,內存的開銷更大,使用好緩存和對象池能有效減小沒必要要的系統內存管理,提高遊戲執行效率。


單線程的尷尬——————————————————

併發編程
併發編程是利用線程實現的一系列技術,普遍用於執行耗時的任務。利用多線程技術,能夠使遊戲顯示載入頁面的同時在後臺加載數據,也能夠使遊戲在運行的同時在後臺進行下載任務等。併發編程的優點巨大,使用起來也並不困難,在這一章中,咱們會詳細介紹併發編程的方法。

單線程的尷尬
從新回顧下Cocos2d-x的並行機制。引擎內部實現了一個龐大的主循環,在每幀之間更新各個精靈的狀態、執行動做、調用定時函數等,這些操做之間能夠保證嚴格獨立,互不干擾。不得不說,這是一個很是巧妙的機制,它用一個線程就實現了併發,尤爲是將連續的動做變化切割爲離散的狀態更新時,利用幀間間隔刷新這些狀態即實現了多個動做的模擬。
但這在本質上畢竟是一個串行的過程

原本這個問題是難以免的,可是隨着移動設備硬件性能的提升,雙核甚至四核的機器已經愈來愈廣泛了,若是再不經過多線程挖掘硬件潛力就過於浪費了。

pthread——————————————————————

pthread是一套POSIX標準線程庫,能夠運行在各個平臺上,包括Android、iOS和Windows,也是Cocos2d-x官方推薦的多線程庫。它使用C語言開發,提供很是友好也足夠簡潔的開發接口。

一個線程的建立一般是這樣的:
void* justAnotherTest(void *arg)
? {
? LOG_FUNCTION_LIFE;
? //在這裏寫入新線程將要執行的代碼
? return NULL;
? }
? void testThread()
? {
? LOG_FUNCTION_LIFE;
? pthread_t tid;
? pthread_create(&tid, NULL, &justAnotherTest, NULL);
? } ? 這裏咱們在testThread函數中用pthread_create建立了一個線程,新線程的入口爲justAnotherTest函數。pthread_create函數的代碼以下所示:
? PTW32_DLLPORT int PTW32_CDECL pthread_create(
? pthread_t * tid,
? const pthread_attr_t * attr,
? void *(*start) (void *),
? void *arg);

pthread_create是建立新線程的方法,它的第一個參數指定一個標識的地址,用於返回建立的線程標識;第二個參數是建立線程的參數,在不須要設置任何參數的狀況下,只需傳入NULL便可;第三個參數則是線程入口函數的指針,被指定爲void* (void*)的形式。函數指針接受的惟一參數來源於調用pthread_create函數時所傳入的第四個參數,能夠用於傳遞用戶數據。

線程安全————————————————
使用線程就不得不提線程安全問題。線程安全問題來源於不一樣線程的執行順序是不可預測的,線程調度都視系統當時的狀態而定,尤爲是直接或間接的全局共享變量。若是不一樣線程間都存在着讀寫訪問,就極可能出現運行結果不可控的問題。

在Cocos2d-x中,最大的線程安全隱患是內存管理。引擎明確聲明瞭retain、release和autorelease三個方法都不是線程安全的。若是在不一樣的線程間對同一個對象做內存管理,可能會出現嚴重的內存泄露或野指針問題。好比說,若是咱們按照下述代碼加載圖片資源,就極可能出現找不到圖片的報錯:

{
? LOG_FUNCTION_LIFE;
? CCTextureCache::sharedTextureCache()->addImage("fish.png");
? return NULL;
? }
?
? void makeAFish()
? {
? LOG_FUNCTION_LIFE;
? pthread_t tid;
? pthread_create(&tid, NULL, &loadResources, NULL);
? CCSprite* sp = CCSprite::create("fish.png");
? }

所以,使用多線程的首要原則是,在新創建的線程中不要使用任何Cocos2d-x內建的內存管理,也不要調用任何引擎提供的函數或方法,由於那可能會致使Cocos2d-x內存管理錯誤。

一樣,OpenGL的各個接口函數也不是線程安全的。也就是說,一切和繪圖直接相關的操做都應該放在主線程內執行,而不是在新建線程內執行。

線程間任務安排————————————————————

使用併發編程的最直接目的是保證界面流暢,這也是引擎佔據主線程的緣由。所以,除了界面相關的代碼外,其餘操做均可以放入新的線程中執行,主要包括文件讀寫和網絡通訊兩類。

文件讀寫涉及外部存儲操做,這和內存、CPU都不在一個響應級別上。若是將其放入主線程中,就可能會形成阻塞,尤其嚴重的是大型圖片的載入。對於碎圖壓縮後的大型紋理和高分辨率的背景圖,一次加載可能耗費0.2 s以上的時間,若是徹底放在主線程內,會阻塞主線程至關長的時間,致使畫面停滯,遊戲體驗很糟糕。在一些大型的卷軸類遊戲中,這類問題尤其明顯。考慮到這個問題,Cocos2d-x爲咱們提供了一個異步加載圖片的接口,不會阻塞主線程,其內部正是採用了新建線程的辦法。

併發編程————
p456- 472


跨平臺——————————————————————————————————
Cocos2d-x對於iOS、Android和Windows已經支持已久了,而最近的Cocos2d-x將支持推廣到了微軟的Windows 8(指Windows 8的現代UI,曾經代號爲Metro)和Windows Phone 8兩大移動平臺上。同時,藉助Cocos2d-HTML5,也能夠輕鬆開發瀏覽器上的遊戲。

win8
能夠不做任何修改便可直接移植到Windows 8平臺上,而咱們只須要在Windows 8的Visual Studio 2012上從新編譯一次便可。

可能引發迷惑的是底層繪圖的相關調用。目前,Windows 8的現代風格界面應用僅由DirectX提供繪圖支持,也就是說,咱們必須放棄OpenGL繪圖API。好在兩者區別不是特別大,實現一樣的功能幾乎都能互相找到替代品,只須要簡單地替換便可。

Windows Phone平臺
爲了保護Windows Phone 7的底層硬件,並無開放C++接口,而是統一使用C#進行開發。因而Cocos2d-x也就擴展到了Cocos2d-x for XNA版本,使用XNA支持底層繪圖,向上提供幾乎和同期C++版本徹底一致的開發接口。加上C#和C++同爲面向對象的語言,實際上,不須要太多的改動就能夠將遊戲從其餘平臺移植到Windows Phone 7平臺上。


Cocos2d-HTML5————————————————

最後值得一提的是Cocos2d-HTML5這一版本。這是Cocos2d搭建在HTML5上的版本,基於Canvas繪圖,使用JavaScript開發。

首先,如今任何一個操做系統都必定具有瀏覽器,基於HTML5編寫的遊戲能夠說天生具有了跨平臺的特性。其次,JavaScript做爲一種腳本語言能夠直接運行在任何設備之上,Cocos2d-x與Cocos2d-iPhone都提供了一套徹底兼容Cocos2d-HTML5的API,這意味着咱們編寫的Cocos2d-HTML5遊戲不須要進行額外的工做就能夠無縫集成到現有引擎之中。

/************************************************************************
HTML(Hyper Text Mark-up Language )即超文本標記語言,是 WWW 的描述語言,由 Tim Berners-lee提出。設計 HTML 語言的目的是爲了能把存放在一臺電腦中的文本或圖形與另外一臺電腦中的文本或圖形方便地聯繫在一塊兒,造成有機的總體,人們不用考慮具體信息是在當前電腦上仍是在網絡的其它電腦上。這樣,你只要使用鼠標在某一文檔中點取一個圖標,Internet就會立刻轉到與此圖標相關的內容上去,而這些信息可能存放在網絡的另外一臺電腦中。

HTML文本是由 HTML命令組成的描述性文本,HTML 命令能夠說明文字、 圖形、動畫、聲音、表格、連接等。 HTML的結構包括頭部 (Head)、主體 (Body) 兩大部分。頭部描述瀏覽器所需的信息,主體包含所要說明的具體內容
************************************************************************/

/************************************************************************
Canvas 對象表示一個 HTML 畫布元素 -<canvas>。它沒有本身的行爲,可是定義了一個 API 支持腳本化客戶端繪圖操做。
你能夠直接在該對象上指定寬度和高度,可是,其大多數功能均可以經過CanvasRenderingContext2D 對象得到。 這是經過 Canvas 對象的getContext() 方法而且把直接量字符串 "2d" 做爲惟一的參數傳遞給它而得到的。
************************************************************************/

移植————————————

對於採用Cocos2d-x引擎實現的跨平臺遊戲,iOS、Android、Windows Phone 8三個基礎的平臺是能夠無縫切換的,許多狀況下只要從新編譯就能夠了。
而若是是Windows Phone 七、瀏覽器這些非原生Cocos2d-x支持的平臺,那麼咱們將面臨大量一比一轉換的代碼,這時候能夠考慮先使用語言間的機械轉換機,結合正則表達式等全文替換,將遊戲移植到目標平臺後再進行細微的調試。

而對於從其餘引擎轉入Cocos2d-x引擎的遊戲,實際上至關於一次重構。從如今的發展方向看,幾乎不可能丟下iOS、Android和Windows Phone中的任何一個平臺,橫跨全部平臺是大勢所趨,咱們推薦在這個時候使用JavaScript開發基於Cocos2d-HTML5的遊戲,利用強大的可移植性爲咱們的開發節省大量時間。

 

Cocos2d-HTML5——————————————————————————

在前面的章節中咱們已經提到,目前移動設備的遊戲開發趨勢是多平臺開發,Cocos2d-x就是爲了實現讓遊戲運行在多個平臺而開發的引擎。雖然引擎已經作到了iOS、Android與Windows下的跨平臺,但對於新興的HTML5平臺來講則無能爲力,Cocos2d-HTML5就是爲了解決這個問題而產生的。

概述 ?
Cocos2d-HTML5是基於HTML和JavaScript的遊戲引擎,採用HTML5提供的Canvas對象與DOM進行繪圖,於是使用Cocos2d-HTML5建立的遊戲能夠運行在各類主流的瀏覽器上,不須要依賴操做系統。表19-1列舉出了各類主流平臺與瀏覽器對HTML5的支持狀況。能夠看出,到目前爲止,Cocos2d-HTML5能夠運行在絕大多數平臺上。從這個角度來說,Cocos2d-HTML5是首個真正實現平臺無關開發的Cocos2d引擎。

基於Cocos2d-HTML5開發遊戲時,遊戲邏輯採用JavaScript實現。JavaScript是一種動態、弱類型、基於原型(prototype)的腳本語言,能夠直接在各類主流的瀏覽器上運行

採用Cocos2d-HTML5開發的遊戲能夠運行在幾乎全部的圖形操做系統之上,只要此係統擁有支持HTML5的瀏覽器,就能夠運行咱們開發的遊戲。一樣,在任何擁有HTML5瀏覽器的操做系統上,咱們均可以進行遊戲開發。

只須要一個文本編輯器就能夠編寫遊戲代碼,只須要一個支持HTML5的瀏覽器就能夠運行遊戲。相對於其餘的Cocos2d版本而言,Cocos2d-HTML5對開發環境的要求是最低的。

在Cocos2d-HTML5開發環境中,咱們必須擁有的兩個工具是文本編輯器和瀏覽器。文本編輯器用於建立並編輯遊戲代碼,實現遊戲開發中全部的編碼工做,瀏覽器用於運行和調試遊戲。這兩個工具構成了最基本的Cocos2d-HTML5開發環境

當咱們擁有了基本的開發環境以後,理論上已經就能夠任意地開發遊戲了。然而文本編輯器畢竟只是一個簡單的文字編輯工具,在更復雜的開發工做中,單純的文本編輯器就顯得軟弱無力了。在JavaScript工程的開發中,也有功能強大的IDE,在此咱們向讀者推薦JetBrains公司的開發工具WebStorm。 ?

Cocos2d-HTML5能夠運行在任何瀏覽器中,固然也能夠部署到Chrome Web Store。通俗地講,Chrome Web Store是Chrome瀏覽器版本的App Store。與移動設備應用商店不一樣的是,Chrome Web Store提供的是運行在瀏覽器下的網頁應用。使用Cocos2d-HTML5移植的《捕魚達人》不久以前就在Web Store中發佈了。

開發環境介紹 ?
與Cocos2d-x開發的流程同樣,在正式開始開發遊戲以前,咱們須要搭建開發環境。Cocos2d-HTML5所採用的開發環境由如下幾個部分組成。 ?
編輯器:用於編輯項目的代碼文件,對於中大型項目,建議使用集成開發環境來編寫 ? 代碼。 ?
瀏覽器:用於查看遊戲運行效果並進行調試。

Web服務器:用於託管遊戲內容,這是可選組件。Web服務器主要用來解決Chrome的安全性問題,所以使用Firefox的用戶能夠沒必要安裝。

Cocos2d採用XMLHttpRequest對象來讀取文件。對於偏好使用Chrome瀏覽器的用戶來講,它的默認安全配置禁止了本地HTML文件使用XMLHttpRequest對象,咱們有兩個辦法來解決這個問題。第一個辦法是配置一個Web服務器端,把遊戲頁面發佈到網站中;第二個辦法是啓動Chrome的時候加入-allow-file-access-from-files或-disable-web-security參數。

一般,咱們能夠選擇輕量級的文本編輯器,如免費的EditPlus,也能夠選擇較爲複雜的IDE;對於瀏覽器,咱們能夠選擇目前執行效率最高的Google Chrome;對於Web服務器,咱們能夠選擇開源軟件Apache或微軟IIS。

搭建開發環境 開始開發————————

p487 -509

命名原則————————————————————————————————————————————
移植
正是因爲不一樣平臺下的Cocos2d都必定程度地遵循着一樣的命名原則,才使得遊戲的移植成爲可能。

不管是Cocos2d-iPhone,仍是派生自Cocos2d-iPhone的Cocos2d-x,甚至派生自Cocos2d-x的Cocos2d-XNA與Cocos2d-HTM5,都遵循相似Objective-C的命名原則

類名稱
Cocos2d的全部類名稱都包含"CC"前綴

類函數
在Objective-C中,並不存在嚴格意義上的構造函數,開發者須要調用以"init"爲前綴的初始化方法,或者類提供的靜態工廠函數來建立類

屬性
Objective-C使用@property關鍵字來實現屬性,這種方法在本質上把建立get和set訪問器的工做交給了編譯器。

選擇器
選擇器是Objective-C中用來代替類函數指針的類型。

全局變量、函數與宏
對於C#來講,它徹底沒有全局變量、函數和宏的概念。所以對於全局變量和函數,咱們只能採用靜態類的方式來模擬。例如,能夠建立一個Global類,在Global中添加靜態變量以及靜態函數

avaScript沒有C#嚴格,它容許存在全局變量和函數,可是爲了不變量名稱產生衝突,咱們能夠把全部的全局變量和函數保存到一個全局對象中。例如,在Cocos2d-HTML5中全部的類、對象、函數都保存在cc全局對象中
JavaScript一樣不支持宏,所以咱們只能利用代碼來實現宏的效果。對於C語言中的條件編譯,咱們能夠採用if語句來判斷

跨語言移植—————————————————————————————————————————————
許多狀況下,第一次開發所採用的平臺並不必定合適,例如許多遊戲採用Cocos2d-iPhone開發,但隨着遊戲的成功,開發者決定把遊戲平臺擴展到Android,這就須要把基於Cocos2d-iPhone的遊戲移植到Cocos2d-x下。而在其餘狀況下,當但願把遊戲平臺進一步擴展到Windows Phone 7上時,則須要把Cocos2d-x代碼移植到Cocos2d-XNA上;當但願把遊戲改寫成基於JavaScript的腳本代碼以便部署和升級維護時,則須要把遊戲移植到Cocos2d-HTML5上。

不管是Cocos2d-iPhone、Cocos2d-x、Cocos2d-XNA,仍是Cocos2d-HTML5,每一種遊戲引擎採用的語言都不相同,所以咱們就須要進行一項十分煩瑣的工做:把一種語言實現的遊戲轉換成另外一種語言實現。以《捕魚達人》爲例,現行Android版本的《捕魚達人》1.6.3版本的代碼量高達40 000餘行,若是逐行移植這個遊戲,那將是一個不可想象的龐大工程。爲此,咱們將在下面介紹跨語言移植遊戲的基本步驟以及技巧。

爲了便於介紹,咱們把移植遊戲的過程劃分爲相對獨立的3個階段,每一個階段在開始以前都須要完成前一個階段。而相對地,在每一個階段中多我的員能夠同時進行移植工做,這使得移植速度得以保障。

第一階段:代碼移植————————————————

最初咱們須要進行的任務是把原遊戲代碼移植到目標平臺上。爲了便於描述,咱們把待移植的版本稱做原遊戲,把原遊戲使用的平臺稱做原平臺。相對地,咱們把新的遊戲稱做目標遊戲,把新的平臺稱做目標平臺。

在這個階段,咱們首先須要選取一個版本的遊戲做爲原遊戲,而後在目標平臺上對原遊戲的代碼進行1:1的翻譯。若是按照翻譯方式進行分類,則能夠把翻譯的順序分爲自頂向下與自底向上兩種。自頂向下的移植適合進行小規模的全人工移植,一般不易出錯,但速度較慢;自底向上的移植適合配合自動化工具進行大規模移植,速度較快,可是因爲容易出錯,還須要安排嚴密的測試。

自頂向下的移植
在咱們所說的"自頂向下"的移植方式中,"頂"指的是代碼的高級架構,即類與類之間的關係,而"下"則指的是具體的語句。自頂向下的移植方式相似於工程開發的過程。回想在遊戲開發中,咱們一般先搭建遊戲的架構,建立好全部的結構以後再逐個進行實現,最終完成遊戲。自頂向下的移植方式與這種工做方式相似,咱們一樣根據原遊戲的架構在目標平臺上建立一個一致的架構,而後開始向框架中添加代碼。

當框架建立好後,翻譯工做對整個工程翻譯的完成度依賴不大,所以能夠把工做分配給多我的員並行完成,以加快翻譯的速度。

自頂向下移植的優點在於,移植過程當中首先建立與原遊戲一致的架構,而這個過程是人工完成的,一般不易在架構的層次上出現錯誤,所以即便遇到了問題也能夠輕易排查並解決——————須要良好的閱讀代碼能力 才能看懂框架!!!!!!!!!!!!
而自頂向下移植的缺點在於不便於引入自動化工具,整個代碼移植的過程都須要人工參與,對於大型項目來講開銷較大。所以,自頂向下的移植方式很是適合小型項目的移植。

自底向上的移植
自底向上的移植方式指的是在不創建遊戲架構的狀況下,先直接對源碼逐行進行對等的移植工做,而後再處理模塊間的關係,最終完成整個架構的移植。在這種移植方式中,咱們的工做主要集中於兩個部分:第一部分是逐行的代碼翻譯,第二部分是修正模塊間的關係。
在自頂向下的翻譯中,因爲工做流程是首先創建架構,而後向架構中填充代碼,填充的代碼分散在各個函數或方法中,所以並不適合利用自動化的翻譯工具進行處理。然而當咱們採用自底向上的移植方法時,由於首先須要對所有代碼進行翻譯,而後再對架構上的錯誤進行修正,因此能夠直接在翻譯的時候引入自動化翻譯工具。

逐行翻譯代碼其實是一種不斷重複的體力勞動,這種工做徹底能夠利用工具或腳原本代替人們的勞動,所以在此處咱們引入自動化的翻譯工具,用來下降逐行翻譯代碼的工做量。然而翻譯工具並不能徹底代替人們的工做。因爲跨語言的翻譯是一項十分困難的工做,工具一般也只能完成一部分工做,而且翻譯的結果並不能保證徹底正確。所以,咱們利用工具翻譯代碼後還須要大量時間來修復。

第一類翻譯工具的原理是正則表達式替換。正則表達式替換徹底能夠勝任許多簡單的轉換工做,例如C++中的域符號"::"、箭頭符號"->"、點符號".",它們都等價於C#中的點符號".",因而咱們能夠建立一個正則表達式的替換,把C++中的3種符號都替換成點符號。第一類翻譯工具實現較爲簡單,任何一個開發者均可以輕鬆地編寫正則表達式,然而正則表達式沒有能力識別複雜的語句,處理後還須要手動修復許多代碼。即便如此,這一類翻譯工具仍然能夠爲咱們減小許多時間。下面列舉經常使用的此類型的工具或腳本

第二類翻譯工具又稱做翻譯編譯器(source-to-source compiler、transcompiler或transpiler)。第一類翻譯器只能轉換詞法以及部分簡單的語法,而第二類翻譯器的原理是語法分析,根據分析的結果生成目標語言的代碼。它的原理與編譯器相似,相對於第一類翻譯工具,這類翻譯工具能夠轉換較爲複雜的語句,甚至直接處理整個工程。能夠說,第二類翻譯器是比較理想的翻譯工具,但越是強大的工具,數量就越少。

C++ to C# Converter
ObjC2J:由Andremoniy開發的開源翻譯器,能夠把Objective-C代碼翻譯成對應的Java代碼。
Emscripten:一個免費的JavaScript代碼生成器,接受任何LLVM支持的語言(如C和C++)並生成等價的JavaScript代碼。

翻譯主要能體現出兩個問題。
第一個問題是從支持宏的語言翻譯到不支持宏的語言(例如從C++翻譯到C#),宏的翻譯容易出現錯誤,此時咱們就必須手工修復宏的翻譯了。
第二個問題是翻譯工具對於每條語句的把握相對較爲準確,可是對於繼承關係的翻譯經常會出錯,若是忽略了檢查工做,在測試階段這種問題一般難以察覺與修復,所以翻譯事後須要逐個類檢查繼承、重載關係,確保目標遊戲的架構與原遊戲一致。

4個遊戲引擎在內存管理方面並不徹底一致。
基於Objective-C的Cocos2d-iPhone與基於C++的Cocos2d-x所使用的語言並不具有垃圾回收器,須要手動管理內存,而基於C#的Cocos2d-XNA和基於JavaScript的Cocos2d-HTML5則能夠使用垃圾回收器來自動管理內存。所以,在前兩種語言中,咱們不得不採起本書第一部分介紹的引用計數機制來管理對象的釋放時機;而在後兩種語言中,咱們不須要關心對象的內存管理,所以全部的內存管理語句(如retain方法等)都沒必要保留。 ?


第二階段:消除平臺差別(1)————————————
完成了代碼翻譯後,咱們還面臨着許多問題。此時咱們的遊戲仍是不能編譯運行,由於遊戲中多多少少會用到一些非遊戲引擎的庫,其中一部分是語言的公共庫,另外一部分是第三方庫,如各類社交網絡的分享API。所以,咱們須要修改代碼來實現與新平臺上各類庫或API的對接。

對於公共庫而言,最值得咱們討論的功能涵蓋了容器、XML文件處理和網絡通訊這3個方面,其餘公共庫的功能雖然也可能使用,可是用到的頻率不高

容器————

XML處理
對於通常化的XML文件,在各個平臺下的處理方式不盡相同。不一樣平臺下有許多可供選擇的XML庫

在使用Objective-C或C++進行開發的時候,不但能夠使用各自語言中的庫,也能夠使用標準的C語言庫。因爲C語言庫能夠同時在3種語言中使用,十分便於移植,所以,LibXML2也是一個很好的選擇。C#與JavaScript都有各自提供的XML解析器,一般不必去使用第三方庫。

網絡通訊
Cocos2d系列引擎並無負責網絡通訊的模塊。若是咱們開發的遊戲用到了網絡通訊的功能,就須要使用第三方庫,這爲咱們的移植帶來了一些麻煩。

在遊戲開發中,許多簡單的通訊使用HTTP實現。HTTP在各個平臺的開發中都是十分常見的,每一個平臺下都有許多可供咱們選擇的庫來使用。例如,Cocos2d-x官方推薦使用libcurl來實現HTTP、FTP等協議的通訊。libcurl使用C語言實現,所以能夠同時在C、C++以及Objective-C中使用而沒必要額外移植。在表20-5中,咱們簡單地列舉了各個平臺下經常使用的HTTP通訊庫。

HTTP是無狀態的短鏈接協議,所以適合用於進行簡單的通訊。而當須要開發實時在線的網絡遊戲時,就須要使用網絡套接(socket)來保持長鏈接了。在各個平臺下,套接字的實現也不徹底相同,然而因爲大多數操做系統都支持POSIX標準,套接字也是此標準的一部分,所以在C、C++以及Objective-C中,咱們均可以使用標準BSD Socket函數來實現長鏈接。在C#中,套接字的實現位於System.Net.Sockets命名空間中,而JavaScript則能夠使用WebSocket來實現長鏈接。


第三方庫

在遊戲中,咱們常常會使用到第三方提供的廣告平臺、遊戲中心與社交網絡等組件,這些API一般以閉源庫的形式提供給咱們。當咱們的遊戲須要移植到另外一個平臺時,這些閉源庫就必須替換爲目標平臺上的庫了。

替換第三方庫是有條件的,那就是咱們所使用的庫必須在目標平臺上有對應的版本,不然替換就無從談起。根據目標平臺的不一樣以及庫所在語言的不一樣,替換第三方庫的難度也會有所不一樣。替換時可能會有如下3種狀況,咱們一一討論

第一種狀況,在目標平臺上,引擎所使用的語言與庫的語言相同,此時庫的替換是最輕鬆的。例如,當咱們打算把Cocos2d-x移植到Cocos2d-XNA,遊戲中使用的MD5算法庫剛好又有C++與C#兩個版本,那麼咱們只須要把目標遊戲的代碼中涉及MD5算法庫的調用替換爲C#版本的代碼便可。值得注意的是,在Windows Phone 7平臺上存在兩種應用--Silverlight與XNA,二者並不能友好地共存。Cocos2d-XNA建立的遊戲是XNA應用,所以基於Silverlight的許多廣告API就不能用於遊戲開發。 ?

第二種狀況,當咱們的目標平臺是Android時,也許只能找到Java版本的庫供咱們使用,例如許多廣告平臺都只提供Android上的Java庫。若是咱們的遊戲是基於C++開發的,並不能直接使用Java上的第三方庫。所幸Android NDK提供了Java到C++的綁定功能,稱爲Java Native Interface(JNI),使得咱們能夠在Java與C++代碼中互相自由調用函數。關於JNI技術,限於篇幅,咱們在此不作介紹。

第三種狀況,當庫沒有提供目標平臺上的實現時,咱們就幾乎無能爲力了。這種狀況一般在JavaScript語言上出現,由於JavaScript仍然是一個十分年輕的新平生臺,大多數第三方庫並無提供對JavaScript的支持。所幸基於JavaScript的Cocos2d-HTML5並非只能運行在瀏覽器中,當它運行在Cocos2d-iPhone或Cocos2d-x的腳本引擎中時,若是庫提供了iOS版本或Android版本,則咱們能夠把庫經過腳本引擎的JavaScript綁定功能暴露給腳本,從而實現JavaScript上的庫替換。舉一個簡單的例子,咱們須要把一個使用AdMob廣告平臺的遊戲移植到Cocos2d-HTML5中,並使用Cocos2d-x在移動設備上以腳本的形式運行遊戲,則須要如下3個步驟。

利用Android NDK的JNI工具在C++代碼中建立AdMob的包裝。 ?
在Cocos2d-x初始化時,利用JavaScript綁定技術把AdMob在C++中的包裝暴露到腳本引擎中。
在JavaScript代碼中調用AdMob的相關函數。 ?

其餘問題 然而Android以及Windows Phone設備衆多,屏幕像素可謂五花八門,最新的iPhone 5也採用了與上一代手機徹底不一樣的屏幕比例。在這種狀況下,咱們就須要對遊戲的佈局系統進行從新設計,以保證遊戲能夠運行在不一樣分辨率下。

相關文章
相關標籤/搜索