2013/3/31

[Unity] Futile 2D Framework 試用心得



Futile是一套給Unity 用的2D Framework, 架構類似於AS3及Cocos2D,有用過這兩種的人會很容易上手。


官網 & GitHub位置: https://github.com/MattRix/Futile

Demo及入門教學影片: http://struct.ca/futile/

最近上架的Nimble Quest即是用Futile所開發

這是我目前試用的幾個Unity 2D方案中,最適合Flash/AS3人員轉移的了。原因是Futile不需要在Unity IDE上做太多操作,大部份都是用code來完成的,有點像是在Flash Builder上做事。唯一差別是寫C#而非AS3。

而沒寫過C#的人也不用太擔心,大致上寫起來不會太難,與AS3也有許多相似之處,尤其Futile延用了許多AS3人員熟悉的概念(Sprite, Container, AddChild...etc.) 以致上手時備感親切。


實際操作

1.於Unity裏建立一個新project(File -> New Project...)

2.至GitHub網站下載Futile的檔案

3.解開zip後,開啟裏面的Futile.unitypackage或是在Unity裏從Assets->Import Package->Custom Package…去安裝Futile


4.至以上步驟為止,我們已經把Futile安裝至我們的Project中了。

5.在Unity裏,把預設的Main Camera刪除掉


6.建立一個空白的GameObject


7.為方便辨識,在Inspector面板裏把新建的Game Object重新命名為"FutileObject"


8.在專案資料夾中,Assets/Plugins/Futile/裏放著Futile的所有程式檔。這時需先把主程式Futile.cs與剛建立的FutileObject綁定在一起。可以從Project面板把Futile.cs直接拉到Hierarchy面板中的FutileObject中,如下圖


9.可以在Inspector面板中確認Futile是否有綁進FutileObject裏


10.為了方便之後的開發,我們可以在Assets裏新建一個"Scripts"的資料夾,放置我們自己寫的.cs檔


11.在Scripts裏新建一個C# script檔,命名為"MyGame"



12 .接著我們試著在Futile裏呈現一張圖。Futile預設的素材載入路徑為/Assets/Resources/,所以先隨便找一張圖放在此處。(我放了一張名為baozi.png的圖)


13.接下來在Project面板中點兩下MyGame.cs,此時Unity會自動開啟MonoDevelop,這是Unity自帶的程式撰寫工具。以下是MyGame.cs需撰寫的內容:

using UnityEngine;
using System.Collections;

public class MyGame : MonoBehaviour {

 // Use this for initialization
 void Start () {
  FutileParams futileParams = new FutileParams(true,true,true,true);
  
  futileParams.AddResolutionLevel(480,1,1,"");
  
  Futile.instance.Init(futileParams);
  
  Futile.atlasManager.LoadImage("baozi");
  
  FSprite mySprite = new FSprite("baozi");
  Futile.stage.AddChild(mySprite);
 }
 
 // Update is called once per frame
 void Update () {
 
 }
}





14.最後記得把MyGame.cs也拉進FutileObject裏


15.點擊preview看看圖片是否有正常出現在畫面中


16 .在撰寫FutileParams及AddResolutionLevel等程式時,MonoDevelop就會自動出現各參數的解說了,在此就不解釋了。

17.Update這個event會有點類似我們在Flash裏用的EnterFrame,是一個持續執行的function,我們改一下剛剛的程式:

using UnityEngine;
using System.Collections;

public class MyGame : MonoBehaviour {
 
 private FSprite _mySprite;
 
 // Use this for initialization
 void Start () {
  FutileParams futileParams = new FutileParams(true,true,true,true);
  
  futileParams.AddResolutionLevel(480,1,1,"");
  
  Futile.instance.Init(futileParams);
  
  Futile.atlasManager.LoadImage("baozi");
  
  _mySprite = new FSprite("baozi");
  Futile.stage.AddChild(_mySprite);
 }
 
 // Update is called once per frame
 void Update () {
  _mySprite.rotation ++;
 }
}



18.正確的話,應該會看到載入的圖片不斷的轉動,程式中的rotation就如同AS3的使用方式,也可以改成x, y, alpha, scale等屬性試試。


