【連載】Starlingフレームワークを用いたStage3Dによる2Dアニメーション 第4回 StarlingフレームワークのTweenクラスにおける最適化とJugglerクラスの実装 [Edit]

前回の「StarlingフレームワークのTweenクラスによるアニメーション」では、TweenとJugglerのふたつのクラスを使ったトゥイーンアニメーションについて解説しました。今回もこのふたつのクラスを扱います。ただし、トゥイーンアニメーションのつくり方ではなく、TweenとJugglerクラスをお題として、Starlingフレームワークがどのように最適化をはかっているのか、その工夫や考え方についてご説明します。そして、TweenやJugglerクラスの実装も覗いてみます。今回は、中級者寄りの内容です。

  • サンプルファイル: スクリプト002と003 (Starling_04_002_003.zip/Flash CS6形式/約25KB)
    ダウンロードファイルには、Starlingフレームワークのライブラリは含まれていません。予めインストールしてください。

01 イベントをコールバック関数で扱う

まずは、前回書いたスクリプト003(再掲)をお題にしましょう。3つのTweenオブジェクトに異なるイージングを定めて、順にトゥイーンアニメーションさせました。このスクリプトにおける課題は、ひとつのTweenオブジェクトのアニメーションが済んだことをどのようにして捉えるかでした。

第3回スクリプト003■3つのTweenオブジェクトのトゥイーンを順にアニメーションさせる(再掲)

// ActionScript 3.0クラスファイル: MySprite.as
package  {
	import starling.core.Starling;
	import starling.display.Sprite;
	import starling.display.Image;
	import starling.textures.Texture;
	import starling.events.Event;
	import starling.animation.Tween;
	import starling.animation.Transitions;
	import flash.display.BitmapData;
	public class MySprite extends Sprite {
		private var instance:Image;
		private var myTransitions:Vector.<String>;
		private var positions:Vector.<Number>;
		private var ratios:Vector.<Number> = new <Number>[0.5, 1];
		private var radians:Vector.<Number> = new <Number>[Math.PI * 2, -Math.PI * 2];
		private var i:uint = 0;
		public function MySprite() {
			addEventListener(Event.ADDED_TO_STAGE, initialize);
		}
		private function initialize(eventObject:Event):void {
			var myBitmapData:BitmapData = new Pen();
			var myTexture:Texture = Texture.fromBitmapData(myBitmapData);
			instance = new Image(myTexture);
			var halfWidth:Number = instance.width / 2;
			myTransitions = new <String>[
				Transitions.EASE_IN_OUT_ELASTIC,
				Transitions.EASE_IN_OUT_BOUNCE,
				Transitions.EASE_OUT_IN_ELASTIC
			];
			positions = new <Number>[stage.stageWidth - halfWidth / 2, halfWidth];
			instance.pivotX = halfWidth;
			instance.pivotY = instance.height / 2;
			instance.y = stage.stageHeight / 2;
			addChild(instance);
			setTween();
		}
		private function setTween():void {
			if (myTransitions.length) {
				var myTransition:String = myTransitions.shift();
				var ratio:Number = ratios[i];
				var myTween:Tween = new Tween(instance, 7, myTransition);
				myTween.moveTo(positions[i], instance.y);
				myTween.fadeTo(ratio);
				myTween.scaleTo(ratio);
				myTween.animate("rotation", radians[i]);
				myTween.onComplete = setTween;
				Starling.juggler.add(myTween);
				i ^= 1;
			}
		}
	}	
}

そのために、トゥイーンを定めるメソッド(setTween())の中で、Tween.onCompleteプロパティにコールバック関数を定めました。プロパティの参照するTweenオブジェクトのトゥイーンアニメーションが終わると、Tween.onCompleteプロパティのコールバック関数が呼出されます。

private function setTween():void {
    if (myTransitions.length) {
        var myTransition:String = myTransitions.shift();
        var myTween:Tween = new Tween(instance, 7, myTransition);
        // トゥイーンの設定
        myTween.onComplete = setTween;
        Starling.juggler.add(myTween);
        // ...[中略]...
    }
}

