Flash Professional CS6は、シンボルとそのアニメーションをスプライトシートとして書出すことができます[*1]。Starlingフレームワークでスプライトシートからどのようにアニメーションを作るかについてご説明します。なお、本稿には前編に当たる「ActionScript 3.0のフレームアクションをStarlingフレームワークに移行する」があります。Starlingフレームワークの基礎について学びたい場合には、まずそちらからお読みください。

[*1] Flash Professional CS6から[スプライトシートを生成]する手順については、AdobeのPaul Trani氏による解説ビデオ(日本語字幕入り)があります。

01 ムービークリップシンボルから[スプライトシートを生成]する

Starlingフレームワークで使うアニメーションを、ムービークリップシンボルとして[ライブラリ]に作りました(図001)。この例では簡単なフレームアニメーションにしています。もちろん、モーショントゥイーンで作っても差支えありません。

スプライトシートを書出すには、[ライブラリ]でシンボルを右クリックしてショートカット(コンテクスト)メニューを開きます。そして、[スプライトシートを生成]のメニューを選びます(図002)。

すると、[スプライトシートを生成]のダイアログボックスが開きますので、書き出しの設定を行います(図003)。スプライトシートはStarlingフレームワークで使うため、[データ形式]から[Starling]を選びます。[画像形式]は、ここでは背景を透過させる[PNG 32 bit]にします[*2]。

[アルゴリズム]の選択や、[カット]と[フレームをスタック]の設定を変えると、[スプライトシート]タブで各フレームの画像の並び方や数が変わるのを確かめられます(図004)。また、上部に[出力ファイルサイズ]が示されますので、書出すシンボルによって試してみるとよいでしょう。

必要があれば[参照]ボタンで保存場所を定めてから、[書き出し]ボタンをクリックすると、スプライトシートが書き出されます。でき上がるファイルはPNG画像とXMLデータで、デフォルトではFLAファイルと同じ名前です(図005)。これらのファイルを用いて、Starlingフレームワークでシンボルのアニメーションを再生します。

[*2] [PNG 32 bit]以外の透過できない[画像形式]を選ぶと、[背景色]が定まります。筆者の環境では、その後[PNG 32 bit]に戻しても、その前の[背景色]がそのまま残り透過されません(図006)。その場合には、改めて[背景色]をなし(アルファ0)にします。

02 ActionScript 3.0を使った簡単なアニメーション

Starlingフレームワークのルートクラスは、前出「ActionScript 3.0のフレームアクションをStarlingフレームワークに移行する」のスクリプトをもとに、書き替えていくことにします。内容は、[ライブラリ]の静止画像(ビットマップ)をスクロールするアニメーションです(図007)。

このStarlingルートクラス(MySprite)は、以下にスクリプト001として再掲します(前出「ActionScript 3.0のフレームアクションをStarlingフレームワークに移行する」ではスクリプト004)。また、FLAファイルには次のフレームアクションが書かれているものとします。

import starling.core.Starling; var myStarling:Starling = new Starling(MySprite, stage); myStarling.start();

スクリプト001 [ライブラリ]のビットマップから作ったインスタンスを水平にスクロールする(001_start)

// ActionScript 3.0クラス定義ファイル: MySprite.as package { import flash.display.BitmapData; import starling.display.Sprite; import starling.display.DisplayObject; import starling.display.Image; import starling.textures.Texture; import starling.events.Event; public class MySprite extends Sprite { private var nWidth:Number; private var nScrollWidth:Number; public function MySprite() { this.addEventListener(Event.ADDED_TO_STAGE, initialize); } private function initialize(eventObject:Event):void { var myBitmapData:BitmapData = new Pen(); var myTexture:Texture = Texture.fromBitmapData(myBitmapData); var instance:Image = new Image(myTexture); addChild(instance); instance.addEventListener(Event.ENTER_FRAME, scroll); instance.y = (stage.stageHeight - instance.height) / 2; nWidth = instance.width; nScrollWidth = stage.stageWidth + nWidth; } private function scroll(eventObject:Event):void { var instance:DisplayObject = eventObject.currentTarget as DisplayObject; var nX:Number = instance.x - 5; if (nX < -nWidth) { nX += nScrollWidth; } instance.x = nX; } } }

