程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> 書生教你cocos2d-x-保衛蘿卜(二)

書生教你cocos2d-x-保衛蘿卜(二)

編輯:關於C語言

書生教你cocos2d-x-保衛蘿卜二)

      上一章搭建了主界面。這一章開始,我們構建游戲裡要用的動畫類。動畫是游戲開發裡很重要的一個概念,可惜的是公司不會安排一個新人去寫這一塊。許多新人朋友接手代碼的時候,這一塊已經完成了。他們會以為我要創建動畫,只要new一個animation抑或是create一個場景裡的對象就行了。這就直接導致當策劃告訴你“我希望主角在攻擊的第三幀發出子彈”時,新人朋友會回答“什麼是幀?”。因此,我們在這個游戲裡拋棄cocos2d-x的動畫類,寫一個屬於我們自己的。如果有機會,以後加入切片,到時候把module映射的概念也分享給大家。

大概用2個篇幅記錄開發動畫類的過程。本文介紹最初的分析,以及cocos2d-x給我們哪些接口,讓我們實現動畫類。下一篇會給大家一個封裝好的動畫類,這些測試代碼都會刪掉。本文相關代碼下載地址:

源碼下載地址 http://down.51cto.com/data/1015763

備用地址 http://down.51cto.com/data/1015743

先明確這個幾個概念,Animation,Action,Frame。

Animation往往指我們創建出來的可繪制的對象類型,譬如說一個主角。

Action是指動作不要和ccaction弄混,不一樣)。一個Animation有很多動作,譬如主角有攻擊動作,跑步動作。譬如說保衛蘿卜裡的敵兵只有跑的動作,而塔有待機和攻擊兩種動作。蘿卜則有個調皮和眨眼的動作。

Frame是幀,一個動作不是一瞬間就沒了,也往往不是靜止不動的。譬如說攻擊可能是抬刀,揮刀,收刀三幀。即是說這個動作有3幀組成。

切片和module在保衛蘿卜這個游戲裡用不到,以後再說吧。停留幀什麼的,遇到了就告訴大家。

大致可以理解為,一個動畫有很多動作,一個動作有好幾幀。而如何描述記錄這些信息呢,我們不需要記錄在創建出來的每個animation對象裡。因為就算你創建出N個相同的人物動畫,他們有幾個動作,每個動作有多少幀,每幀用哪張紋理,這些信息是相同的。這些數據只要一份就行了。叫AnimationData太難聽了,我們叫AnimationBase好了。

那麼我們動畫類的結構就出來了。

1.FrameAnimationBase,這個類記錄某個動畫的基本信息,描述它有多少動作,動作有哪些幀,用到什麼圖之類的。

2.FrameAnimation,這個類是根據base創建出來的可見的動畫對象。它具有切換動作的接口play),以及更新顯示,讓自己從這一幀切換到下一幀的的接口step)。

如果你下載源碼,會發現還有這麼一個類。

3.AnimationManager ,這個類是動畫管理類,全局一份,本文裡沒有實現,之後會整理代碼,使它具有幫助我們管理動畫資源,以及自動更新動畫播放的功能。


下面看代碼,TestScene是我們專門用於單元測試的場景,我們在這裡測試某個功能,確定OK後,才用到游戲裡去。點擊主界面上的新浪圖標,會進到這裡來。

image

左邊有四個精靈,其中一個會自動切幀,動來動去的。

右邊有四個按鈕,第一個是返回主菜單。下面三個分別對應靜止的三個精靈。點擊按鈕,精靈顯示會改變,切到下一幀。我們一個一個看。