イベントにコールバック関数を定めて呼出すのは、ActionScript 2.0におけるイベントハンドラメソッドの仕組みと同じです。ActionScript 3.0では、イベントハンドラメソッドに替えて、イベントリスナーが採入れられました。イベントがより扱いやすくなり、応用範囲も広がりました。

もっとも、イベントが起こったときに、ただ関数をひとつ呼出したいだけの場合もあります。イベントリスナーの応用範囲が広いということは、そういう場合には要らない処理も中に含まれています。Tweenクラスは、コールバック関数という制約のある仕組みを用いることで、無駄な処理を省いているのです。

たとえば、イベントリスナーであれば、ひとつのインスタンスの同じイベントに、リスナー関数をいくつでも加えられます。しかし、Tween.onCompleteプロパティにはコールバック関数はひとつしか定められません。別の関数を加えれば、前の関数は上書きされてしまうからです。逆にいえば、Tweenクラスにはひとつのイベントで呼出す関数を複数もつ仕組みが要らず、決まったプロパティ(Tween.onComplete)に納められた関数ひとつを呼出せばよいのです。

ここで、Tweenクラスの実装を覗いてみます。StarlingオブジェクトがもつJugglerオブジェクトは後述するとおり、Juggler.add()メソッドで加えられたインスタンスに対して、フレームレートでそのインスタンスのadvanceTime()というメソッドを呼出します。Tweenクラスにも、Tween.advanceTime()メソッドが備わっています。引数には、前回の呼出しからの経過秒数が渡されます。

Tween.advanceTime()メソッドの実装で、Tween.onCompleteプロパティに関わるステートメントは以下のとおりです。頭に小文字の"m"がつくプロパティは、クラスに内部的(private)に宣言されています。mCurrentTimeはTween.currentTime、mTotalTimeがTween.totalTime、そしてmOnCompleteはTween.onCompleteプロパティに定められた値をもちます。プロパティTween.currentTimeはトゥイーンを始めてからの累計経過時間、Tween.totalTimeがコンストラクタの第2引数に渡したトゥイーンにかける合計時間です。

これまでの経過時間(previousTime)はまだ合計時間(mTotalTime)に達せず、新たに経過時間(time)が加わると合計時間を超えるときが、トゥイーンを終えるべきときです。その場合には、コールバック関数(mOnComplete)が定められているのを確かめたうえで、呼出しています。呼出す関数はひとつしかありませんので端的です。

public function advanceTime(time:Number):void {
	// ...[中略]...
	var previousTime:Number = mCurrentTime;
	mCurrentTime += time;
	// ...[中略]...
	if (previousTime < mTotalTime && mCurrentTime >= mTotalTime) {
		// ...[中略]...
		if (mOnComplete != null) mOnComplete.apply(null, mOnCompleteArgs);
	}
}

なお、コールバック関数(mOnComplete)を直に呼出さず、Function.apply()メソッドを用いているのは引数が渡せるようにするためです。予めTween.onCompleteArgsプロパティ(内部プロパティmOnCompleteArgs)に引数にしたい値を配列で定めておけば、コールバック関数に渡されます。Tween.advanceTime()メソッドとそのTween.onCompleteプロパティの実装に関してご紹介したプロパティを表001にまとめました。

表001■Tween.advanceTime()メソッドとTween.onCompleteの実装に関わるTweenクラスのプロパティ
プロパティ・メソッド 説明
advanceTime(time:Number):void 引数timeの秒数分トゥイーンを進める。インスタンスをStarling.jugglerプロパティが参照するJugglerオブジェクトに加えると、フレームレートで呼出される。
currentTime:Number [読取り専用] トゥイーンを始めてからの累計経過秒数。
totalTime:Number [読取り専用] トゥイーンにかける合計秒数。コンストラクタの第2引数で定められた時間。
onCompleteArgs:Array Tween.onCompleteプロパティに定めたコールバック関数に渡す引数をエレメントとして納めた配列。

02 Tweenオブジェクトを使い回す

前掲第3回スクリプト003について、もうひとつ考えたいことがあります。それは、トゥイーンを定めるメソッド(setTween())が、呼出されるたびにTween()コンストラクタにより新たなインスタンスをつくっていることです。Tween.onCompleteプロパティのコールバック関数として定めたこのメソッドが呼出されるとき、古いTweenインスタンスのトゥイーンはすでに終わっているので、もはや要りません。そういうときは、新たなインスタンスをつくるより、古いインスタンスを使い回した方がエコ、つまりお得になるのです。