PNG画像やXMLデータをスクリプトに組み込むやり方はいくつか考えられます。ただ、その処理は通常のActionScript 3.0と変わらず、本稿の主題ではありません。ですから、もっとも簡便な手法で済ませます。

まず、PNG画像はもとのFLAファイルで[ライブラリ]のビットマップと差替えます。[ライブラリ]でビットマップを選び、ショートカット(コンテクスト)メニューから[プロパティ]で[ビットマッププロパティ]ダイアログボックスを開きます。[読み込み]ボタンでスプライトシートのPNGファイルを指定したら、[OK]ボタンをクリックすれば[ライブラリ]のビットマップが差し替わります。

この手順はビットマップを差し替えただけですので、[クラス]の設定もそのまま残ります。したがって、前掲スクリプト001のStarlingルートクラス(MySprite)は、とりあえずエラーなく動きます。もちろん、複数のフレームの画像が1つになっていますので、それら全てが集団で動くアニメーションになります(図010)。

この1つのPNG画像からフレームごとにイメージを切り出し、シンボルのアニメーションに添って並べ替える情報がXMLデータに納められています(図011)。まず、TextureAtlas要素にはPNG画像のパスが定められています(imagePath属性)。次に、その子のSubTexture要素がひとつひとつのフレームを表します。name属性にはシンボル名 + 連番が定められています。連番は0から始まるフレーム番号です。

このXMLデータも、簡便に済ますため、スクリプトに文字列(Stringデータ型)で直に書込みます。この処理は、前掲Starlingルートクラス(スクリプト001)の初期化の関数(initialize())に加えましょう。スクリプトが見やすく修正もしやすいように、XMLテキストを行ごとに加えました。そのまとめたテキストをXML()関数でXMLオブジェクトに変換して、新たに宣言した変数(textureAtlas_xml)に納めます。

private var textureAtlas_xml:XML; // XMLデータを入れる変数宣言 public function MySprite() { // XMLテキストを行ごとに加える nbsp; var textureAtlas_str:String = ""; textureAtlas_str += '<TextureAtlas imagePath="TextureAtlas.png">'; textureAtlas_str += '<SubTexture name="pen_walk0000" x="0" y="0" width="82" height="109"/>'; textureAtlas_str += '<SubTexture name="pen_walk0001" x="0" y="0" width="82" height="109"/>'; textureAtlas_str += '<SubTexture name="pen_walk0002" x="0" y="109" width="82" height="109"/>'; textureAtlas_str += '<SubTexture name="pen_walk0003" x="82" y="0" width="82" height="109"/>'; textureAtlas_str += '<SubTexture name="pen_walk0004" x="82" y="0" width="82" height="109"/>'; textureAtlas_str += '<SubTexture name="pen_walk0005" x="82" y="109" width="82" height="109"/>'; textureAtlas_str += '</TextureAtlas>'; textureAtlas_xml = XML(textureAtlas_str); // テキストをXMLオブジェクトに変換 addEventListener(Event.ADDED_TO_STAGE, initialize); }

スプライトシートとして書き出したPNG画像とXMLデータをスクリプトに加えましたので、これらのデータにもとづいてシンボルと同じアニメーションを作ります。

03 スプライトシートからアニメーションが含まれたMovieClipインスタンスを作る

スプライトシートとして書き出された画像とXMLデータからアニメーションに使うフレームごとのイメージ(Textureオブジェクト)を取り出すのがTextureAtlasクラスです。TextureAtlasクラスのコンストラクタメソッドには、画像から作ったTextureオブジェクトと、XMLオブジェクトを引数として渡します。

