【連載】Starlingフレームワークを用いたStage3Dによる2Dアニメーション 第1回 Starlingフレームワークで書く初めてのアニメーション [Edit]

「Starling」は、Flash Player 11の新しい描画機能「Stage3D」にもとづいてつくられた2次元の描画用フレームワークです。ハードウェアの「GPU」を用いることにより、速くて滑らかな描画ができます。これから何回かにわたって、Starlingフレームワークによるスクリプティングを解説します。まずは、Stage3Dにインスタンスを置いて、簡単にアニメーションさせます。

なお、Starlingはオープンソースのフレームワークなので、予めダウンロードしてインストールしておかなければなりません。Starlingフレームワークのインストール方法については、FumioNonaka.com「Starlingフレームワークをインストールする」をお読みください。

01 Stage3DとStarlingフレームワーク

「Stage3D」は、2次元・3次元にかぎらず、スクリーンの描画を速め、きめ細かで滑らかなアニメーションを実現する技術です。Flash Player 11から備わりました。ハードウェアの「GPU」を使うことによって、描画の処理を飛躍的に高めます。ハードウェアにそもそもGPUが備わっていない場合や、チップセットやドライバが古いなどの理由でGPUを使えないときには、CUPによる描画に切り替わります。

「GPU」(Graphics Processing Unit)は、コンピュータの処理の中でも負荷が高くパフォーマンスに影響する画像処理の演算を扱うプロセッサです。単に画面を描くことだけでなく、その前に行う座標空間における変換やカラーの演算など描画に伴う大量の処理までCPUに代わって担います。画像処理の専門家が、描画のパフォーマンスを高めるということです。いってみれば、パティシエという専門家のいるレストランは、デザートに力が入っていて美味しいと期待できるようなものでしょう。

ただし、Stage3Dには少し気難しいところがあります。それは、専門用語で注文しなければならないことです。デザートとはかなり趣の異なる料理で、「ヤサイマシマシ、アブラオオメ!」といった難解な注文の飛び交うお店があります。素人にはハードルが高そうです。Stage3Dでも、たとえば[ライブラリ]のビットマップを少し変形して描くとしましょう(図001)。

図001■[ライブラリ]のビットマップを変形して描く
図001

簡単な処理にしては、ステートメント数が多めです(図002)。とはいえ、コメントを含めてフレームアクションで80行くらいですので、やった内容と比べて考えなければ長過ぎるというほどではありません。スクリプトも、もちろんActionScript 3.0の文法です。しかし、そのコメントだけ見ても、「テクスチャにアップロード』とか「頂点バッファ」や「インデックスバッファ」、「頂点シェーダ」に「断片シェーダ」など、専門用語だらけです。Stage3Dのプログラミングはアセンブラ(機械語)に近く、ハードウェアの画像処理を知らなければ書けないのです。

図002■Stage3Dのスクリプティング
図002

先ほどの「ヤサイマシマシ、アブラオオメ!」のお店でも、誰か通訳してくれれば素人でも入れるでしょう(「『ラーメン二郎』初心者のためのコール(呪文)ジェネレータ」)。Stage3Dで2次元のコンテンツをつくるとき、まさにその通訳を務めるのがStarlingフレームワークなのです。Stage3Dは表示リストの階層ももたなければ、アニメーションのフレームという考え方も知りません。けれど、Starlingフレームワークを使えば、つぎのようなおなじみのActionSctiptで意図したとおりの動きが得られます。

addChild(instance);
addEventListener(Event.ENTER_FRAME, rotate);

02 Stage3Dのコンテンツはどこに描かれるか

Stage3DのコンテンツはFlashのステージには描かれません。別のStage3Dの描画領域がつくられ、コンテンツはそこに表示されます。また、Starlingフレームワークは、このStage3Dの領域にStarlingのStageオブジェクトをつくります。Starlingフレームワークでつくるコンテンツは、Stageオブジェクトを頂点とする表示リストをもち、そこに表示オブジェクトが加えられます。これまでのFlashコンテンツと同じ感覚でオブジェクトが扱えるのです。

けれど、StarlingのステージはいわばStage3D界にあり、Flash界のステージとは違う次元にあります。コンテンツの中で動くDisplayObjectインスタンスも、同じように見えながら、Flash界とは別の世界の住人です。これがどういう意味をもつかは、この後のスクリプティングで明らかにします。また、Stage3Dの描画領域つまりStarling界が描かれるのは、Flashのステージよりも奥になるということも覚えておきましょう(図003)。

図003■StarlingコンテンツはFlashのステージの背面に描かれる
図003