private function setTween():void {
    if (myTransitions.length) {
        var myTransition:String = myTransitions.shift();
        var myTween:Tween = new Tween(instance, 7, myTransition);
        // トゥイーンの設定
        myTween.onComplete = setTween;
        Starling.juggler.add(myTween);
        // ...[中略]...
    }
}

すでにあるオブジェクトを初期化して使い回した方がお得な理由は大きくふたつあります。

  1. オブジェクトは新たにつくるより設定し直す方が速い
  2. 古いオブジェクトを片づけるガベージコレクションの手が煩わされない

第1に、オブジェクトは基本的に新しくつくるより、プロパティなどの設定をし直す方が速く済みます。そして第2に、古いオブジェクトを上書きして新たにつくり直すと、要らなくなったオブジェクトはメモリから消さなければなりません。その仕事は「ガベージコレクション」という仕組みが担います[*1]。しかし、ガベージコレクションは重い処理なので、要らないオブジェクトをすぐにはかたづけません。消されるまでの間、オブジェクトはメモリに居座ります。すでにあるオブジェクトを使い回せば、ガベージコレクションの手を煩わせずに済むのです。

オブジェクトを使い回す方法は、クラスによって変わってきます。Tweenクラスには、Tween.reset()という初期化のメソッドが備わっています。引数はTween()コンストラクタと同じです[*2]。つまり、コンストラクタで新たなインスタンスをつくる替わりに、すでにあるTweenオブジェクトに対してこのメソッドを呼出せばよいのです。

Tweenオブジェクト.reset(インスタンス, 時間, イージング)

すると、トゥイーンを定めるメソッド(setTween())は、つぎのように書替えることになります。Tweenインスタンスは使い回しますので、ローカル変数でなく、プロパティ(myTween)として宣言しておきます。そのプロパティにまだオブジェクトが納められていなければ、Tween()コンストラクタで新たなインスタンスをつくります。けれど、すでにインスタンスがあれば、Tween.reset()メソッドによりインスタンスを初期化して使い回します。

private var myTween:Tween;
// ...[中略]...
private function setTween():void {
    if (myTransitions.length) {
        var myTransition:String = myTransitions.shift();
        // var myTween:Tween = new Tween(instance, 7, myTransition);
        if (myTween) {
            myTween.reset(instance, 7, myTransition);
        } else {
            myTween = new Tween(instance, 7, myTransition);
        }
        // トゥイーンの設定
        myTween.onComplete = setTween;
        Starling.juggler.add(myTween);
        // ...[中略]...
    } else {
        myTween = null;
    }
}

なお、すべてのトゥイーンが済んで、イージングを納めたVectorオブジェクト(myTransitions)が空になったとき、Tweenインスタンスの入ったプロパティ(myTween)にnullを与えて参照は消しました。ひとつのTweenオブジェクトを使い回すように書直したStarlingルートクラス(MySprite)はつぎのスクリプト001のとおりです。

スクリプト001■ひとつのTweenオブジェクトを使い回して順にトゥイーンアニメーションさせる