new TextureAtlas(Textureオブジェクト, XMLオブジェクト)

そして、TextureAtlas.getTextures()メソッドに引数としてシンボル名の文字列を渡して呼出せば、シンボルの中のフレームごとの画像をTextureエレメントとして納めたVectorオブジェクトが得られます[*3]。これらの画像をフレームごとに切り替えれば、アニメーションが再生されるという訳です。

var Vectorオブジェクト用変数:Vector.<Texture> = TextureAtlasオブジェクト.getTextures(シンボル名の文字列)

けれど、その仕組みを新たにスクリプトで作るには及びません。そのためにMovieClipクラスが備えられています。MovieClip()コンストラクタメソッドの引数には、Textureエレメントが納められたVectorオブジェクトを渡します。

new MovieClip(Textureベース型Vectorオブジェクト)

さて、これらを前掲Starlingルートクラス(スクリプト001)の初期化の関数(initialize())に加えて書き替えると次のようになります。import宣言には、StarlingフレームワークのクラスMovieClipとTextureAtlasが増えています。逆に、Imageクラスの処理は要らなくなりました。

// import starling.display.Image; import starling.display.MovieClip; import starling.textures.TextureAtlas; public class MySprite extends Sprite { private function initialize(eventObject:Event):void { var myBitmapData:BitmapData = new Pen(); var myTexture:Texture = Texture.fromBitmapData(myBitmapData); // var instance:Image = new Image(myTexture); var myTextureAtlas:TextureAtlas = new TextureAtlas(myTexture, textureAtlas_xml); var frames:Vector.<Texture> = myTextureAtlas.getTextures("pen_walk"); var instance:MovieClip = new MovieClip(frames); addChild(instance); instance.addEventListener(Event.ENTER_FRAME, scroll); instance.y = (stage.stageHeight - instance.height) / 2; nWidth = instance.width; nScrollWidth = stage.stageWidth + nWidth; } }

前掲Starlingルートクラス(スクリプト001)に前項02「スプライトシートのデータをStarlingルートクラスに組み込む」処理と、TextureAtlasクラスからMovieClipインスタンスを作る修正を加えたクラス定義(MySprite)全体が、次のスクリプト002です。

スクリプト002 TextureAtlasとMovieClipクラスを使ったStarlingルートクラスの定義(002_TextureAtlas)

// ActionScript 3.0クラス定義ファイル: MySprite.as package { import flash.display.BitmapData; import starling.display.Sprite; import starling.display.DisplayObject; import starling.display.MovieClip; import starling.textures.TextureAtlas; import starling.textures.Texture; import starling.events.Event; public class MySprite extends Sprite { private var nWidth:Number; private var nScrollWidth:Number; private var textureAtlas_xml:XML; public function MySprite() { var textureAtlas_str:String = ""; textureAtlas_str += '<?xml version="1.0" encoding="UTF-16"?>'; textureAtlas_str += '<TextureAtlas imagePath="animation.png">'; textureAtlas_str += '<SubTexture name="pen_walk0000" x="0" y="0" width="82" height="109"/>'; textureAtlas_str += '<SubTexture name="pen_walk0001" x="0" y="0" width="82" height="109"/>'; textureAtlas_str += '<SubTexture name="pen_walk0002" x="0" y="109" width="82" height="109"/>'; textureAtlas_str += '<SubTexture name="pen_walk0003" x="82" y="0" width="82" height="109"/>'; textureAtlas_str += '<SubTexture name="pen_walk0004" x="82" y="0" width="82" height="109"/>'; textureAtlas_str += '<SubTexture name="pen_walk0005" x="82" y="109" width="82" height="109"/>'; textureAtlas_str += '</TextureAtlas>'; textureAtlas_xml = XML(textureAtlas_str); this.addEventListener(Event.ADDED_TO_STAGE, initialize); } private function initialize(eventObject:Event):void { var myBitmapData:BitmapData = new Pen(); var myTexture:Texture = Texture.fromBitmapData(myBitmapData); var myTextureAtlas:TextureAtlas = new TextureAtlas(myTexture, textureAtlas_xml); var frames:Vector.<Texture> = myTextureAtlas.getTextures("pen_walk"); var instance:MovieClip = new MovieClip(frames); addChild(instance); instance.addEventListener(Event.ENTER_FRAME, scroll); instance.y = (stage.stageHeight - instance.height) / 2; nWidth = instance.width; nScrollWidth = stage.stageWidth + nWidth; } private function scroll(eventObject:Event):void { var instance:DisplayObject = eventObject.currentTarget as DisplayObject; var nX:Number = instance.x - 5; if (nX < -nWidth) { nX += nScrollWidth; } instance.x = nX; } } }