注意: Futile的座標系統與Flash大不相同,Futile的(0,0)在畫面的正中間,y值向上為正,向下為負。


以上便算完成一個最基本的入門範例。除了一開始的刪除Main Camera,建新Game Object,以及把程式與Game Object設綁定之外,幾乎所有開發都是在MonoDevelop裏寫code。這點對於尚不熟悉Unity介面的人來說,是一個不錯的優點。

下一篇應該會來寫一個簡單的打地鼠遊戲。(其實也只是照著Futile作者的教學影片做變化而已,有興趣的人不妨自己去官網看影片會比較快)

2013/3/3

Spriter好好玩系列 (四) ---- 使用SpriterMC


說在前頭:
如果你看到上一篇的SpriterAS已經覺得很夠用的話,那這篇SpriterMC其實可以直接跳過。原因是我個人認為SpriterAS目前的實用性大過於SpriterMC,而寫這篇只是因為都花時間研究了,就留下點筆記而已。另外SpriterMC唯一較強的是目前有支援Spriter的Bones及IK,所以除非有此特別需求或是好奇想看看的話,個人建議可直接跳過。
(以上心得基於SpriterMC v1.3版的狀況)

.官網:http://www.sammyjoeosborne.com/SpriterMC/

.github載點:https://github.com/sammyjoeosborne/spritermc

.基本用法:http://wiki.starling-framework.org/extensions/spritermc#usage

先看一下範例:

(click to open)
Download Source

載入SCML:
在官網中是這樣寫的:
var monster1:SpriterMC = SpriterMCFactory.createSpriterMC("monster", "xml/monster.scml");
monster1.play(); //Note: SpriterMC's will not actually start playing or show up on stage until SpriterMC.SPRITER_MC_READY is broadcast
 
//Add each SpriterMC to a Juggler, just like a regular Starling MovieClip
myJuggler.add(monster1);
但他們另外提供了事先做好TextureAtlas的做法,並且推薦用這種:
   var monster1:SpriterMC = SpriterMCFactory.createSpriterMC("monster", "xml/monster.scml", _textureAtlas);
monster1.playbackSpeed = 1.5; //Demonstrating playbackSpeed, which is like Scale, 1 == 100%. You can also set negative values to play backward
monster1.play(); //Note: SpriterMC's will not actually start playing or show up on stage until SpriterMC.SPRITER_MC_READY is broadcast
 
//Add each SpriterMC to a Juggler, just like a regular Starling MovieClip
myJuggler.add(monster1);
但我個人測試結果是,你必需使用TextureAtlas的寫法,否則使用第一種的話,執行時會出現error。

如何使用TextureAtlas:
照SpriterMC官網的說明,在他解析並產生這些SCML動畫時,會動態將所有的元件圖檔拼成一張大張的TextrueAtlas,這樣更能符合Starling的圖形加速處理,所以他建議使用者不如一開始自己就先手動做好拼圖這個動作。這件事我們可以利用TexturePacker來做(格式選AS3/Starling)。做完會像這樣:

一張大拼圖及一個xml描述檔。而AS3寫法可參考以下:
protected function init():void{
   loadTexture('assets/heroTA.png') 
  }
  
  private function loadTexture(_url:String):void
  {
   var _loader:Loader = new Loader();
   _loader.contentLoaderInfo.addEventListener(starling.events.Event.COMPLETE, textureLoadedHandler);
   _loader.load(new URLRequest(_url));
  }
  
  private function textureLoadedHandler(e:*):void 
  {
   
   _heroTexture = Texture.fromBitmap(Bitmap(e.target.loader.content));
   loadTextureAtlasXML("assets/heroTA.xml");
  }
  
  private function loadTextureAtlasXML(_url:String):void 
  {
   var _urlLoader:URLLoader = new URLLoader(new URLRequest(_url));
   _urlLoader.addEventListener(starling.events.Event.COMPLETE, atlasXMLLoadedHandler);
  }
  
  private function atlasXMLLoadedHandler(e:*):void 
  {
   var _xml:XML = XML(e.target.data);
   _textureAtlas = new TextureAtlas(_heroTexture, _xml);
   createCharacters();
  }
  
  private function createCharacters():void
  { 
    _hero = SpriterMCFactory.createSpriterMC("heroIK", "assets/heroIK/heroIK.scml",_textureAtlas,spriterReadyHandler,true);
  }
  private function spriterReadyHandler(e:starling.events.Event):void 
  {
   _hero.x= 100
   _hero.y=380
   
   
   _hero.setAnimationByName('idle')
   _hero.play();
   
   addChild(_hero);
   Starling.juggler.add(_hero)
   
  }