// ActionScript 3.0クラスファイル: MySprite.as
package  {
	import starling.core.Starling;
	import starling.display.Sprite;
	import starling.display.Image;
	import starling.textures.Texture;
	import starling.events.Event;
	import starling.animation.Tween;
	import starling.animation.Transitions;
	import flash.display.BitmapData;
	public class MySprite extends Sprite {
		private var instance:Image;
		private var myTween:Tween;
		private var myTransitions:Vector.<String>;
		private var positions:Vector.<Number>;
		private var ratios:Vector.<Number> = new <Number>[0.5, 1];
		private var radians:Vector.<Number> = new <Number>[Math.PI * 2, -Math.PI * 2];
		private var i:uint = 0;
		public function MySprite() {
			addEventListener(Event.ADDED_TO_STAGE, initialize);
		}
		private function initialize(eventObject:Event):void {
			var myBitmapData:BitmapData = new Pen();
			var myTexture:Texture = Texture.fromBitmapData(myBitmapData);
			instance = new Image(myTexture);
			var halfWidth:Number = instance.width / 2;
			myTransitions = new <String>[
				Transitions.EASE_IN_OUT_ELASTIC,
				Transitions.EASE_IN_OUT_BOUNCE,
				Transitions.EASE_OUT_IN_ELASTIC
			];
			positions = new <Number>[stage.stageWidth - halfWidth / 2, halfWidth];
			instance.pivotX = halfWidth;
			instance.pivotY = instance.height / 2;
			instance.y = stage.stageHeight / 2;
			addChild(instance);
			setTween();
		}
		private function setTween():void {
			if (myTransitions.length) {
				var myTransition:String = myTransitions.shift();
				var ratio:Number = ratios[i];
				if (myTween) {
					myTween.reset(instance, 7, myTransition);
				} else {
					myTween = new Tween(instance, 7, myTransition);
				}
				myTween.moveTo(positions[i], instance.y);
				myTween.fadeTo(ratio);
				myTween.scaleTo(ratio);
				myTween.animate("rotation", radians[i]);
				myTween.onComplete = setTween;
				Starling.juggler.add(myTween);
				i ^= 1;
			} else {
				myTween = null;
			}
		}
	}	
}

[*1] ガベージコレクションは、プログラムが動的に割当てたメモリのうち、要らなくなった領域を自動的に解放する仕組みです。Flash Playerでは、ActionScriptで使われている各オブジェクトへの参照を確かめ、参照がすべてなくなったオブジェクトをメモリから消し去ります。

ただし、すべての参照が失われたからといって、ただちにガベージコレクションが働く訳ではありません。ガベージコレクションは、負荷の少なくない処理です。そのため、Flash Playerの忙しさやメモリの空き具合によって、実行される度合いが変わります。なお、拙著『ActionScript 3.0による三次元表現ガイドブック』Column 01「ガベージコレクションと弱い参照」(PDFプレビュー公開)p.024-025および「DisplayObjectインスタンスの削除とガベージコレクション」をご参照ください。

[*2] Tweenクラスの実装では、Tween()コンストラクタはつぎのように、同じ引数でTween.reset()メソッドを呼出しています。

public function Tween(target:Object, time:Number, transition:String = "linear") {
    reset(target, time, transition);
}

03 Jugglerオブジェクトに加えたインスタンスはadvanceTime()メソッドが呼ばれる

StarlingオブジェクトがもつJugglerオブジェクト(Starling.jugglerプロパティ)にインスタンスを加えると、そのadvanceTime()メソッドがフレームレートで呼出されます。Jugglerオブジェクトに入れるのは、関数ではなくインスタンスです。けれど、呼出されるのはインスタンスに定められたadvanceTime()という決まった名前のメソッドですので、前述01「イベントをコールバック関数で扱う」でご説明したコールバック関数に含められます。したがって、DisplayObject.enterFrameイベント(定数Event.ENTER_FRAME)にリスナー関数を加えるより、応用の範囲は狭まるものの、最適化するうえではお得です[*3]

もっとも、Jugglerクラスそのものは、自らのオブジェクトにJuggler.add()メソッドで加えられたインスタンスのadvanceTime()メソッドを、フレームレートで実行するとは決められていません。それは、StarlingオブジェクトがもつJugglerオブジェクトの場合です。Jugglerオブジェクトを別につくれば、加えたインスタンスのadvanceTime()メソッドは好きなときに呼出せます。そのときは、JugglerオブジェクトのJuggler.advanceTime()メソッドを呼出せばよいのです。つまり、Jugglerクラスは、Juggler.advanceTime()メソッドが呼ばれたとき、自らに加えられたインスタンスのadvanceTime()メソッドを呼出す仕組みといえます。

Jugglerオブジェクト.advanceTime(経過秒数)

ただし、JugglerオブジェクトにJuggler.add()メソッドで加えるインスタンスには、必ずadvanceTime()メソッドが備わっていなければなりません。そのためのActionScript 3.0の仕掛けが「インターフェイス」です。改めてJuggler.add()メソッドの引数を確かめると、データ型はIAnimatableと定められています(表002)。このIAnimatableがインターフェイスです。