これで、スプライトシートから切り出されたフレームの画像が水平にスクロールします。ただし、[ムービープレビュー]を確かめると、最初のフレームの画像のまま切り替わらず、ムービークリップシンボルに作ったフレームアニメーションが再生されません(図012)。実はもう1つ、スプライトシートの画像を切り替えるという処理を加えなければならないのです。

[*3] [ライブラリ]で複数のシンボルを選んで、1つの[スプライトシートを生成]することもできます(図013)。

その場合、TextureAtlas.getTextures()メソッドに渡す引数のシンボル名により、VectorオブジェクトにどのシンボルのTextureエレメントを納めるかが定められます(図014)。

04 StarlingオブジェクトがもつJugglerオブジェクトにMovieClipインスタンスを加える

MovieClipインスタンスがもつTextureオブジェクト(VectorオブジェクトのTextureエレメント)を切り替えるということは、DisplayObject.enterFrameイベントにリスナーメソッドを加えて処理することになるのでしょうか。内部的には、そのような結果にはなります。けれど、リスナーメソッドを新たに定めることはありません。ステートメントをたった1行加えるだけです。

Starling.juggler.add(MovieClipオブジェクト)

このメソッドは、StarlingオブジェクトがもつJugglerというオブジェクトに、MovieClipインスタンスを加えます[*4]。すると、StarlingオブジェクトがDisplayObject.enterFrameイベントを受取るたびに、Jugglerの中のMovieClipインスタンスにTextureオブジェクトを切り替えるよう命じるのです。このステートメントを加えたのが次のスクリプト003です。なお、Starlingオブジェクトを参照するために、Starlingクラスのimport宣言が加わりました。

スクリプト003 MovieClipインスタンスをJugglerオブジェクトに加えたStarlingルートクラスの定義(003_Jugglar)