頭尾相連的動畫
成功讀入scml後,首先會發現動畫有點問題,就是原本來Spriter裏,最後一個keyframe都會自動做tween連回第一個keyframe,除非你自己取消這個功能。但是從SpriterMC讀進來的動畫卻完全都沒有這個效果。

正常來說,我們在Spriter裏可以透過上圖a點來切換是否要開啟"頭尾相連"的功能,但在SpriterMC裏目前完全無效,所以變通的方法就是自行將0ms的keyframe複製到動畫最尾處,如上圖b點。

動畫播完的callBack
這個功能對遊戲開發十分重要,例如我們做完一個攻擊、做完一個跳躍動畫,都會需要馬上切換至一般狀態的動畫,所以動畫播完呼叫一個callback會是十分常做的事情。在SpriterAS裏有提供了addCallBack這樣的method,但SpriterMC目前尚未提供這樣的api,我只能先用變通的方式來處理。
   private function onClickJump(e:MouseEvent):void{
   if(_hero.currentAnimation.name == 'jump')return;
   
   _hero.setAnimationByName('jump')
   _hero.loop=false;
   _hero.play()
   _hero.addEventListener(starling.events.Event.ENTER_FRAME,onEnterCheck);
  
  }
  
  protected function onEnterCheck(e:starling.events.Event):void{
   if(_hero.isComplete){
    _hero.removeEventListener(starling.events.Event.ENTER_FRAME,onEnterCheck);
    _hero.setAnimationByName('idle')
    _hero.loop=true;
   }
  }
這邊我使用ENTER_FRAME不斷去check他的isComplete是否為true,如此才能知道他是不是播完了。而在使用isComplete時,有兩個注意事項:
1.需把loop設為false,如此isComplete才有機會變成true,否則會永遠得不到true。
2.我個人懷疑這是bug。當我設_hero.setAnimationByName('jump')並成功播完一次後,下次再執行_hero.play()時,isComplete會在一開始就是true了,也就是它並沒有因為重新play,而重設為false。為了解決這問題,我直接修改了SpriterMC裏Animation.as這支檔案的play函式:
internal function play():void
  {
   //if we aren't looping and play was called while the animation was on its last frame, restart it
   if (!_loop && _currentKeyIndex == _lastFrameIndex)
   {
    _currentTime = mainKeys[_firstFrameIndex].time; //this is gonna make currentTime 0 or the length of the animation, depending on which way the animation is playing
    _currentKeyIndex = _firstFrameIndex;
    
   }
   _animationEnded = false;//maso add
   _isPlaying = true;
  }
修改過後就能正常執行了。

其他心得
SpriterMC目前能做的應用很少,所以試到這裏就沒什麼好繼續了,所以其他請自行參閱官網。倒是有一點較特別的是,Spriter的timeline是以ms為單位,但SpriterMC裏卻提到了一個叫currentFrame屬性,這裏試了一下,所謂的frame其實是Spriter中的Keyframe,第一個Keyframe得到是0,第二個是1,以此類推。不過在沒有call back的應用下,這個currentFrame其實也沒太大的用處。



Index:
Spriter好好玩系列 (一) ---- 什麼是Spriter ?
Spriter好好玩系列 (二) ---- Spriter 基本操作
Spriter好好玩系列 (三) ---- 使用SpriterAS
Spriter好好玩系列 (四) ---- 使用SpriterMC

2013/3/2

Spriter好好玩系列 (三) ---- 使用SpriterAS


這篇要來把動畫跟AS3串接起來。用SpriterAS+Starling來做。

SpriterAS是一款基於Starling架構,專門播放Spriter動畫的AS3 Library。由加拿大的TreeFortress小組所開發。(TreeFortress專注於Mobile Game開發,成員包括了知名的gskinner大神)