bool TestLayer::init(){
if(!cocos2d::CCLayer::init()){
returnfalse;
    }
    cocos2d::CCSize win_size=cocos2d::CCDirector::sharedDirector()->getWinSize();
    cocos2d::CCSpriteFrameCache::sharedSpriteFrameCache()->addSpriteFramesWithFile("Items04.plist");    
    cocos2d::CCMenu* menu=cocos2d::CCMenu::create();
    menu->setPosition(ccp(0,0));
this->addChild(menu);
    cocos2d::CCMenuItemSprite* bac_btn=GCreateBtnWithFrameSprite("NT-1.png","NT-2.png");
    menu->addChild(bac_btn);
    bac_btn->setPosition(ccp(win_size.width*0.9,win_size.height*0.9));
    bac_btn->setTarget(this,menu_selector(TestLayer::BtnBackCallBack));
    TestStepFrame();
    TestAnimation();
returntrue;
}

首先創建一個返回按鈕,點擊回主界面,這個不用說。其中的GCreateBtnWithFrameSprite是我在工具類裡封裝的一個方便我創建按鈕的函數,大家有興趣可以自己看一看。

TestStepFrame()是我們測試幀切換的相關代碼,進去看看。

void TestLayer::TestStepFrame(){
    cocos2d::CCSize win_size=cocos2d::CCDirector::sharedDirector()->getWinSize();
    cocos2d::CCSpriteFrameCache::sharedSpriteFrameCache()->addSpriteFramesWithFile("Monsters01.plist");    //測試精靈test_frame_sprite=cocos2d::CCSprite::createWithSpriteFrameName("fly_boss_yellow01.png");this->addChild(test_frame_sprite);
    test_frame_sprite->setPosition(ccp(win_size.width*0.1,win_size.height*0.9));//菜單cocos2d::CCMenu* menu=cocos2d::CCMenu::create();this->addChild(menu);
    menu->setPosition(ccp(0,0));//測試按鈕cocos2d::CCMenuItemSprite* btn_step_frame=  GCreateBtnText("stepframe");
    menu->addChild(btn_step_frame);
    btn_step_frame->setPosition(ccp(win_size.width*0.9,win_size.height*0.8));
    btn_step_frame->setTarget(this,menu_selector(TestLayer::btnTestFrameCallBack));//測試精靈autostep_frame_sprite=cocos2d::CCSprite::createWithSpriteFrameName("fly_boss_yellow01.png");this->addChild(autostep_frame_sprite);
    autostep_frame_sprite->setPosition(ccp(win_size.width*0.3,win_size.height*0.9));this->schedule(schedule_selector(TestLayer::Autostepframe),0.3);
}
這裡創建了2個精靈,test_frame_sprite和autostep_frame_sprite

由於他們用的圖片和切片信息都來自Monsters01.plist,所以我們先手動緩存了這個文件關聯的spriteframe。

對spriteframe不熟悉的朋友老老實實回去看我上一篇講主界面的博文。

image

剛創建出來應該是這樣的。之後有個btn_step_frame按鈕,對應了一個回調TestLayer::btnTestFrameCallBack。同時這個layer也每0.3也會調用一個回調TestLayer::Autostepframe

看看這兩個函數的內容

void TestLayer::btnTestFrameCallBack(cocos2d::CCObject* pSender){static std::string currer_frame="fly_boss_yellow01.png";if(currer_frame=="fly_boss_yellow01.png"){
        cocos2d::CCSpriteFrame* frame=cocos2d::CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName("fly_boss_yellow02.png");
        test_frame_sprite->setDisplayFrame(frame);
        currer_frame="fly_boss_yellow02.png";
    }else{
        cocos2d::CCSpriteFrame* frame=cocos2d::CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName("fly_boss_yellow01.png");
        test_frame_sprite->setDisplayFrame(frame);
        currer_frame="fly_boss_yellow01.png";
    }
}