そうすると、StarlingコンテンツはFlash Professionalのステージにエレメントを置いてつくるのではなさそうだ、ということは薄うす勘づかれるでしょう。そのとおりです。素材として[ライブラリ]のビットマップは使えるものの、インスタンスをつくって動かすのはActionScriptの仕事になります。そして、とくにStarlingでは、フレームアクションでなくクラスにして書かないと使い勝手はよくありません(何が面倒なのか興味ある方は「Starlingフレームワークのスクリプトをフレームアクションに書いてみる」をお読みください)。

クラスを定義するというところで、また少しハードルが上がった感じのする読者もいるでしょう。そこで、基本的なクラス定義の書き方から、Starlingフレームワークのスクリプティングを解説していくことにします。

03 Starlingルートクラスを定義する

まず、クラス定義をご存知の方は、ドキュメントクラスを書くのかと思われたかもしれません。けれど、それは違います。「ドキュメントクラス」というのは、FlashのStageオブジェクトのいわば第一子であるメインタイムラインを定めるものです。でも、すでにご説明したとおり、Starling界のステージはFlash界とは別世界です。したがって、これから書くのは、Starling界のStageオブジェクトの第一子を定義するクラスです。これを「Starlingルートクラス」と呼ぶことにします。

もちろん、Flash界の方にも、Starling界をつくるステートメントが要ります。それはたったの3行ですので、フレームアクションで足ります(スクリプト001)。まず、Starlingを使うためのimport宣言です。importディレクティブについては、クラスを定義するときに改めてご説明します。つぎに、Starling()コンストラクタnew演算子で呼出して、インスタンスをつくります。引数はこれからつくるルートクラス(MySprite)とFlash界のStageオブジェクト(DisplayObject.stageプロパティ)です。そして、Starling.start()メソッドでフレームワークを動かします。

スクリプト001■Starlingフレームワークを初期化するフレームアクション

// フレームアクション: メインタイムライン
import starling.core.Starling;
var myStarling:Starling = new Starling(MySprite, stage);
myStarling.start();

これで、Starling界がつくられて、ルートクラスで定めたインスタンスがFlash界と同じように動き出します。ルートクラスの定義に入る前に、前出「Starlingフレームワークをインストールする」でも説明した[パブリッシュ設定]を確かめておきましょう(図004)。[ターゲット]のFlash Playerは11.0以降です。また、[ハードウェアアクセラレーション]はつい忘れがちですのでご注意ください。なお、ActionScript 3.0の[ソースパス]と[ライブラリパス]については、「Starlingフレームワークをインストールする」をお確かめください。

図004■[パブリッシュ設定]で[ターゲット]と[ハードウェアアクセラレーション]を指定
図004

それでは、Starlingルートクラスを定義します。[新規]ファイル([ファイル]メニュー)として[ActionScript 3.0クラス]を選び、[クラス名](MySprite)を定めて開きます(図005)。[クラス名](MySprite)は、前掲スクリプト001で決めました。

図005■[新規ドキュメント]ダイアログボックスで[ActionScript 3.0クラス]の[クラス名]を定めて開く
図005

ActionScript 3.0クラスファイルには、クラス名にもとづいて最低限必要なスクリプトがすでに書かれています(図006)。このActionScript(AS)ファイルにクラス名をつけて、Flashムービー(FLA)ファイルと同じ場所に保存しておきます。

図006■最低限の記述が加えられたActionScript 3.0クラスファイル
図006

ActionScript 3.0のクラスにつけた名前は、クラスだけでなく、保存するファイルとコンストラクタメソッドの都合3箇所で使われます。そして、フレームアクションでいえば変数となるプロパティ(var)宣言と関数であるメソッド(function)定義を加えることができます。さらに、スクリプトで用いるクラスは、import宣言しておかなければなりません。なお、コンストラクタはクラスのインスタンスがつくられるとき呼出されるメソッドです。

// ActionScript 3.0クラスファイル: クラス名.as
package {
	// import宣言
	// プロパティ宣言
	public class クラス名 {
		public function クラス名() {  // コンストラクタ
			// 初期化の処理
		}
	}
	// メソッド定義
}

クラスとコンストラクタメソッドの定めに添えられたpublicというキーワードは、アクセス制御の属性を示します。これは、SNSで公開範囲を決めるのと似ています。publicは「全体に公開」で、誰からでもアクセスできます(コンストラクタはpublicがデフォルトで、これ以外の属性は指定できません)。