.先看一下範例:

(click to open)
Download Source

上述範例我在Spriter裏做了三則animation,分別是idle(平常狀況)、fight_0(輕拳)、fight_1(重拳)。其他如眨眼、拳頭上的特效、頭掉下來,都是由程式控制的,而SpriterAS也支援Playback Speed的控制,這也是突顯了元件動畫優於連續圖檔的一大強項。

TreeFortress有寫了一篇Introducting SpriterAS - Play Spriter Animations with Starling。算是SpriterAS的入門介紹文,本篇範例也是看完他們的介紹文後的產物。建議有空可以先去看看,以下我只挑幾個重點介紹。

.github載點 https://github.com/treefortress/SpriterAS

.載入scml並叫出動畫
//**注意:SpriterAS是建立在Starling之上的,必需在Starling內
//**使用下列程式才能有作用。

protected var spLoader:SpriterLoader;

protected function init():void{
   
 spLoader = new SpriterLoader();
 spLoader.completed.add(onSpriterLoadComplete);
 //SpriterLoader可一次載入多個scml檔,所以參數是填一個array,可代入多個scml位置
 spLoader.load(['assets/hero/hero.scml'],1)
  
   
}
protected function onSpriterLoadComplete(spLoader:SpriterLoader):void
  {
   //從loader中取得動畫,以scml名稱為索引
   hero = spLoader.getSpriterClip('hero')
   //idle是hero.scml裏其中一段animation的名稱
   hero.play("idle")
   hero.x = 100
   hero.y =300
   addChild(hero)
   Starling.juggler.add(hero) 
}
這裏需要注意一點是,spLoader必需是class內的固定成員,不可以是function中的區域變數,例如我試過以下這樣寫的話,onSpriterLoadComplete將無法被觸發:
protected function init():void{
   
 var spLoader:SpriterLoader = new SpriterLoader();//不能用區域宣告
 spLoader.completed.add(onSpriterLoadComplete);
 spLoader.load(['assets/hero/hero.scml'],1)
  
   
}


.按下button切換animation至"fight_0"(輕拳),並在播放完畢時,切回"idle"
private function onClickFight0(e:MouseEvent):void
  {
   hero.play("fight_0")
   hero.addCallback(onOverFight,400)//400ms後呼叫onOverFight
}
protected function onOverFight():void{
 if(hero.animation.name != 'idle')hero.play("idle")
} 
直接使用 play("animation名稱"),即可切換動畫。
而 addCallback則是一個相當實用的method,可設定動畫播到多少ms時回call某個function,而此ms數會依照當初在Spriter裏設定的timeline來執行,因此不會受playback speed而受影響。
完整使用方式是:
addCallback(回call的function, 回call時間, 是否只執行一次)

.眨眼功能(動態切換元件)
private function onClickBlink(e:MouseEvent):void
  {
   hero.swapPiece("head_0", "head_1");
   TweenLite.delayedCall(.1,hero.unswapPiece,['head_0'])
}
swapPiece("A", "B")可將動畫中的A圖換成B圖。
unswapPiece("A")則是取消加諸在"A"身上的任何swapPiece效果。

.附著在拳頭上的粒子效果:
private function getHandPos():Point{
  var handImg:Image //Starling中的Image
  handImg = hero.getImage("leftHand_0");
  return new Point(handImg.x,handImg.y)
}
粒子效果非本篇重點,所以特效的部份就不多寫,上述程式只針對取得拳頭位置做示範,不過這邊要注意,取得的handImg.x及y後,記得還要加上hero.x及y,才是拳頭在畫面上看到的位置。

.頭掉下來(動態將元件拆離scml設定,或再組合回去)
private function onClickDropHead(e:MouseEvent):void
  {
   var headImg:Image = hero.getImage("head_0")
   if(headImg.y>-100){//弱弱的用y的位置來判斷目前頭的狀況
    hero.includePiece(headImg);//組回
    return;
   }
   
   hero.excludePiece(headImg,true)//脫離
   
   TweenLite.to(headImg,.6,{y:25,ease:Bounce.easeOut})
  }