表002■Juggler.add()とJuggler.advanceTime()メソッド
プロパティ・メソッド 説明
add(object:IAnimatable):void インスタンスに引数objectのオブジェクトを加える。インスタンスからadvanceTime()メソッドを呼出す対象となる。
advanceTime(time:Number):void Juggler.add()メソッドで加えられたオブジェクトのadvanceTime()メソッドに、引数timeを経過秒数として渡して呼出す。

「Starling Framework Reference」でインターフェイスIAnimatableを調べると、IAnimatable.advanceTime()メソッドひとつだけが定められています。もっとも、インターフェイスのメソッドには中身がなく、名前と引数や戻り値のデータ型しか決まっていません。中身はインターフェイスを「実装」(implement)するクラスが定めるのです[*4]。インターフェイスIAnimatableを実装するクラス(implementor)は、DelayedCall、Juggler、MovieClip、Tweenの4つです(図001)。

図001■インターフェイスIAnimatableはメソッドの実装を定める
図001上

図001下

「Starling Framework Reference」でJugglerクラスを見ると、初めに継承(inheritance)に加えて実装(implements)が記されており、そこにインターフェイスIAnimatableが示されています(図002)。インターフェイスを実装すると、インターフェイスに定められたメソッドを同じ名前とデータ型で備えなければなりません。つまり、JugglerクラスにはadvanceTime()メソッドが必ずあると保証されるのです。

図002■JugglerクラスはインターフェイスIAnimatableを実装する
図002

では、Juggler.add()Juggler.advanceTime()メソッドの実装を確かめてみましょう。まず、JugglerクラスがインターフェイスIAnimatableを実装します。これは、class定義の後にimplements定義キーワードで定めます。つぎに、Juggler.add()メソッドで加えられるのも、インターフェイスIAnimatableを実装したオブジェクトです。そこで、それらのオブジェクトを納めるVectorオブジェクト(mObjects)を用意します。もちろん、ベース型はIAnimatableで定めます。Juggler.add()メソッドは、引数に受取ったオブジェクト(object)があり、すでに加えられたオブジェクトと重複しないことを確かめたうえで、Vectorオブジェクト(mObjects)に納めます。

Juggler.advanceTime()メソッドが呼ばれると、Vectorオブジェクト(mObjects)のエレメントを順に取出して、そのオブジェクト(object)のadvanceTime()メソッドを呼出します。それが戸惑うことなくできるのは、Juggler.add()メソッドの引数がIAnimatableインターフェイスで型指定されているので、advanceTime()メソッドを必ず備えていると確信できるからです。

public class Juggler implements IAnimatable {
	private var mObjects:Vector.<IAnimatable>;
	// ...[中略]...
	public function Juggler() {
		// ...[中略]...
		mObjects = new <IAnimatable>[];
	}
	// ...[中略]...
	public function add(object:IAnimatable):void {
		if (object && mObjects.indexOf(object) == -1) {
			mObjects.push(object);
			// ...[中略]...
		}
	}
	// ...[中略]...
	public function advanceTime(time:Number):void {   
		var numObjects:int = mObjects.length;
		// ...[中略]...
		var i:int;
		// ...[中略]...
		for (i=0; i < numObjects; ++i) {
			var object:IAnimatable = mObjects[i];
			if (object) {
				object.advanceTime(time);
				// ...[中略]...
			}
		}
		// ...[中略]...
	}
}

データ型はクラスだけでなく、インターフェイスで定めてもよい、というのがこの仕組みの大事なところです。クラスの継承は違っていても、同じインターフェイスが実装されたオブジェクトは同じに扱うことができるのです。継承が血筋で見ていると考えれば、インターフェイスは資格で扱うようなものといえるでしょう。

図003■継承が血筋ならインターフェイスは資格
図003

[*3] ActionScript 3.0定義済みのイベントリスナーでは、リスナー関数に渡すイベントオブジェクトがイベントを配信するたびに新たにつくられます。DisplayObject.enterFrameイベントですと、フレームレートの頻度で、リスナーの数だけイベントオブジェクトができるのです。「Flash Platform のパフォーマンスの最適化」の「イベントモデルとコールバック」はつぎのように説明します(日本語が少しわかりにくいので、筆者が原文より邦訳しました)。