Starlingルートクラスには、さらに指定が加わります。ルートクラスはFlashのメインタイムラインの役割を果たすのですから、Stageオブジェクトの子になり、自らも表示リストに子を加えたりできなければなりません。ActionScript 3.0では「継承」という仕組みにより、すでに定められたクラスの機能つまりプロパティやメソッドなどを受継ぐことができます。

そこで、Spriteクラスをextends定義キーワードにより継承します。クラス定義の中で別のクラスを参照するときは、初めにその正式名(「完全修飾名」といいます)を明らかにしなければなりません。それが、import宣言です。Spriteクラスの完全修飾名「starling.display.Sprite」をimportに続けて記述します。

// ActionScript 3.0クラスファイル: クラス名.as
package {
	import starling.display.Sprite;
	// プロパティ宣言
	public class クラス名 extends Sprite {
		public function クラス名() {  // コンストラクタ
			// 初期化の処理
		}
	}
	// メソッド定義
}

少し面倒な決まりに思えるかもしれません。でも、import宣言はクラスの初めに1度するだけで、後はクラス名のみで参照できます。クラス定義という世界の中で、クラスがもつ呪文を使うには、初めに古の正式な名前を唱えるしきたりがあるのです。

import リュシータ・トゥエル・ウル・ラピュタ;
シータ.バルス();

さて、Spriteクラスの完全修飾名をもう一度ご覧ください。頭が「starling」です(クラスの前の記述を「パッケージ」といいます)。定義済みActionScript 3.0におけるSpriteクラスの完全修飾名は、クラスの前に「flash」で始まる「パッケージ」を添えた「flash.display.Sprite」です。つまり、クラス名は同じでも別人なのです。そして、これがStarlingフレームワークの通訳の仕組みです。クラスの名前や使うプロパティ・メソッドが同じでも彼らはStarling界の住人で、おなじみのActionScript 3.0のことばをStage3Dの専門用語に訳して命じてくれているのです(表001)[*1]

表001■ActionScript 3.0とStarlingフレームワークで名前は同じでも異なるプロパティやメソッドなど
プロパティやメソッドなど 実装するクラス
ActionScript 3.0定義済み Starlingフレームワーク
x/y/width/height/stageプロパティ flash.display.DisplayObject starling.display.DisplayObject
addChild()メソッド flash.display.DisplayObjectContainer starling.display.DisplayObjectContainer
addEventListener()メソッド flash.events.EventDispatcher starling.events.EventDispatcher
enterFrameイベント flash.display.DisplayObject starling.display.DisplayObject
ENTER_FRAME定数 flash.events.Event starling.events.Event

さて、Starlingルートクラスにステートメントを加えて、Starlingのステージに何か置きましょう(スクリプト002)。簡単なところで、Quadインスタンスを登場させます。ActionScript 3.0の定義済みクラスにはいないStarling界だけの住人で、矩形として描かれます。コンストラクタには、幅と高さおよび色を引数として渡します。もちろん、import宣言を加えます。

インスタンスをつくって表示リストに加える初期化の処理は、メソッド(initialize())として別に定めました。その中で用いたDisplayObjectContainer.addChild()メソッドは、Starling界(starlingパッケージ)のSpriteクラスを継承したルートクラスに対して呼出されています(前掲表001参照)。アクセス制御の属性privateは、公開範囲がもっとも狭く、定義したクラスの外からは参照できません。

スクリプト002■Quadインスタンスをステージに置く

// ActionScript 3.0クラスファイル: MySprite.as
package  {
	import starling.display.Sprite;
	import starling.display.Quad;
	public class MySprite extends Sprite {
		private var instance:Quad;
		public function MySprite() {
			initialize();
		}
		private function initialize():void {
			instance = new Quad(100, 100, 0x0000FF);
			addChild(instance);
		}
	}
	
}

前掲スクリプト001を書いたFLAファイルを[ムービープレビュー]または[パブリッシュ]すると、ステージ左上角に100×100ピクセルの青い正方形が描かれます(図007)。

図007■青い正方形のQuadインスタンスがステージに描かれた
図007


[*1] このため、フレームアクションにStarlingフレームワークのスクリプトを書くと、Flash界の同じ名前のクラスと重複が生じてしまいます。それを避けようとすれば、すべてのStarling界のクラスは参照するたびに正式名(完全修飾名)を使わなければならなくなります。ですから、Starlingフレームワークのスクリプトは、クラスで定めた方がよいのです。具体的な例は前出「Starlingフレームワークのスクリプトをフレームアクションに書いてみる」をお読みください。

なお、Flash Professionalのフレームアクションでは、ActionScript 3.0の基本的な定義済みクラスは自動的にimportされる仕組みになっています。