void TestLayer::Autostepframe(float dt){static std::string currer_frame="fly_boss_yellow01.png";if(currer_frame=="fly_boss_yellow01.png"){
        cocos2d::CCSpriteFrame* frame=cocos2d::CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName("fly_boss_yellow02.png");
        autostep_frame_sprite->setDisplayFrame(frame);
        currer_frame="fly_boss_yellow02.png";
    }else{
        cocos2d::CCSpriteFrame* frame=cocos2d::CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName("fly_boss_yellow01.png");
        autostep_frame_sprite->setDisplayFrame(frame);
        currer_frame="fly_boss_yellow01.png";
    }
}
內容差不多,每次執行回調,會改變精靈對應的紋理和紋理區域,

test_frame_sprite->setDisplayFrame(frame);

setDisplayFrameframe)這個函數的意思是設置一個新的SpeiteFrame改變精靈的顯示。之前說了CCSpriteFrame是對 紋理以及紋理中的顯示區域的描述,叫切片信息更合適。但是由於早期做iphone游戲那會,大家認為iphone內存夠大,往往一個切片就是整個一動畫幀的顯示了沒有分成手,腳,身體來拼湊,全部畫出來了),所以cocos2d-x那幫人把它命名為CCSpriteFrame精靈幀)了。

image

點擊按鈕或等0.3秒,我們的精靈的顯示就被刷新了。這個效果就是我們要的幀的切換。不過現在還沒有加入動作的概念,所以最多用它做個金幣翻轉什麼的效果,沒法做動作。但是這個效果確實實現了幀的切換,之後我們以它為基礎,實現動畫。


下面我們看testaniamtion),內容比剛才多點。2個部分,先看前面

//測試stepFrameAnimationBase* base=FrameAnimationBase::create();base->retain();base->AddFrame("run","land_boss_pink01.png");base->AddFrame("run","land_boss_pink02.png");

     ani=FrameAnimation::create(base);this->addChild(ani);
    ani->setPosition(ccp(win_size.width*0.1,win_size.height*0.7));


我們創建了一個AnimationBase,往裡面添加了一個叫“run”的動作,這個動作有2幀,之後基於這個base創建創建了一個實例化動畫ani

ps我寫代碼不用插件,base在我的vs裡不變色,大家寫代碼要注意,不要用容易和關鍵字混淆的變量名,別用這個變量名)

用著是挺簡單,但是可能有人看不懂,我們去看看AnimationBase的結構。

class FrameAnimationBase:public cocos2d::CCObject{
public:
    friend class FrameAnimation;
virtual ~FrameAnimationBase();
static FrameAnimationBase* create();
bool init();
void AddFrame(std::string action_name,cocos2d::CCSpriteFrame* frame);
void AddFrame(std::string action_name,std::string  frame_name);
private:
    FrameAnimationBase();
    cocos2d::CCDictionary* action_dic;
    std::string default_action;
    cocos2d::CCSpriteFrame* GetFrame(std::string action_name,int frame_index);
};


由於保衛蘿卜資源的問題,我們這次實現的動畫是幀動畫,因此我最終把這個動畫類的名字加了Frame前綴。

之前說了,FrameAnimationBase是記錄並描述一個動畫有多少動作,每個動作有哪些幀的數據信息。

因此它有一個動畫列表。action_dic,key是動作名字,value是一個幀列表。

default_action是我“畫蛇添足”加上去的,因為我們創建的動畫對象必須有個默認動作。

AddFrame動作名,動畫幀)是我們添加數據的入口。如果沒有動作,會創建一個新動作把幀加進去,如果已有動作,會直接把幀加進去。下面看具體實現。


void FrameAnimationBase::AddFrame(std::string action_name,cocos2d::CCSpriteFrame* frame){
    cocos2d::CCArray* frame_array=dynamic_cast<cocos2d::CCArray*>( action_dic->objectForKey(action_name));
if(frame_array==NULL){
        frame_array=cocos2d::CCArray::create();
        action_dic->setObject(frame_array,action_name);
if(default_action==""){
            default_action=action_name;
        }
    }
    frame_array->addObject(frame);
}
void FrameAnimationBase::AddFrame(std::string action_name,std::string  frame_name){
    cocos2d::CCSpriteFrame* frame=cocos2d::CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(frame_name.c_str());
    AddFrame(action_name,frame);
}