定義済みのイベントモデルは、従来のコールバック関数を用いるよりも、遅くなったり、多くのメモリを費やす場合があります。イベントオブジェクトをつくって、メモリに割当てることが、パフォーマンスを引下げるのです。たとえば、Event.ENTER_FRAMEイベントにリスナーを加えると、そのイベントリスナーのために新たなイベントオブジェクトが毎フレームつくられます。

[*4] 同じ名前のメソッドを同じように扱いながら、クラスによってその中身が変えられるという性質は、オブジェクト指向プログラミング言語で「ポリモーフィズム」と呼ばれます(「条件分岐をポリモーフィズムに置換える」参照)。

04 Starlingオブジェクトに備わるJugglerオブジェクト

Starling.jugglerプロパティで参照されるJugglerオブジェクトは、Juggler.advanceTime()メソッドの呼出しをどのように受取っているのでしょうか。内部的には、StarlingオブジェクトがFlash PlayerからEvent.ENTER_FRAMEイベントを受取って、Starling.advanceTime()メソッドからJuggler.advanceTime()メソッドを呼出しています(表003参照)[*5]

表003■Starlingクラスのアニメーションを進めるメソッド
プロパティ・メソッド 説明
nextFrame():void Starling.advanceTime()Starling.render()メソッドを呼出す。
advanceTime(passedTime:Number):void 表示リストにEvent.ENTER_FRAMEイベントを配信し、Jugglerオブジェクトを進め、タッチ操作の処理を行う。
render():void 表示リストすべてを再描画する。

Starlingクラスの実装を確かめてみましょう。Starling()コンストラクタは、内部的(private)なプロパティ(mJuggler)にJugglerオブジェクトをつくって与え、StageオブジェクトのEvent.ENTER_FRAMEイベントにリスナーメソッド(onEnterFrame())を加えます。このとき参照しているのは、コンストラクタが第2引数に受取ったFlashに定義済みのStageオブジェクトであることにご注目ください。つまり、Starlingオブジェクトは、Event.ENTER_FRAMEイベントをFlash界から受取り、それをStarling界に配信しているのです。

Event.ENTER_FRAMEイベントのリスナーメソッド(onEnterFrame())は、まずStarling.nextFrame()メソッドを呼出します。するとつぎに、Starling.advanceTime()メソッドが呼出されます(前掲表003参照)。そして、このメソッドがJugglerオブジェクト(mJuggler)のJuggler.advanceTime()メソッドを呼出すのです。なお、StarlingクラスはIAnimatableを実装していませんので、Starling.advanceTime()はインターフェイスの実装にもとづくメソッドではありません。

public class Starling extends EventDispatcher {
	// ...[中略]...
	private var mJuggler:Juggler;
	// ...[中略]...
	public function Starling(rootClass:Class, stage:flash.display.Stage, 
		viewPort:Rectangle = null, stage3D:Stage3D = null,
		renderMode:String = "auto", profile:String = "baselineConstrained") {
		// ...[中略]...
		mJuggler = new Juggler();
		// ...[中略]...
		stage.addEventListener(Event.ENTER_FRAME, onEnterFrame, false, 0, true);
		// ...[中略]...
	}
	// ...[中略]...
	public function nextFrame():void {
		// ...[中略]...
		advanceTime(passedTime);
		// ...[中略]...
	}
	// ...[中略]...
	public function advanceTime(passedTime:Number):void {
		// ...[中略]...
		mJuggler.advanceTime(passedTime);
		// ...[中略]...
	}
	// ...[中略]...
	private function onEnterFrame(event:Event):void {
		// ...[中略]...
		nextFrame();
		// ...[中略]...
	}
	// ...[中略]...
}

[*5] Starling.advanceTime()メソッドは、表示リストにEvent.ENTER_FRAMEイベントも配信します(前掲表003参照)。具体的には、Stage.advanceTime()メソッドを呼出し、その中からDisplayObjectContainer.broadcastEvent()メソッドEvent.ENTER_FRAMEイベントのオブジェクトを渡して配信しています。

05 Jugglerオブジェクトでインスタンスをアニメーションさせる

