(譯)如何在cocos2d裡面使用動畫和spritesheet
免責申明(必讀!):本部落格提供的所有教程的翻譯原稿均來自於網際網路,僅供學習交流之用,切勿進行商業傳播。同時,轉載時不要移除本申明。如產生任何糾紛,均與本部落格所有人、發表該翻譯稿之人無任何關係。謝謝合作!
原文連結地址:http://www.raywenderlich.com/1271/how-to-use-animations-and-sprite-sheets-in-cocos2d
教程截圖:
在這個部落格中,我收到了大量的讀者來信說,你能不能寫一個關於如何在cocos2d裡面使用動畫和spritesheet的教程。這篇教程就應運而生了!
在這個教程裡,我將向大家展示如何用cocos2d來製作一隻熊在走路的動畫。同時,我會使用spritesheet來使動畫執行效率更高,還有如何讓使用者滑鼠點選決定熊的行走方向,以及怎樣基於熊當前行進的方向改變熊的面朝方向。
如果你對cocos2d完全陌生的話,你可能需要先閱讀《怎麼使用cocos2d來製作簡單的iphone遊戲》這一系列的教程,但是也不一定!(如果說你已經有相關經驗就另當別論了)
Getting Started
讓我們首先建立一個工程骨架--使用cocos2d工程模板建立一個新的專案並取名為AnimBear.
接下來,下載一些由我的老婆製作的熊行走的圖片。(老婆會美工多好啊!)
當你解壓之後,看看那些圖片---它們僅僅是一張張單個的熊在行走的動畫幀。但是,當你把它們連續地放映,就會看到一隻熊在移動。
現在,把這些圖片加到工程裡面,然後基於這些單個的圖片來建立動畫。然後,在cocos2d裡面,還有另一種更加高效的方式來建立動畫--那就是使用spritesheet。(也叫精靈表單)
精靈表單和熊
如果你從來沒有使用過spritesheet,你可以把它看作是一張巨大的圖片,你可以把許許多多的sprite放進去。與spritesheet對應的,還有一個plist檔案,這個檔案指定了每個獨立的sprite在這張“大圖”裡面的位置和大小,當你在程式碼之間需要使用這個sprite的時候,就可以很方面地使用plist檔案中的這些資訊來獲取sprite。
為什麼這會提高效率呢?因為cocos2d對它進行了優化!如果你使用spritesheet來獲取sprite,那麼當場景中有許多sprite的時候,如果這些sprite共享一個spritesheet,那麼cocos2d就會使用一次OpenGL ES呼叫來渲染這些sprite。但是,如果是單個的sprite的話,那麼就會有N次OpenGL ES call,這個代價是相當昂貴的。
簡而言之--使用spritesheet會更快,尤其是當你有很多的sprite的時候!(使用spritesheet還可以減少遊戲佔用的記憶體大小,具體參考我翻譯的文章《在cocos2d裡面如何使用TexturePacker和畫素格式來優化spritesheet》)
由於要使用spritesheet,你當然可以手工用圖片編輯器來建立,然後建立一個plist指定每一個sprite在spritesheet裡面的位置和大小。然後,那樣將會是一個非常傻比的行為,因為Robert Payne已經開發出了一個非常好用的工具,叫做Zwoptex,它可以幫助我們自動生成這一切!
Zwoptex To Victory!
如果你還沒有這個工具,那麼可以從 zwoptexapp.com上面下載。它有一個免費的Flash版本和一個收費的安裝版,但是最近我使用的是可安裝的版本。
安裝完這個工具之後,選擇File\New,然後你將會看到一個空白視窗。開啟你先前下載的熊的圖片,並把它們拖到這個視窗裡面。
你會看到,所有的熊的圖片都層疊在一起。我們需要將他們攤開放在spritesheet上面,因此在Layout部分點選“Apply”來排序。
當你這樣做以後,你會注意到,預設的畫布(512×512)太小了,不足以把所有的熊圖片裝下。所以,還會有一些圖片層疊在一起。因此,我們在Canvas部分把畫布改成512×1024,然後在Layout部分點選“Apply”來重新排序它們。
我們馬上要完成了--但是,請注意,有些熊的圖片比其它寬一些。如果你看一下原圖,你會發現和原圖尺寸不一樣了--這是因為,Zwoptex在預設情況下會把圖片周圍的透明區域剪裁掉。
對於這些圖片,它們並不是我們最終需要的,因為對於動畫來說,這些圖片的位置資訊已經錯亂了(由於透明區域的裁剪)。還好,這非常容易解決--在工具欄上選擇”Untrim“,然後再點”Apply“。
這時,你的視窗可能和下圖類似:
就這麼多,讓我們儲存spritesheet圖片和定義,這樣我們就可以在程式中使用它們啦.
點選Export部分的”Save.png“,把這個檔案取名為”AnimBear.png“並儲存到你的resources資料夾下面。然後點選”Save.plist“,命名為”AnimBear.plist“,同樣儲存到你的resources資料夾下面。
更新:當我們在Zwoptex裡面點選儲存的時候,確保選擇”Cocos2d“作為匯出格式,否則你的plist檔案就不能正確在cocos2d裡面使用!謝謝Muhammad在評論部分給我指出來了!
現在,讓我們回到XCode,然後把剛剛這兩個檔案加進去。或擊點選Resources資料夾,選擇”Add\Existing Files。。。“,選擇AnimBear.png和AnimBear.plist檔案,然後點增加。
好了,讓我們開啟AnimBear.plist檔案,看看Zwoptex到底為我們做了些什麼事。你將會看到它僅僅是一個包含兩個section的屬性檔案--兩個部分分別為frames和metadata。在frames部分,包含了一系列的對spritesheet中每個圖片的描述資訊,這些描述資訊裡面包含了圖片在spritesheet中的位置、大小和名字等資訊。很cool,不是嗎?
但是,如果你能讓這隻熊動起來,那將會更酷!下面就跟著我,一步步地讓熊動起來!
簡單的動畫
首先,讓我們把熊放在螢幕中間,然後迴圈播放所有的動畫幀,這樣看起來熊就在永遠的移動,這裡僅僅是先讓程式碼可以跑起來。
因此,讓我們在HelloWorldScene.h裡面增加一些屬性吧,在那個檔案中做以下修改:
CCSprite *_bear;
CCAction *_walkAction;
CCAction *_moveAction;
BOOL _moving;
// Add after the HelloWorld interface
@property (nonatomic, retain) CCSprite *bear;
@property (nonatomic, retain) CCAction *walkAction;
@property (nonatomic, retain) CCAction *moveAction;
實際上,我們並不是馬上需要所有的這些屬性,但是,我們把它們先定義在這裡,這樣,等下我們就不用回過頭來再改程式碼了。
現在,有趣的部分來了!開啟HelloWorldScene.m,然後作如下改動:
@synthesize bear = _bear;
@synthesize moveAction = _moveAction;
@synthesize walkAction = _walkAction;
// In dealloc
self.bear = nil;
self.walkAction = nil;
// Replace the init method with the following
-(id) init {
if((self = [super init])) {
// Add the stuff from below!
}
return self;
}
為了獲得動畫效果,我們有5個步驟需要做。接下,將會一個步驟一個步驟給大家講解。把下面的一些程式碼片斷按順序增加到你的init的Add the stuff from below註釋後面。
1) 緩衝sprite幀和紋理
@"AnimBear.plist"];
首先,呼叫CCSpriteFrameCache的addSpriteFramesWithFile方法,然後把Zwoptex生成的plist檔案當作引數傳進去。這個方法做了以下幾件事:
- 尋找工程目錄下面和輸入的引數名字一樣,但是字尾是.png的圖片檔案。然後把這個檔案加入到共享的CCTextureCache中。(這我們這個例子中,就是載入AnimBear.png)
- 解析plist檔案,追蹤所有的sprite在spritesheet中的位置,內部使用CCSpriteFrame物件來追蹤這些資訊。
2) 建立一個精靈批處理結點
batchNodeWithFile:@"AnimBear.png"];
[self addChild:spriteSheet];
接下來,建立CCSpriteBatchNode物件,把spritesheet當作引數傳進去。spritesheet在cocos2d中的工作原理如下:
- 你建立一個CCSpriteBatchNode物件,通過傳遞一個包含所有sprite的spritesheet的名字作為引數,並把它加入到當前場景之中。
- 接下來,你從spritesheet中建立的任何sprite,你應該把它當作CCSpriteBatchNode的一個孩子加進去。只要sprite包含在spritesheet中,那麼就沒問題,否則會出錯。
- CCSpriteBatchNode可以智慧地遍歷它的所有的孩子結點,並通過一次OpenGL ES call來渲染這些孩子,而不是以前每個sprite都需要一個OpenGL call,這樣渲染速度就會更快。
注意:CCSpriteBatchNode以前叫做CCSpriteSheet,你可能會在一起比較老的程式碼裡面看見它。
3) 收集幀列表
for(int i =1; i <=8; ++i) {
[walkAnimFrames addObject:
[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:
[NSString stringWithFormat:@"bear%d.png", i]]];
}
為了建立一系列的動畫幀,我們簡單地遍歷我們的圖片名字(它們是按照Bear1.png-->Bear8.png的方式命名的),然後使用共享的CCSpriteFrameCache來獲得每一個動畫幀。記住,它們已經在快取裡了,因為我們前面呼叫了addSpriteFramesWithFile方法。
4) 建立動畫物件
animationWithFrames:walkAnimFrames delay:0.1f];
接下來,我們通過傳入sprite幀列表來建立一個CCAnimation物件,並且指定動畫播放的速度。我們使用0.1來指定每個動畫幀之間的時間間隔。
5) 建立sprite並且讓它run動畫action
self.bear = [CCSprite spriteWithSpriteFrameName:@"bear1.png"];
_bear.position = ccp(winSize.width/2, winSize.height/2);
self.walkAction = [CCRepeatForever actionWithAction:
[CCAnimate actionWithAnimation:walkAnim restoreOriginalFrame:NO]];
[_bear runAction:_walkAction];
[spriteSheet addChild:_bear];
我們首先通過spriteframe來建立一個sprite,並把它放在螢幕中間。然後,生成CCAnimationAction,並賦值給場景的walkAction屬性,最後讓熊來執行這個action。
最後,我們把熊加個場景中--把它當作spritesheet的孩子加到spritesheet中去。注意,如果在這裡我們沒有把它加到spritsheet中,而是加到當前層裡面的話。那麼我們將得不到spritesheet為我們帶來的效能提升!!!
完成了!
就這麼多!編譯並執行,你將會看到一隻熊歡快地在螢幕上面走動!
基於熊的移動方向改變熊的朝向
一切看起來好極了--除了我們並不想讓熊自己獨自一個人走之外,那太危險了!如果我們能夠通過點選螢幕就可以想讓熊往哪走,它就會往哪走的話,那就太棒了.
因此,在HelloWorldScene.m檔案中做如下修改:
//[_bear runAction:_walkAction];
// And add this to the init method
self.isTouchEnabled = YES;
// Add these new methods
-(void) registerWithTouchDispatcher
{
[[CCTouchDispatcher sharedDispatcher] addTargetedDelegate:self priority:0
swallowsTouches:YES];
}
-(BOOL) ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event {
return YES;
}
-(void) ccTouchEnded:(UITouch *)touch withEvent:(UIEvent *)event {
// Stuff from below!
}
-(void)bearMoveEnded {
[_bear stopAction:_walkAction];
_moving = FALSE;
}
開始之前,我們先把init方法中的執行行走action的程式碼註釋掉,因為我們並不想讓熊自己動,直到我們發出指令之後,它才能動!
我也設定了層能夠接收touch事件,然後實現了registerWithTouchDispatcher和ccTouchBegan方法。如果你對使用這個方法的好處感到好奇的話(為什麼不使用ccTouchesBegan呢?),你可以檢視《如何在cocos2d裡面製作基於Tile地圖的遊戲教程》。(當前是英文,以後會更新)
當bearMoveEnded方法被呼叫的時候,我們想讓熊停止任何正在執行的動畫,並且設定標記為不再移動。
看到ccTouchEnded方法,那裡就是待會要實現功能的地方。那兒有許多東西要實現,因此,讓我把它們分解成一些小片斷,一步步向眾位看官道來:
1) 計算touch座標點
touchLocation = [[CCDirector sharedDirector] convertToGL: touchLocation];
touchLocation = [self convertToNodeSpace:touchLocation];
這裡沒什麼新東西--我們僅僅是把touch點轉換成我們要使用的區域性座標系點。
2) 設定熊移動速度
這裡,我們設定了熊的移動速度。我假設熊要花3秒鐘時間才能從iphone螢幕(480個畫素寬)的一頭移動到另一頭。因此,簡單地用480個畫素除以3秒。
3) 計算x軸和y軸的移動量
接下來,我們需要計算出熊相當於x軸和y軸移動了多遠。我們簡單地使用touch座標減去熊當前的座標。這裡使用了cocos2d的一個幫助函式ccpSub來實現這個功能。
4) 計算實際移動的距離
我們需要計算出熊實際移動的距離(歐幾里德距離)。cocos2d裡面也提供了一個幫助函式來做這個事情,這個函式就是ccpLength,用來求一個向量的長度。
5) 計算移動需要花費的時間
最後,我們需要計算出熊要花費多長時間來走完這段路程,只需要拿距離除以速率就可以了。
6) 按照需要翻轉動畫
_bear.flipX = NO;
} else {
_bear.flipX = YES;
}
接下來,我們通過判斷移動的差值,如果小於0,那麼就不需要翻轉動畫,否則,就需要翻轉。因為我們的原畫裡面,熊就是往左移動的,因此,當熊往左移動時,我們不需要翻轉動畫,而往右移動的時候,只需要翻轉動畫。
我們的第一直覺可能是用圖片編輯器重新建立另一套朝向不同的熊的動畫序列圖,然後使用它們。但是,cocos2d裡面有一種更容易的方式(也更高效)--我們僅僅翻轉已經存在的圖片就行了。
這種方式可行,實現上,我們只是設定了執行動畫的sprite的flip屬性,但是它會使所有相關的動畫幀也相應地翻轉。在這個例子中,當熊往右行走的時候,我們就設定熊的flipX為Yes。
7) 執行合適的action
if (!_moving) {
[_bear runAction:_walkAction];
}
self.moveAction = [CCSequence actions:
[CCMoveTo actionWithDuration:moveDuration position:touchLocation],
[CCCallFunc actionWithTarget:self selector:@selector(bearMoveEnded)],
nil];
[_bear runAction:_moveAction];
_moving = TRUE;
接下來,我們停止任何正在執行的action。(因為我們將要覆蓋任何已經存在的命令,讓它移動到其它地方去!)。當然,如果我們沒有移動,我們也需要停止任何動畫action。(防止意外情況)。如果我們已經在移動了,那麼我們當然需要停止,因為這樣就不會影響後面執行的action。(這段話有些繞口,大家仔細體會,就是說,我們在讓一個sprite執行一個atcion之前,最好先讓它停止任何已經在執行的action。)
最後,我們建立移動action,指定移動的位置,花費的時間,並且指定一個回撥函式,這個函式會在熊移動到指定位置之後被呼叫。我們也需要記錄,我們移到那個點了!
完成啦!
寫了好多程式碼啊---但是值得,不是嗎?編譯並執行,然後點選螢幕,你將會看到一隻熊在螢幕上面移動。
何去何從?
這裡有這個教程的完整原始碼。
現在,你應該知道如何在專案裡面使用spritesheet了吧。你可以在你的專案中建立自己的動畫,然後看看你到底能做些什麼有趣的事情!just do it!