.playback speed 控制
private function onSlideFPS(e:Event):void
  {
   _stage.frameRate = _fpsSlider.value
  } 
  private function onSlideSpeed(e:Event):void
  {
   hero.playbackSpeed = _speedSlider.value
  } 
這邊就沒什麼好解說的了,playbackSpeed代1的話表示正常進度,2為兩倍,0.5為半速,以此類推。

.動態才用到的元件
這邊算是個人使用心得。當我在製作範例中的眨眼功能,遇到了點小問題。原本我在Spriter裏編輯時只有三段animations,分別是idle, fight_0, fight_1,而這三段動畫完全沒用到眨眼的那張圖(head_1.png),使得當我在用hero.swapPiece("head_0", "head_1")時,會完全沒有做用。原因是當我查看scml檔的內容,發現scml在最前頭會先將所有圖檔列一份清單,之後的keyframe資訊就只會記下清單中的id,而這時又發現scml內只會記錄所有animation有用到過的圖檔,而不是所有放在project folder中的圖檔,所以它並沒有將我的head_1.png存進去,導致讓swapPiece("head_0", "head_1")無效。
變通的辨法是,再開一個新的animation,把所有程式會用到的圖都放進去,再輸出成scml即可。

例如上圖我就是把程式才會用到的圖,都放在"assets"這段animation裏。

.心得與注意事項:
SpriterAS使用下來,我個人覺得功能算是很完整了,已能符合大多數需求,唯一缺點是Spriter中的Bones功能尚未支援(如果有帶bone的scml,在spriterAS裏會爛掉),SpriterAS在官方blog是說未來會支援。不過我個人心得是,這部份還好,在我們真的在調比較細緻的動畫時,bones及IK其實沒什麼太大的幫助。另外作者群還有提到未來可能會推出JS版,有用JS的人可以期待一下。

*另一個library: SpriterMC有支援bones及IK,我在下一篇會簡單介紹一下SpriterMC



Index:
Spriter好好玩系列 (一) ---- 什麼是Spriter ?
Spriter好好玩系列 (二) ---- Spriter 基本操作
Spriter好好玩系列 (三) ---- 使用SpriterAS
Spriter好好玩系列 (四) ---- 使用SpriterMC

2013/3/1

Spriter好好玩系列 (二) ---- Spriter 基本操作


這篇來講基本操作。

首先到這個頁面去下載,Spriter 同時支援了win及mac,請找自己需要的版本去下載


目前免費提供的版本是alpha4.1版,未來正式版也會分free及pro付費版,free版會有一些功能限制,這個頁面有詳細說明,有興趣可以看看。

各位應該有注意到下載頁面右邊有放了兩支影片,上面還寫了"Don't try Spriter without watching these first"。對,沒錯,請務必看過這兩支影片再來試會比較快。因為目前還在alpha 版,作者幾乎沒有提供任何教學、範例、說明書之類的,而軟體本身介面也沒有簡單到像iPhone一樣可以不看說明書就會用,所以建議是先耐心看過影片後,再來使用。

以下我就挑一些重點來說明

.新建Project時會請你選擇一個folder,請直接選擇你放置動畫元件的folder,這些元件才會出現在你右邊的清單裏,然後就可以從右邊的清單拉進stage裏。(如下圖)


.如下圖,當游標在A區時,滑鼠滾輪可縮放stage;當游標在B區時,滾輪可縮放timeline;C區也可設定stage的縮放,但他會自動隱藏,游標移到A及C區時會出現。


.下圖為Timeline區。Spriter的Timeline是以ms為單位,這樣比較能通用於各種平台。圖中a處為playhead,拖動playhead至你要新增keyframe的地方,然後直接調整你的角色,keyframe就會自動產生。另外也可以在拖動playhead至某時間點後,直接按下Add Keyframe(圖中b點),便會以當下角色的狀態產生一個新的keyframe。最後c點的按鈕為"是否要將最後一個keyframe做tween回到第0ms的keyframe"。(也就是頭尾串接的動畫)

另外上圖有顯示"CurrentPlaybackTime:391",為目前playhead所在的詳細位置,你可以在"391"那個地方點擊滑鼠,會出現手動輸入框,可精準調整playhead位置。