04 [ライブラリ]のビットマップをStarlingのステージに置く

Starlingフレームワークでコンテンツをつくりこむときは、素材はビットマップで用意することが多いです。そこで、[ライブラリ]のビットマップを、Starlingのステージに置いてみましょう。ActionScriptで扱えるように、[ビットマッププロパティ]ダイアログボックス(パネルメニューから[プロパティ]を選択)で[クラス]を設定しておきます(図008)。

図008■[ライブラリ]のビットマップに[クラス]を設定
図008上
図008下

Flash界つまりActionScript 3.0定義済みクラスでは、ふたつのクラスで扱います。まず、ビットマップに定めたクラスのインスタンスをつくります。このクラスは、BitmapDataクラスを継承しています(前掲図008下図の[基本クラス]参照)。ただし、このオブジェクトは、直に表示リストには加えられません。そこで、そのオブジェクトを引数にして、Bitmapインスタンスをつくります。そして、BitmapインスタンスをDisplayObject.addChild()メソッドに渡すのです。これで、[ライブラリ]のビットマップイメージを入れたBitmapインスタンスがステージに表れます。

var myBitmapData:BitmapData = new Pen();
var instance:Bitmap = new Bitmap(myBitmapData);
addChild(instance);

ところが、BitmapDataとBitmapのクラスはふたつともFlash界だけに住んでいて、Starling界には同じ名前のクラスがありません。ただし、それぞれの役割を担う別のクラスはあります(表002)。つまり、ビットマップイメージはTextureオブジェクトとしてつくり、Imageインスタンスに入れてStarlingのステージに置けばよいのです。

表002■ActionScript 3.0とStarlingフレームワークでビットマップを扱うためのクラス
オブジェクト クラス
ActionScript 3.0定義済み Starlingフレームワーク
ビットマップイメージ BitmapData Texture
ステージに表示する容れ物 Bitmap Image

ただ、ひとつ困るのが、[ライブラリ]のビットマップはあくまでBitmapDataを継承するということです。Flash界の住人は、Starling界には住めません。そこで、StarlingのTextureクラスには、静的メソッドTexture.fromBitmapData()が備わっています。名前のとおり、Flash界のBitmapDataオブジェクトからイメージを取出して、Textureインスタンスにするのです。

ここまでわかれば、Starlingルートクラスをどう書けばよいのか、おおむね察しがつくでしょう。おなじみのActionScript 3.0の作法にしたがえばよいのです(スクリプト003)。FLAファイルの[ムービープレビュー]を確かめると、[ライブラリ]のビットマップイメージがImageインスタンスに加えられて、Starlingのステージ左上角に描かれます(図009)。

スクリプト003■[ライブラリ]のビットマップからImageインスタンスをつくってステージに置く

// ActionScript 3.0クラスファイル: MySprite.as
package  {
	import starling.display.Sprite;
	import starling.display.Image;
	import starling.textures.Texture;
	import flash.display.BitmapData;
	public class MySprite extends Sprite {
		private var instance:Image;
		public function MySprite() {
			initialize();
		}
		private function initialize():void {
			var myBitmapData:BitmapData = new Pen();
			var myTexture:Texture = Texture.fromBitmapData(myBitmapData);
			instance = new Image(myTexture);
			addChild(instance);
		}
	}
	
}

図009■[ライブラリ]のビットマップがStarlingのステージに表示される
図009

つぎは、インスタンスの位置をステージの真ん中にします。Stageオブジェクトは、DisplayObject.stageプロパティで参照できます。すると、クラスMySpriteの初期化のメソッド(initialize())に、つぎのようなステートメントを加えればよさそうです。

// クラスMySpriteを修正
private function initialize():void {
	var myBitmapData:BitmapData = new Pen();
	var myTexture:Texture = Texture.fromBitmapData(myBitmapData);
	instance = new Image(myTexture);
	instance.x = stage.stageWidth / 2;
	instance.y = stage.stageHeight / 2;
	addChild(instance);
}

ところが、[ムービープレビュー]を試すと、[出力]パネルにランタイムエラー#1009が示されます。どこかにnullつまり参照できないオブジェクトがあるということです。これは、DisplayObject.stageプロパティを参照するタイミングに問題があります。

TypeError: Error #1009: nullのオブジェクト参照のプロパティまたはメソッドにアクセスすることはできません。