フレームごとに関数を呼出して動きを進めるだけのアニメーションでしたら、Event.ENTER_FRAMEイベントにリスナーとして加えるより、Jugglerオブジェクトで進めた方が軽くなります。ただし、JugglerオブジェクトにJuggler.add()メソッドで加えるには、インスタンスがインターフェイスIAnimatableを実装していなければなりません。そのようなクラスのサンプルをつくってみましょう。

お題には、第1回「Starlingフレームワークで書く初めてのアニメーション」のスクリプト005(再掲)を採上げます。[ライブラリ]のビットマップ(クラスPen)からインスタンスをつくってステージに置き、Event.ENTER_FRAMEイベントで水平スクロールさせています。このスクロールするインスタンスを、IAnimatableインターフェイスが実装されたクラスで定めます。

第1回スクリプト005■インスタンスをステージ左端から右端に水平スクロールさせる(再掲)

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

水平スクロールするクラス(MyImage)は、コンストラクタにTextureオブジェクトとスクロールする左右両端の座標を引数として渡すことにします。

new MyImage(Textureオブジェクト, 左端座標, 右端座標)

アニメーションするインスタンス自身の振舞いはこの新たなクラス(MyImage)に定めますので、Starlingルートクラス(MySprite)からそれらのステートメントはつぎのスクリプト002のように除いてしまいます。そして、Event.ENTER_FRAMEイベントにリスナー関数を加えるのではなく、IAnimatableインターフェイスが実装されたインスタンス(instance)をStarling.jugglerプロパティの参照にJuggler.add()メソッドで渡します。

スクリプト002■IAnimatableインターフェイスが実装されたインスタンスをStarling.jugglerにJuggler.add()メソッドで加える

// ActionScript 3.0クラスファイル: MySprite.as
package  {
	import starling.core.Starling;
	import starling.display.Sprite;
	import starling.textures.Texture;
	import starling.events.Event;
	import flash.display.BitmapData;
	public class MySprite extends Sprite {
		private var instance:MyImage;
		public function MySprite() {
			addEventListener(Event.ADDED_TO_STAGE, initialize);
		}
		private function initialize(eventObject:Event):void {
			var myBitmapData:BitmapData = new Pen();
			var myTexture:Texture = Texture.fromBitmapData(myBitmapData);
			var stageWidth:int = stage.stageWidth;
			instance = new MyImage(myTexture, 0, stageWidth);
			instance.x = stageWidth + instance.width / 2;
			instance.y = stage.stageHeight / 2;
			addChild(instance);
			Starling.juggler.add(instance);
		}
	}	
}

それでは、本題のIAnimatableインターフェイスが実装されたクラス(MyImage)の定め方です(スクリプト003)。Imageクラスを継承(extends)し、インターフェイスIAnimatableを実装(implements)します。コンストラクタが引数に受取ったTextureオブジェクト(myTexture)は、スーパークラス(Image)のコンストラクタが求めているので、super()ステートメントで渡します。

そして、インターフェイスIAnimatableが決めたとおりの名前とデータ型で、advanceTime()メソッドを定めなければなりません。フレームごとのアニメーションの動きは、このメソッドで扱います。これで、[ライブラリ]のビットマップからつくられたインスタンスが、ステージを水平にスクロールします(図004)。アニメーションは、Starling.jugglerプロパティのJugglerオブジェクトから、インスタンスのadvanceTime()メソッドが呼出されて進みます。

スクリプト003■Imageクラスを継承しIAnimatableインターフェイスが実装されたアニメーションするクラス

// ActionScript 3.0クラスファイル: MyImage.as
package  {
	import starling.display.Image;
	import starling.textures.Texture;
	import starling.animation.IAnimatable;
	public class MyImage extends Image implements IAnimatable {
		private var scrollLeft:Number;
		private var scrollWidth:Number;
		public function MyImage(myTexture:Texture, left:Number, right:Number) {
			super(myTexture);
			var halfWidth:Number = width / 2;
			pivotX = halfWidth;
			pivotY = height / 2;
			scrollLeft = left - halfWidth;
			scrollWidth = right + width;
		}
		public function advanceTime(time:Number):void {
			var nX:Number = x;
			nX -= 5;
			if (nX < scrollLeft) {
				nX += scrollWidth;
			}
			x = nX;
		}
	}
}
図004■[ライブラリ]のビットマップからつくられたインスタンスが水平にスクロールする
図004

 

その他の記事