// ActionScript 3.0クラス定義ファイル: MySprite.as package { import flash.display.BitmapData; import starling.core.Starling; import starling.display.Sprite; import starling.display.DisplayObject; import starling.display.MovieClip; import starling.textures.TextureAtlas; import starling.textures.Texture; import starling.events.Event; public class MySprite extends Sprite { private var nWidth:Number; private var nScrollWidth:Number; private var textureAtlas_xml:XML; public function MySprite() { var textureAtlas_str:String = ""; textureAtlas_str += '<?xml version="1.0" encoding="UTF-16"?>'; textureAtlas_str += '<TextureAtlas imagePath="animation.png">'; textureAtlas_str += '<SubTexture name="pen_walk0000" x="0" y="0" width="82" height="109"/>'; textureAtlas_str += '<SubTexture name="pen_walk0001" x="0" y="0" width="82" height="109"/>'; textureAtlas_str += '<SubTexture name="pen_walk0002" x="0" y="109" width="82" height="109"/>'; textureAtlas_str += '<SubTexture name="pen_walk0003" x="82" y="0" width="82" height="109"/>'; textureAtlas_str += '<SubTexture name="pen_walk0004" x="82" y="0" width="82" height="109"/>'; textureAtlas_str += '<SubTexture name="pen_walk0005" x="82" y="109" width="82" height="109"/>'; textureAtlas_str += '</TextureAtlas>'; textureAtlas_xml = XML(textureAtlas_str); this.addEventListener(Event.ADDED_TO_STAGE, initialize); } private function initialize(eventObject:Event):void { var myBitmapData:BitmapData = new Pen(); var myTexture:Texture = Texture.fromBitmapData(myBitmapData); var myTextureAtlas:TextureAtlas = new TextureAtlas(myTexture, textureAtlas_xml); var frames:Vector.<Texture> = myTextureAtlas.getTextures("pen_walk"); var instance:MovieClip = new MovieClip(frames); addChild(instance); Starling.juggler.add(instance); instance.addEventListener(Event.ENTER_FRAME, scroll); instance.y = (stage.stageHeight - instance.height) / 2; nWidth = instance.width; nScrollWidth = stage.stageWidth + nWidth; } private function scroll(eventObject:Event):void { var instance:DisplayObject = eventObject.currentTarget as DisplayObject; var nX:Number = instance.x - 5; if (nX < -nWidth) { nX += nScrollWidth; } instance.x = nX; } } }

これでスプライトシートのアニメーションがMovieClipインスタンスで再生されます。[ムービープレビュー]を確かめると、もとのムービークリップシンボルに設定したフレームアニメーションがスプライトシートにより再生されながら、MovieClipインスタンスは水平スクロールします(図015)。

[*4] 正確には、静的プロパティStarling.jugglerでStarlingオブジェクトがもつJugglerオブジェクトを参照し、Juggler.add()メソッドによりMovieClipインスタンスを加えています。

05【Advanced】StarlingフレームワークでJugglerオブジェクトが何をしているか

少し踏み込んだ話題として、StarlingフレームワークでJugglerオブジェクトがどのようにアニメーションを進めているのか、もう少し詳しくご説明します。StarlingオブジェクトがもつJugglerオブジェクトは、静的プロパティStarling.jugglerで参照されました。そして、Jugglerオブジェクトには、Juggler.add()メソッドによりMovieClipインスタンスが加えられます。

厳密には、Juggler.add()メソッドの引数は、データ型がインターフェイスIAnimatableで定められています。IAnimatableは、MovieClipやTweenのほか、Jugglerクラスも実装するインターフェイスです(図016)[*5]。advanceTime()メソッドを備えなければならないとされます。

Starlingクラスは、StageオブジェクトのDisplayObject.enterFrameイベントにリスナーメソッドを加え、リスナーメソッドはStarling.jugglerで参照されるオブジェクトのJuggler.advanceTime()メソッドを呼出します。そして、Juggler.advanceTime()メソッドは、Juggler.add()メソッドで加えられた全てのインスタンス(IAnimatableデータ型)を順に取出して、それぞれのadvanceTime()メソッドを呼出します。

こうして、StarlingオブジェクトがもつJugglerオブジェクトに加えられた全てのインスタンスのadvanceTime()メソッドが、フレームレートの頻度で呼出されます[*6]。MovieClip.advanceTime()は、MovieClipインスタンスに設定されたVectorオブジェクトのTextureエレメントを切替えて、アニメーションが再生されることになるのです。

[*5] Tweenクラスについては、「StarlingフレームワークのTweenクラスを使ったアニメーション」をお読みください。

[*6] フレームレートで行う処理は、DisplayObject.enterFrameイベントにリスナーを加えてもできるはずです。イベントリスナーの仕組みをあえて使わず、Jugglerクラスを定めたのは処理を最適化するためだと考えられます。なお、前出注[*5]「StarlingフレームワークのTweenクラスを使ったアニメーション」の03「【Advanced】Jugglerオブジェクトの仕事」に、Jugglerクラスの実装をコードも引用して解説しました。