.左邊是目前出現在stage上的元件列表,可drag移動上下層關係,愈下面的layer在Stage裏表示愈上層。

值得一提的是,Spriter可在不同Keyframe中變換Layer的順序,這在以往Flash上是十分麻煩的事情。

.點擊Stage上的元件都會出現如下圖的畫面,元件四週的九個方塊是形變控制點,而a點可控旋轉,另外b點是中心點,所有形變、旋轉都是以b為中心。如果想直接輸入數字來控制元件,可點擊c點開啟詳細面板。


.提到旋轉,就不能不知如何調整中心點位置。一般情況下,只要拖曳中心點的那個圓圈即可,但一個新拉進stage的元件,Spriter預設會把中心點設在左上角,與左上角的形變控制點重疊在一起,直接移動的話會變成是點到形變,而不是中心點,所以在點擊時要注意一下游標位置。

當游標移至重疊的中心點時,中心點的圓圈會放大以方便分辨,這時若點擊在綠色區域,則會移動中心點,若點擊在紅色區域,則做形變控制。

.Spriter也支援元件中途變換不同圖片的功能,只要在stage上以右鍵點擊元件,就會秀出小面板讓你切換。此變換只對目前的keyframe有作用,之前的keyframe還會是舊的那張圖。也就是"換圖"這件事也是keyframe包括的內容之一。


.再來是類似Flash中的Onion Skin功能,可以讓你看到前幾格或後幾格keyframe的位置,這是2D動畫中十分重要的功能。開啟後效果如下圖

紅色表示前幾格,綠色表示後幾格。開始方式請參考下圖紅、綠色塊的位置,以playhead為分界點,timeline線以上的這塊區域,在紅色範圍內按右鍵拖拉即可設定過去的keyframes顯示,而綠色範圍內右鍵拖拉即可設定未來的keyframes顯示


.在Timeline上也以一次選取多個Keyframes,下圖中,先點一下a,按著"shift"不放再點一下b,即可選取多個keyframes。選取後,可進行移動、複製貼上等功能,也可在選取多個的狀態下,按著"alt"不放,去拖拉a點或b點,即可做整串keyframe的壓縮及放大。


.Spriter也提供了Bones系統及IK功能,只要在stage上空白處,按著"alt"同時以滑鼠點擊拖拉即可拉出一個Bone。在選取某個Bone的狀況下新增Bone的話,則新的Bone則會是原來選取的Bone的Child。選取任何Bone時,都會以顏色提示其主從關係,如下圖


.另外也可以在左側的Bone Hierarchy面板調整Bone的主從關係,在這面板也可以為各個Bone命名,以及將元件與Bone綁定,如下圖,head_2.png即與bone_000綁定在一起。這個面板是以樹狀結構呈現,一切都用拖拉即可完成。最後要注意一點,這個面板會列出所有Bones及stage上的元件,如果沒看到元件,可能是右上角的眼睛沒點開。(Show Sprites in Heirarchy)


.在調整Bone時,一樣有方形的形變控制點及圓形的旋轉控制點,當按著"shift"同時調整旋轉控制點的話,即可開啟IK功能。而在Bone上按下右鍵的話,會出現IK的錨點功能,也就是這段Bone的終點會釘在某個位置,並牽制其他Bone的變動。


.完成一串動畫即可存成一則animation,右側下的Animations可檢視目前所有的animation,並可在此面板中新增、複製、刪除animation,一個SCML檔可存入多個animations


.最後當所有動作都完成時,選擇"File"->"Save Project"即可存檔(同時會產生scml檔),然後就可以拿去做程式開發了。

.Spriter目前還在alpha,所以還有很多bug,例如偶爾會無預警關閉,用IK時bone會往奇怪的角度旋轉、莫明產生keyframe…等,都不是什麼大問題,不過建議就是勤於存檔,並期待正式版時這些bug都清掉。若發現什麼bug想通報作者的話,可參考這篇的回報方式。



Index:
Spriter好好玩系列 (一) ---- 什麼是Spriter ?
Spriter好好玩系列 (二) ---- Spriter 基本操作
Spriter好好玩系列 (三) ---- 使用SpriterAS
Spriter好好玩系列 (四) ---- 使用SpriterMC