FLAファイルのフレームアクションでStarling()コンストラクタを呼出すと、第1引数に渡したルートクラスのインスタンスがつくられます。つまり、ルートクラスのコンストラクタが呼ばれるということです。すると、初期化のメソッド(initialize())に加えたDisplayObject.stageプロパティが参照されます。ところが、このときまだStarlingインスタンスはStarlingフレームワークのStageオブジェクトに子として加えられていません。Stageオブジェクトを頂点とする表示リストに加わらないかぎり、DisplayObject.stageプロパティは参照できず、値はnullになります。これは、ActionScript 3.0にもとからある決まりです。

// フレームアクション
var myStarling:Starling = new Starling(MySprite, stage);
// クラスMySprite
public function MySprite() {
	initialize();
}
private function initialize():void {
	// ...[中略]...
	instance.x = stage.stageWidth / 2;
	instance.y = stage.stageHeight / 2;
	// ...[中略]...
}

では、どうすればよいでしょう。StarlingインスタンスがStageオブジェクトの表示リストに加わるのを待ちます。そのイベントは、定義済みActionScript 3.0と同じDisplayObject.addedToStage(定数Event.ADDED_TO_STAGE)です。

// クラスMySprite
public function MySprite() {
	// initialize();
	addEventListener(Event.ADDED_TO_STAGE, initialize);
}

これだけでは、まだもの足りなさが残ります。Imageインスタンスの基準点は、左上角です。すると、インスタンスの基準点が真ん中にくるのであって、インスタンスの位置がステージ中央には見えません(図010)。インスタンスの幅と高さを加味して、位置を決めるべきでしょう。このとき、StarlingのDisplayObjectクラスには、インスタンスの基準点を動かすDisplayObject.pivotXDisplayObject.pivotYプロパティがあります。

図010■インスタンスの基準点がステージの真ん中にくる
図010

以上を考え合わせて、Starlingルートクラスはつぎのスクリプト004のように書替えました。初期化のメソッド(initialize())は、StarlingインスタンスがStageオブジェクトの表示リストに加わるのを待って呼出しています。そして、メソッドの本体では、Imageインスタンス(instance)の基準点をその中心に移しましたので、ステージの中央に配置されます(図011)。

スクリプト004■[ライブラリ]のビットマップからImageインスタンスをつくってステージ中央に置く

// 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;
		public function MySprite() {
			addEventListener(Event.ADDED_TO_STAGE, initialize);
		}
		private function initialize():void {
			var myBitmapData:BitmapData = new Pen();
			var myTexture:Texture = Texture.fromBitmapData(myBitmapData);
			instance = new Image(myTexture);
			instance.pivotX = instance.width / 2;
			instance.pivotY = instance.height / 2;
			instance.x = stage.stageWidth / 2;
			instance.y = stage.stageHeight / 2;
			addChild(instance);
		}
	}
	
}

図011■インスタンスがステージの真ん中に配置された
図011

05 Starlingフレームワークのアニメーション

描画がきわめて速いStage3Dで、静止画を見せたのでは意味がありません。アニメーションを加えましょう。使うイベントは、DisplayObject.enterFrame(定数Event.ENTER_FRAME)です。インスタンスをステージの右端から左端まで水平にスクロールさせます(図012)。Starlingを意識する必要はなく、いつもどおりのActionScript 3.0のスクリプティングです。

図012■ステージに置かれたビットマップが水平にスクロール
図012

前掲スクリプト004に加えるおもなステートメントは以下のとおりです。イベントリスナーのメソッド(scroll())は、毎フレームインスタンスの位置を左に動かし、スクロール範囲の左端(scrollLeft)を超えたら、水平座標にスクロール幅(scrollWidth)を加えて右端に移します。スクロール幅は、ステージ幅(stageWidth)にオフセットとしてインスタンスの幅(instanceWidth)を加えています。したがって、スクロールの左端も、インスタンスの幅の半分外側にしました。

private var scrollWidth:Number;
private var scrollLeft:Number;
private function initialize():void {
	// ...[中略]...
	var stageWidth:int = stage.stageWidth;
	// ...[中略]...
	var instanceWidth:Number = instance.width;
	scrollWidth = stageWidth + instanceWidth;
	scrollLeft = -instanceWidth / 2;
	// ...[中略]...
	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;
}

インスタンスの水平スクロールを加えたStarlingルートクラスは、つぎのスクリプト005のとおりです。Starling界の通訳をしてくれるクラスたちにより、Flash界そのままのことば(ActionScript 3.0)でアニメーションが加わりました。次回は、ムービークリップシンボルのアニメーションを、Starlingフレームワークに取込みます。Flash Professional CS6から備わったスプライトシートの書出しを使います。

スクリプト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():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;
		}
	}	
}

その他の記事