游戲從出現以來,一直提升着玩家的需求,游戲開發者不止是考慮簡單的位置移動,例如將游戲中各種演員做成動畫,就可以大大提升游戲的品質,在咱們的這個三國統帥的游戲中,小兵和將領是有各種動作的,這些動作對應動畫,讓游戲的互動感覺更加優秀,這次使用cocos2d-xna中的CCAnimate來實現角色動畫。
首先我們要先准備游戲中所需要的動畫資源,按照設計,游戲中至少有這樣的角色和職業:主將、小兵(步兵、槍兵、騎兵、弓兵),它們的各種所扮演的角色因所屬勢力只是樣子不一樣。
角色的動畫禎按照一個規則命名,這樣就能方便的管理:
{id}_{n}表示的是正方向,而{id}f_{n}表示的是反方向。比如說
A1表示的是角色的id,按照編號的顯示動作分別為:
0-3:攻擊動作
4-5:行走動作
6-6:站立動作
7-8:死亡動作
在這個實驗游戲中,我們有兩個勢力對陣,分別為義軍和黃巾軍,按照這樣的規則制作了如下的資源:
A1:黃巾軍步兵
A2:黃巾軍槍兵
A3:黃巾軍騎兵
A4:黃巾軍弓兵
B1:義軍步兵
B2:義軍槍兵
B3:義軍騎兵
B4:義軍弓兵
另外還加了兩個英雄:
Hero02:關羽
Hero11:張角
然后將他們的各種幀制作完成並命名完畢(這里也許你需要美術的幫助,我已將其完成,可以在最終的文件中瀏覽),用TexturePackerGUI打包。
圖片包下載地址:http://files.cnblogs.com/nowpaper/SanguoCommander5_actors.rar
發布一下它,保存成一個plist和png圖,命名為ActorsPack1,以后也許有Pack2,所以單獨分開保存:
最終的plist文件放入工程Content下,可以建立一個例如plist的文件夾,並且將.plist文件的內容管線設置正確:
這張圖並不正確,你應該建立子文件夾Images,並將ActorsPack1.png放入其中。
好了,下一步就可以開始對工程進行代碼編寫了。
從游戲本身的設計經驗而言,最好的方式是數據驅動邏輯,所以,當我們描寫一個角色動畫類的時候,最先有一個比較明確的數據類,這里我們可以將游戲底層角色的復雜數據包含,並且做處理,例如角色最基本的特性、職業、類型等等:
enum ActorType { None, Soldier,Hero } enum ActorPro { None, Infantry, Pikeman, Cavalvy, Archer } enum ActorDir { None, Right, Left } class ActorData { public ActorData(string id) { ActorID = id; } //演員id public string ActorID { get; private set; } //演員分組 public string GroupID { get; private set; } //類型 public ActorType ActorType { get; private set; } //職業 public ActorPro ActorPro { get; private set; } //獲得一個數據 public static ActorData getActorData(string id, string groupid, ActorType type, ActorPro pro) { ActorData data = new ActorData(id); data.GroupID = groupid; data.ActorType = type; data.ActorPro = pro; return data; } }
在最上面,我們定義了三個枚舉,分別來表示類型、職業和方向,方向是用來做動畫用的,而類型和職業則是基本數據的需求,大家可以注意到,設計了一個getActorData靜態方法,用來方便的制作基本測試數據,因為按照游戲數據的處理慣例而言,這些數據都是通過表單的方式配置,程序讀取配置解析成為程序數據,方便游戲設計師隨時調整。
下一步就是建立動畫類了,我們使用類繼承的方式抽象動畫類,建立ActorBase類,這個類只是管理角色最基本的動畫,繼承自CCSprite,通過CCAnimate行為來控制動畫:
class ActorBase : CCSprite { public ActorData ActorData { get; private set; } private CCAnimate _action_attack; private CCAnimate _action_attack_flip; private CCAnimate _action_run; private CCAnimate _action_run_flip; private CCAnimate _action_stand; private CCAnimate _action_stand_flip; private CCAnimate _action_dead; private CCAnimate _action_dead_flip; public ActorDir ActorDir { get; set; } public ActorBase(ActorData data) { ActorData = data; //創建攻擊動畫 List _attackFrames = new List(); List _attackFrames_flip = new List(); for (int i = 0; i < 4; i++) { _attackFrames.Add(CCSpriteFrameCache.sharedSpriteFrameCache().spriteFrameByName(data.ActorID + "_" + i + ".png")); _attackFrames_flip.Add(CCSpriteFrameCache.sharedSpriteFrameCache().spriteFrameByName(data.ActorID + "f_" + i + ".png")); } _action_attack = CCAnimate.actionWithAnimation(CCAnimation.animationWithFrames(_attackFrames, 0.1f)); _action_attack_flip = CCAnimate.actionWithAnimation(CCAnimation.animationWithFrames(_attackFrames_flip, 0.1f)); //創建行走動畫 List _runFrames = new List(); List _runFrames_flip = new List(); for (int i = 4; i < 6; i++) { _runFrames.Add(CCSpriteFrameCache.sharedSpriteFrameCache().spriteFrameByName(data.ActorID + "_" + i + ".png")); _runFrames_flip.Add(CCSpriteFrameCache.sharedSpriteFrameCache().spriteFrameByName(data.ActorID + "f_" + i + ".png")); } _action_run = CCAnimate.actionWithAnimation(CCAnimation.animationWithFrames(_runFrames, 0.1f)); _action_run_flip = CCAnimate.actionWithAnimation(CCAnimation.animationWithFrames(_runFrames_flip, 0.1f)); //創建站立動畫 List _standFrames = new List(); List _standFrames_flip = new List(); for (int i = 6; i < 7; i++) { _standFrames.Add(CCSpriteFrameCache.sharedSpriteFrameCache().spriteFrameByName(data.ActorID + "_" + i + ".png")); _standFrames_flip.Add(CCSpriteFrameCache.sharedSpriteFrameCache().spriteFrameByName(data.ActorID + "f_" + i + ".png")); } _action_stand = CCAnimate.actionWithAnimation(CCAnimation.animationWithFrames(_standFrames, 0.2f)); _action_stand_flip = CCAnimate.actionWithAnimation(CCAnimation.animationWithFrames(_standFrames_flip, 0.2f)); //創建死亡動畫 List _deadFrames = new List(); List _deadFrames_flip = new List(); for (int i = 7; i < 9; i++) { _deadFrames.Add(CCSpriteFrameCache.sharedSpriteFrameCache().spriteFrameByName(data.ActorID + "_" + i + ".png")); _deadFrames_flip.Add(CCSpriteFrameCache.sharedSpriteFrameCache().spriteFrameByName(data.ActorID + "f_" + i + ".png")); } _action_dead = CCAnimate.actionWithAnimation(CCAnimation.animationWithFrames(_deadFrames, 0.3f)); _action_dead_flip = CCAnimate.actionWithAnimation(CCAnimation.animationWithFrames(_deadFrames_flip, 0.3f)); //初始化默認幀 base.initWithSpriteFrame(_standFrames[0]); } CCAction _currentAnimateAction; public void StateToRun() { if(ActorDir == Roles.ActorDir.Left) RunAnimateAction_RepeatForever(_action_run); else RunAnimateAction_RepeatForever(_action_run_flip); } //攻擊狀態 public void StateToAttack() { currentAnimateActionStop(); if (ActorDir == Roles.ActorDir.Left) RunAnimateAction_RepeatForever(_action_attack); else RunAnimateAction_RepeatForever(_action_attack_flip); } //死亡動畫 public void StateToDead() { currentAnimateActionStop(); if (ActorDir == Roles.ActorDir.Left) _currentAnimateAction = runAction(_action_dead); else _currentAnimateAction = runAction(_action_dead_flip); } //站立動畫 public void StateToStand() { if (ActorDir == Roles.ActorDir.Left) RunAnimateAction_RepeatForever(_action_stand); else RunAnimateAction_RepeatForever(_action_stand_flip); } //停止當前的動畫 private void currentAnimateActionStop() { if (_currentAnimateAction != null) this.stopAction(_currentAnimateAction); } //播放循環動畫的統一方法 private void RunAnimateAction_RepeatForever(CCAnimate action) { currentAnimateActionStop(); _currentAnimateAction = runAction(CCRepeatForever.actionWithAction(action)); } }
上述代碼加了一些注釋,希望能夠幫助你的閱讀,動畫行為的制作流程是這樣的:
首先我們要知道有那些幀,它們形成的集合變成CCAnimation的處理類,然后CCAnimate將其加載並形成特定的動畫行為,有興趣的配有可以看cocos2d的底層代碼,CCSprite實際上是帶了一個CCTexture來表示圖像,CCAnimate是按照邏輯變化CCTexture。
在下面的代碼中:StateToRun、StateToAttack、StateToDead、StateToStand等方法都是用來處理角色狀態的動畫,例如對應到當攻擊的時候調用StateToAttack方法。
currentAnimateActionStop和RunAnimateAction_RepeatForever是為了方便處理動畫的特定狀態,因為諸如行走、站立、攻擊,這一類的動畫都是循環性質,統一代碼比較方便。
那么我們就測試一下上面的代碼看看直接的效果,還是為了方便,在本節中,我們直接將動畫放在了開始界面這樣瀏覽很方便了,打開SceneStart.cs,在構造函數中寫如下代碼:
//測試動畫的角色 List id_buff = new List() { "B1","B2","B3","B4","Hero02","A1","A2","A3","A4","Hero11" }; for (int i = 0; i < 2; i++) { for (int j = 0; j < 5; j++) { var actor = new ActorBase(new ActorData(id_buff[i*5 + j])); actor.ActorDir = (ActorDir)(i + 1); actor.position = new CCPoint(64 + i * 64, 64 + j * 64); if(j % 2 ==1) actor.StateToRun(); else actor.StateToAttack(); this.addChild(actor); } } //
上面代碼里,用一個List結構id_buff來描述ID,其實就是角色的前綴名,然后按照順序排成兩列,並且按照單數為行走動畫,雙數為攻擊動畫的形式顯示。
上面是運行測試的效果,后面我們會將它們加到游戲界面中,讓游戲真正的可以玩起來。
本篇例子工程:https://github.com/Nowpaper/SanguoCommander_cocos2dxna_Sample
本例子項目名為:SanguoCommander6