我們要把frame加入一個動作名為action_name的動作裡去。

則先會判斷有沒有這個動作的幀隊列,如果沒有創建一個新的動作插入動作字典表,然後把幀加入動作對應的幀列表。

ps:1.為了確保每個動作都至少有一幀,我們沒開放單獨的創建空動作的接口。

       2.這裡用的CCArray和CCDictionary數據結構未必是高效的,但是寫代碼切記“無目的的優化會導致代碼寫不下去”,同時畢竟是測試代碼,我們只在有需求時才去優化。

對應使用base的代碼

FrameAnimationBase* base=FrameAnimationBase::create(); base->retain();

base->AddFrame("run","land_boss_pink01.png");

base->AddFrame("run","land_boss_pink02.png");

創建了一個動畫base,它有一個run的動作,這個動作有2幀,由於run是它的第一個動作,所以默認動作就是run。


然後看我們是如何創建實例化的Animation對象的。先看Animation的結構

class FrameAnimation :public cocos2d::CCNode{
public:
virtual ~FrameAnimation();
static FrameAnimation* create(FrameAnimationBase* base);
bool init(FrameAnimationBase* base);
void Step();
void Play(std::string action_name);
private:
    FrameAnimation();
    std::string currer_action_name;
int currer_frame_index;
    FrameAnimationBase* base;
    cocos2d::CCSprite* sprite;
};
由於是要加入場景中的可繪制對象,所以繼承了CCNode,內部有一個CCSprite,創建函數必須有個AnimationBase為基礎。

currer_action_name是當前動作的名字currer_frame_index是當前繪制的幀的索引,這2個變量決定了當前精靈是哪個動作的第幾幀

step)是切換幀的接口,而Play)是切片動作的接口。

PS:成員變量base變色的問題,大家自己注意,不要這麼命名變量,這麼寫是不好的,會和關鍵字混淆。

bool FrameAnimation::init(FrameAnimationBase* base){
if(!cocos2d::CCNode::init()){
returnfalse;
    }
this->base=base;
    std::string default_action=base->default_action;
    currer_action_name=default_action;
    cocos2d::CCSpriteFrame* frame=base->GetFrame(default_action,0);
     sprite=cocos2d::CCSprite::createWithSpriteFrame(frame);
this->addChild(sprite);
returntrue;
}
初始化函數裡,根據一個base創建,取得默認動作名,取得這個動作的第一幀,用之前說的方法,創建精靈。

再看切換幀的函數step)

void FrameAnimation::Step(){
    cocos2d::CCArray* frame_array=dynamic_cast<cocos2d::CCArray*>(base->action_dic->objectForKey(currer_action_name));
if(currer_frame_index<frame_array->count()-1){    
        currer_frame_index++;
    }else{
        currer_frame_index=0;
    }
    cocos2d::CCSpriteFrame* frame=base->GetFrame(this->currer_action_name,currer_frame_index);
    sprite->setDisplayFrame(frame);
}
嘗試根據動作名和當前幀,從base裡取下一幀的信息,切換精靈的顯示。
如果已經是最後一幀了,回到第一幀,實現循環播放。
PS:這裡的寫法不肯定是不高效的,同時我們也可以把播放方式從循環播放擴充成倒序播放,播放停留在最後一幀等等,但是“沒有需求就沒有必要優化”。所以就這樣就暫時夠我們用了。
之後再看切換動作的接口Play動作名)
void FrameAnimation::Play(std::string action_name){this->currer_action_name=action_name;this->currer_frame_index=0;
    cocos2d::CCSpriteFrame* frame=base->GetFrame(currer_action_name,0);this->sprite->setDisplayFrame(frame);
}

切換動作會改變到對應動作的第一幀,開始播放。



然後看我們實際的使用

ani=FrameAnimation::create(base);
this->addChild(ani);
ani->setPosition(ccp(win_size.width*0.1,win_size.height*0.7));

簡單的三句話,就在屏幕上創建了一個實例化動畫對象。


image如果點擊按鈕,會執行step)切到下一幀image,由於只有一個動作,沒什麼好切換的。

此時我們已經實現了動畫類的基本接口了,可以切換動作,可以靠顯性調用step)的方法促使它切幀。有不少改進空間。

目前我們游戲需要的改進有2個:文件讀取和自動播放以及資源管理。

每次手動去step每個animation是很不爽的,所以要有AnimationManager,這個我們下一篇再說。

今天顯示先從文件讀取base的雛形,畢竟每次創建一個animationbase都程序去添加內容是不可行。

//從文件讀取base    cocos2d::CCSpriteFrameCache::sharedSpriteFrameCache()->addSpriteFramesWithFile("Items01.plist");
    tinyxml2::XMLDocument* doc=new tinyxml2::XMLDocument();
    doc->LoadFile("luobo.xml");
    tinyxml2::XMLElement *ani_node=doc->RootElement();  
    std::string ani_name=ani_node->FirstAttribute()->Value();//aniFrameAnimationBase* luobo_base=FrameAnimationBase::create();//actiontinyxml2::XMLElement *action_node=ani_node->FirstChildElement("action");  while (action_node)  
    {      
        std::string action_name= action_node->FirstAttribute()->Value();        //frametinyxml2::XMLElement *frame_node=action_node->FirstChildElement("frame");while(frame_node){            
            std::string frame_name=frame_node->FirstAttribute()->Value();
            frame_node=frame_node->NextSiblingElement();
            luobo_base->AddFrame(action_name,frame_name);
        }
        action_node=action_node->NextSiblingElement();  
    }  
    luobo_base->retain();



我弄了一個叫“luobo.xml”的文件,裡面記錄了luobo這個動畫的基本信息。然後通過讀取它創建了一個AnimationBase。

xml的結構,大家自己用文本編輯器看一看,很簡單。

//創建實例luobo_ani=FrameAnimation::create(luobo_base);this->addChild(luobo_ani);
    luobo_ani->setPosition(ccp(win_size.width*0.3,win_size.height*0.7));
    luobo_ani->Play("happy");

    cocos2d::CCMenuItemSprite* btn_step_file_ani=  GCreateBtnText("stepFileAni");
    menu->addChild(btn_step_file_ani);
    btn_step_file_ani->setPosition(ccp(win_size.width*0.9,win_size.height*0.6));
    btn_step_file_ani->setTarget(this,menu_selector(TestLayer::btnTestFileAniCallBack));    
之後根據這個base創建了一個動畫實例,並且切換動作到“happy”,還創建了一個按鈕,點擊會觸發它的step
這一段源碼裡沒有,大家可以自己添加。
imageimageimageimage
經測試,運行良好。
到目前為止,這個動畫類似乎可以用了,但是代碼質量不高。沒關系,畢竟只是雛形,這就是測試場景的作用嘛,下一篇我們來優化這些代碼,
把讀取xml的部分封裝進base的創建函數裡,再封裝一些接口,實現manager的功能。到時候就有一個漂亮的動畫類了。
最後,書生強調一點:
沒有目的的優化是沒有意義的。我們只需要做一個滿足我們游戲需求的組件就可以了,這個動畫類到最後會可以添加很多功能,但是是由於我們有具體的需求,
在一次次的版本迭代後,才能成為一個最適合我們這個項目使用的動畫類,想在一開始就憑空寫一個功能強大的組件,只會分散大家的精力,憑空添加一些無用接口罷了。

本文出自 “書生教你cocos2dx” 博客,請務必保留此出處http://luoposhusheng.blog.51cto.com/8148702/1329353

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved