前回の「StarlingフレームワークのTweenクラスにおける最適化とJugglerクラスの実装」では、Jugglerクラスを用いたコールバックについてご説明しました。けれど、もちろんStarlingフレームワークでも、定義済みActionScript 3.0と同じようにイベントリスナーが使えます。そして、基本的な考え方には、違いがありません。ただ、具体的なイベントの扱いが異なったり、Starlingフレームワーク独自の工夫が加えられた部分もあります。今回は、そのあたりを中心に解説します。
まずは、[ライブラリ]のビットマップからImageインスタンスをつくって、ステージの真ん中に置きます(図001)。この処理は、すでに第1回「Starlingフレームワークで書く初めてのアニメーション」04「[ライブラリ]のビットマップをStarlingのステージに置く」でご説明しました。Starlingルートクラス(MySprite)は、後に掲げるスクリプト001のように定めました。
図001■[ライブラリ]のビットマップからつくられたインスタンスがステージ中央に置かれた
基本的な中身は、第1回スクリプト004と同じです。ただ、後で手を加えやすいようにメソッドを小分けしたり、プロパティも多めに宣言してあります。しかし、ひとつだけ第1回スクリプト004とも定義済みActionScript 3.0とも大きく違うところがあります。それは、DisplayObject.addedToStage
イベント(定数Event.ADDED_TO_STAGE
)のリスナーメソッド(initialize())が引数をもたないことです。
public function MySprite() { addEventListener(Event.ADDED_TO_STAGE, initialize); } // private function initialize(eventObject:Event):void { private function initialize():void { // ...[中略]... }
定義済みActionScript 3.0では、リスナーがイベントオブジェクトを引数に受取らないとエラーになりました。Starlingフレームワークでも、第1回スクリプト004のようにイベントオブジェクトは受取れます。けれど、DisplayObject.addedToStage
やDisplayObject.enterFrame
イベントでは、イベントオブジェクトをリスナーが使うことはほとんどないでしょう。そのようなときには、引数を省いてしまえるのです[*1]。Starlingルートクラス(MySprite)全体は、つぎのスクリプト001のとおりです。
// 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 center:Image;
private var stageWidth:int;
private var stageHeight:int;
private var centerX:Number;
private var centerY: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);
stageWidth = stage.stageWidth;
stageHeight = stage.stageHeight;
centerX = stageWidth / 2;
centerY = stageHeight / 2;
center = createCenter(myTexture);
}
private function createCenter(myTexture:Texture):Image {
var instance:Image = createImage(myTexture);
instance.x = centerX;
instance.y = centerY;
return instance;
}
private function createImage(myTexture:Texture):Image {
var instance:Image = new Image(myTexture);
instance.pivotX = instance.width / 2;
instance.pivotY = instance.height / 2;
addChild(instance);
return instance;
}
}
}
[*1] EventDispatcherクラスが内部的にイベントリスナーを呼出すメソッド(invokeEvent())は、非ドキュメントの |
Starlingフレームワークで扱う基本的なイベントのうちActionScript 3.0と勝手が違うのは、マウスを使った操作です。マウスとタッチスクリーンの操作は、すべてDisplayObject.touch
イベント(定数TouchEvent.TOUCH
)で扱います。つまり、マウスボタンを押すのも放すのも、あるいはドラッグするのもDisplayObject.touch
イベントひとつで捉えるということです。
何らかのマウス操作が行われたことだけは、DisplayObject.touch
イベントでわかります。それがどういう操作かは、イベントリスナーが引数に受取ったTouchEventオブジェクトから情報を得ることになります。まず、TouchEvent.getTouch()
メソッドでTouchオブジェクトを取出します。つぎに、そのTouch.phase
プロパティを調べると、マウス操作がわかるのです。前掲スクリプト001には、マウスドラッグの扱いを加えます。ドラッグに関わるマウス操作と、それぞれのTouch.phase
プロパティの値はつぎの表001のとおりです。
マウス操作 | イベント | Touch.phaseプロパティの値 |
マウスボタンを押す | DisplayObject.touch | TouchPhase.BEGAN |
ドラッグする | TouchPhase.MOVED | |
マウスボタンを放す | TouchPhase.ENDED |
Touch.phase
プロパティの値は文字列です。TouchPhaseクラスに各値が定数として定められていますので、それを用いるのがよいでしょう。マウスだけでなくタッチスクリーンの操作も併せて、TouchPhaseクラスの定数をつぎの表002にまとめました。
TouchPhase クラスの定数 |
操作 | |
タッチスクリーン | マウス | |
BEGAN | 画面に触れる | マウスボタンを押す |
ENDED | 画面から指を離す | マウスボタンを放す |
HOVER | - | マウスポインタを重ねる |
MOVED | 画面に触れた指を動かす | ボタンは押したままマウスを動かす |
STATIONARY | 画面に触れたまま動かさない | ボタンを押したままマウスは動かさない |
ここまで読まれて、Starlingフレームワークでマウス操作を扱うのは面倒そうに感じられたかもしれません。けれど、ドラッグは定義済みActionScript 3.0よりむしろ簡単です。
というのは、TouchPhaseというクラスの名前から想像できるように、マウスの操作が一連の流れの中の段階(phase)として捉えられています。そして、Touch.phase
プロパティが定数TouchPhase.MOVED
という値をとるのは、TouchPhase.BEGAN
の後の段階です。つまり、マウスボタンは押された後そのままマウスが動いているのですから、すなわちドラッグを意味します。定義済みActionScript 3.0のように、マウスを動かしているとき、ボタンが押されているかどうかなど調べずに済むのです[*2]。
それでは、前掲スクリプト001にマウスのドラッグの扱いを加えます。まずは、3つのクラスTouchとTouchEventおよびTouchPhaseを、新たにimport
宣言します。
import starling.events.Touch; import starling.events.TouchEvent; import starling.events.TouchPhase;
前述のとおり、インスタンスをマウスでドラッグして動かすだけでしたら、Touch.phase
プロパティの値が定数TouchPhase.MOVED
のとき処理すれば済んでしまいます(「Starlingフレームワークでインスタンスをドラッグ&ドロップする」をお読みください)。プロパティが他の値の場合も扱いに含めるため、ステージ上を水平ドラッグすると、その幅に応じてインスタンスが回るというお題で考えます(図002)。
図002■ステージ上を水平ドラッグするとその幅に応じてインスタンスが回る
ステージ上でマウスボタンを押してドラッグし、放すまでの3つのTouch.phase
プロパティの値それぞれについて処理を加えるには、スクリプトはつぎのような組立てになります。第1に、Stageオブジェクト(DisplayObject.stage
プロパティ)のDisplayObject.touch
イベント(定数TouchEvent.TOUCH
)にイベントリスナー(onTouch())を加えます。第2に、リスナーメソッドは引数のTouchEventオブジェクト(eventObject)から、TouchEvent.getTouch()
メソッドでTouchオブジェクト(myTouch)を得ます。念のためオブジェクトがあるかを調べ、なければ処理を終えます。そして第3に、TouchオブジェクトのTouch.phase
プロパティによりマウス操作を調べるのです。
private function initialize():void { // ...[中略]... stage.addEventListener(TouchEvent.TOUCH, onTouch); } private function onTouch(eventObject:TouchEvent):void { var myTouch:Touch = eventObject.getTouch(stage); if (!myTouch) return; switch (myTouch.phase) { case TouchPhase.BEGAN: // マウスボタンを押したときの処理 break; case TouchPhase.MOVED: // ドラッグ中の処理 break; case TouchPhase.ENDED: // マウスボタンを放したときの処理 break; } }
Touch.phase
プロパティの値をswitch
ステートメントで3つのcase
に分けたうえで、それぞれつぎのような処理を行います。第1に、プロパティ値がTouchPhase.BEGAN
つまりマウスボタンを押したときはドラッグの始まりですので、現在のマウスポインタの水平座標(Touch.globalX
プロパティ)の値をプロパティ(startX)に納めます。
第2に、Touch.phase
プロパティ値がTouchPhase.MOVED
のとき、ドラッグしている間の処理になります。マウスポインタの水平の動きから回転角(angle)を求め、インスタンスのDisplayObject.rotation
プロパティに定めます。ステージ幅(stageWidth)一杯に動かすと1回転(2πラジアン)するように比率を決めました。なお、回転のラジアン角は±2πの範囲に収まるよう、2πラジアン(PI_2)の剰余%
をとっています。
第3に、マウスボタンを放してドラッグを終えたTouchPhase.ENDED
がプロパティ値の場合です。現在の回転角をプロパティ(startAngle)に納めたうえで、その値をtrace()
関数で[出力]して確かめました。
private const PI_2:Number = Math.PI * 2; private var startAngle:Number = 0; private var startX:Number; // ...[中略]... private function onTouch(eventObject:TouchEvent):void { var myTouch:Touch = eventObject.getTouch(stage); if (!myTouch) return; switch (myTouch.phase) { case TouchPhase.BEGAN: startX = myTouch.globalX; break; case TouchPhase.MOVED: var angle:Number = startAngle + PI_2 * (myTouch.globalX - startX) / stageWidth; angle %= PI_2; center.rotation = angle; break; case TouchPhase.ENDED: startAngle += PI_2 * (myTouch.globalX - startX) / stageWidth; startAngle %= PI_2; trace(startAngle); // 確認用 break; } }
前掲スクリプト001を書替えたStarlingルートクラス(MySprite)は、以下のスクリプト002のとおりです。ステージを水平にドラッグすると、動かした方向と幅に応じて、真ん中に置かれたインスタンスが回ります。
// ActionScript 3.0クラスファイル: MySprite.as
package {
import starling.display.Sprite;
import starling.display.Image;
import starling.textures.Texture;
import starling.events.Event;
import starling.events.Touch;
import starling.events.TouchEvent;
import starling.events.TouchPhase;
import flash.display.BitmapData;
public class MySprite extends Sprite {
private const PI_2:Number = Math.PI * 2;
private var center:Image;
private var stageWidth:int;
private var stageHeight:int;
private var centerX:Number;
private var centerY:Number;
private var startAngle:Number = 0;
private var startX: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);
stageWidth = stage.stageWidth;
stageHeight = stage.stageHeight;
centerX = stageWidth / 2;
centerY = stageHeight / 2;
center = createCenter(myTexture);
stage.addEventListener(TouchEvent.TOUCH, onTouch);
}
private function onTouch(eventObject:TouchEvent):void {
var myTouch:Touch = eventObject.getTouch(stage);
if (!myTouch) return;
switch (myTouch.phase) {
case TouchPhase.BEGAN:
startX = myTouch.globalX;
break;
case TouchPhase.MOVED:
var angle:Number = startAngle + PI_2 * (myTouch.globalX - startX) / stageWidth;
angle %= PI_2;
center.rotation = angle;
break;
case TouchPhase.ENDED:
startAngle += PI_2 * (myTouch.globalX - startX) / stageWidth;
startAngle %= PI_2;
trace(startAngle); // 確認用
break;
}
}
private function createCenter(myTexture:Texture):Image {
var instance:Image = createImage(myTexture);
instance.x = centerX;
instance.y = centerY;
return instance;
}
private function createImage(myTexture:Texture):Image {
var instance:Image = new Image(myTexture);
instance.pivotX = instance.width / 2;
instance.pivotY = instance.height / 2;
addChild(instance);
return instance;
}
}
}
[*2] 逆に、Starlingフレームワークでは、単純なクリックを捉えるのは定義済みActionScript 3.0より手間がかかります。詳しくは、「Starlingフレームワークでインスタンスをクリックする」および「Starlingフレームワークでビットマップ上のクリックを検知する」をお読みください。 |
さて、本稿のお題は「イベントリスナー」です。そこで、ドラッグしたとき、イベントリスナーでインスタンスを回してみましょう。すると、スクリプトはこんな組立てになります。
イベントの配信は、EventDispatcher.dispatchEvent()
メソッドで行います。引数には新たなイベントオブジェクトを渡します。基本の構文はつぎのとおりです。
オブジェクト.dispatchEvent(new Event(イベント名))
すると、スクリプトにはつぎのような手を加えることになります。イベント名に決めた文字列("rotate")は、クラスの定数(ROTATE)にしました。これで、ドラッグしたときリスナーメソッド(rotateAtCenter())が呼出されます。
private const ROTATE:String = "rotate"; // ...[中略]... private function initialize():void { // ...[中略]... addEventListener(ROTATE, rotateAtCenter); } private function onTouch(eventObject:TouchEvent):void { var myTouch:Touch = eventObject.getTouch(stage); if (!myTouch) return; switch (myTouch.phase) { // ...[中略]... case TouchPhase.MOVED: // ...回転角を求める var rotateEvent:Event = new Event(ROTATE); dispatchEvent(rotateEvent); break; // ...[中略]... } } private function rotateAtCenter(eventObject:Event):void { // ...インスタンスを回す }
ここで考えなければならないのは、ドラッグの動きから求めた回転角を、リスナーメソッドにどうやって伝えるかです。ルートクラスのプロパティに入れて、リスナーメソッドからその値を参照する手はあります。けれど、リスナーメソッドにはイベントオブジェクトが送られるのですから、その中に加える方がスマートでしょう。
リスナーが使う値をイベントオブジェクトに入れて送るというのは、イベントリスナーの仕組みではたびたび用いられるやり方です。ただ、定義済みActionScript 3.0のEventクラスには、自由な値が加えられません。そこで、Eventのサブクラスを定めて、新たなプロパティを加え、そこに値を入れるのです。このイベントリスナーの仕組みにとくに問題はないものの、値をひとつ送りたいだけなのにサブクラスを定めるというのは、煩わしく感じられます。
そこで、StarlingフレームワークのEventクラスには、自由な値を入れられるプロパティがひとつ加わりました。それは、Event.data
プロパティです。もっとも、リファレンスには読取り専用とされています。実は、Event()
コンストラクタでインスタンスをつくるとき、第3引数に渡す値がEvent.data
プロパティの値になるのです。なお、第2引数は、イベントを表示オブジェクトの親に送る(バブリング)かどうかのブール(論理)値です(デフォルト値はfalse
)。
new Event(イベント名, バブリング, dataの値)
では、このEvent()コンストラクタの第3引数を使って、回転角をリスナー関数に送りましょう。前掲スクリプト002に、つぎのような手を加えます。まず、初期化のメソッド(initialize())で、回転のイベント(ROTATE)にリスナーメソッド(rotateAtCenter())を加えます。
つぎに、DisplayObject.touch
イベントのリスナーメソッド(onTouch())は、Touch.phase
プロパティの値がTouchPhase.MOVED
つまりドラッグしていたら、EventDispatcher.dispatchEvent()
メソッドで回転のイベントを配信します。このとき送るEventオブジェクトには、Event()
コンストラクタの第3引数で回転角(angle)を加えてあります。
そして、回転のリスナーメソッド(rotateAtCenter())は、受取ったEventオブジェクトのEvent.data
プロパティから回転角の値を取出して、インスタンスを回すのです。なお、Event.data
プロパティは任意の値を納めるため、データ型がObjectに定められています。したがって、他の型指定をした変数に納めるには、as
演算子でデータ型を評価し直します。
private const ROTATE:String = "rotate"; // ...[中略]... private function initialize():void { // ...[中略]... addEventListener(ROTATE, rotateAtCenter); } private function onTouch(eventObject:TouchEvent):void { var myTouch:Touch = eventObject.getTouch(stage); if (!myTouch) return; switch (myTouch.phase) { // ...[中略]... case TouchPhase.MOVED: var angle:Number = startAngle + PI_2 * (myTouch.globalX - startX) / stageWidth; angle %= PI_2; // center.rotation = angle; var rotateEvent:Event = new Event(ROTATE, false, angle); dispatchEvent(rotateEvent); break; // ...[中略]... } } private function rotateAtCenter(eventObject:Event):void { var angle:Number = eventObject.data as Number; center.rotation = angle; }
これで、前掲スクリプト002と同じように、ステージを水平にドラッグする方向と幅に応じて、真ん中に置かれたインスタンスが回ります。ただし、ドラッグしたときに、イベントリスナーの仕組みを使って、回転のイベント(ROTATE)をリスナーメソッド(rotateAtCenter())に送っています。このように書替えたStarlingルートクラス(MySprite)は、つぎのスクリプト003のとおりです。
// ActionScript 3.0クラスファイル: MySprite.as
package {
import starling.display.Sprite;
import starling.display.Image;
import starling.textures.Texture;
import starling.events.Event;
import starling.events.Touch;
import starling.events.TouchEvent;
import starling.events.TouchPhase;
import flash.display.BitmapData;
public class MySprite extends Sprite {
private const PI_2:Number = Math.PI * 2;
private const ROTATE:String = "rotate";
private var center:Image;
private var stageWidth:int;
private var stageHeight:int;
private var centerX:Number;
private var centerY:Number;
private var startAngle:Number = 0;
private var startX: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);
stageWidth = stage.stageWidth;
stageHeight = stage.stageHeight;
centerX = stageWidth / 2;
centerY = stageHeight / 2;
center = createCenter(myTexture);
stage.addEventListener(TouchEvent.TOUCH, onTouch);
addEventListener(ROTATE, rotateAtCenter);
}
private function onTouch(eventObject:TouchEvent):void {
var myTouch:Touch = eventObject.getTouch(stage);
if (!myTouch) return;
switch (myTouch.phase) {
case TouchPhase.BEGAN:
startX = myTouch.globalX;
break;
case TouchPhase.MOVED:
var angle:Number = startAngle + PI_2 * (myTouch.globalX - startX) / stageWidth;
angle %= PI_2;
var rotateEvent:Event = new Event(ROTATE, false, angle);
dispatchEvent(rotateEvent);
break;
case TouchPhase.ENDED:
startAngle += PI_2 * (myTouch.globalX - startX) / stageWidth;
startAngle %= PI_2;
trace(startAngle);
break;
}
}
private function createCenter(myTexture:Texture):Image {
var instance:Image = createImage(myTexture);
instance.x = centerX;
instance.y = centerY;
return instance;
}
private function rotateAtCenter(eventObject:Event):void {
var angle:Number = eventObject.data as Number;
center.rotation = angle;
}
private function createImage(myTexture:Texture):Image {
var instance:Image = new Image(myTexture);
instance.pivotX = instance.width / 2;
instance.pivotY = instance.height / 2;
addChild(instance);
return instance;
}
}
}
前掲スクリプト003は、ステージ上をドラッグしている間中、回転のイベントが配信されます。そして、そのたびに新たなEventオブジェクトがつくられることになります。前回の第4回「StarlingフレームワークのTweenクラスにおける最適化とJugglerクラスの実装」では、02「Tweenオブジェクトを使い回す」で「古いインスタンスを使い回した方がエコ、つまりお得になる」と説明しました。
すでにつくったEventオブジェクトはとっておいて、プロパティだけ書直せるとよさそうです。ところが、Eventクラスのプロパティは多くが読取り専用です[*3]。そこで、Starlingフレームワークには新たなイベント配信のメソッドEventDispatcher.dispatchEventWith()
が備わりました。役目はEventDispatcher.dispatchEvent()
と同じく、リスナーへのイベントの配信です。けれど、予めイベントオブジェクトをつくらなくてよいのです。引数は3つで、Event()
コンストラクタと同じです。
EventDispatcher.dispatchEventWith()
メソッドは、Event()
コンストラクタと同じ3つの引数を使って、まだ使い回せるEventオブジェクトがなければコンストラクタで新たなインスタンスをつくって送ります。そして、使ったオブジェクトは、クラスに蓄えられます。すると、つぎにEventDispatcher.dispatchEventWith()
メソッドを呼出したとき、すでにあるオブジェクトを取出し、コンストラクタでつくるときと同じように設定を書直して配信するのです。
オブジェクト.dispatchEventWith(イベント名, バブリング, dataの値)
前掲スクリプト003をこのEventDispatcher.dispatchEventWith()
メソッドで書替えるのは、つぎのようにとても簡単です。Event()
コンストラクタとEventDispatcher.dispatchEvent()
メソッドがひとつになったと考えればよいからです。これだけで、この頃のエコ家電のように無駄は省かれ、Eventオブジェクトが自動的に使い回されます。
private function onTouch(eventObject:TouchEvent):void { var myTouch:Touch = eventObject.getTouch(stage); switch (myTouch.phase) { case TouchPhase.MOVED: var angle:Number = startAngle + PI_2 * (myTouch.globalX - startX) / stageWidth; angle %= PI_2; // var rotateEvent:Event = new Event(ROTATE, false, angle); // dispatchEvent(rotateEvent); dispatchEventWith(ROTATE, false, angle); break; case TouchPhase.ENDED: } }
もっとも、インスタンスをひとつ回すだけなら、前のスクリプト002のように、プロパティ値を直に変えた方が手っ取り早いです。イベントリスナーは、イベントを受取るリスナーがいくつもあるときに役立ちます。そこで、インスタンスをもうひとつ加え、新たなリスナーメソッドで別の回し方をしてみましょう。
小さめのインスタンス(sub)を新たなメソッド(createSub())でつくり、ステージ真ん中上の方に置きます(図003)。TextureオブジェクトからImageインスタンスをつくって表示リストに加える処理は、すでに定めてあったメソッド(createImage())を使い回しました。
private var sub:Image; // ...[中略]... private function initialize():void { var myBitmapData:BitmapData = new Pen(); var myTexture:Texture = Texture.fromBitmapData(myBitmapData); // ...[中略]... sub = createSub(myTexture); // ...[中略]... } // ...[中略]... private function createSub(myTexture:Texture):Image { var instance:Image = createImage(myTexture); instance.scaleX = instance.scaleY = 0.5; instance.x = centerX; instance.y = instance.height / 2; return instance; } private function createImage(myTexture:Texture):Image { var instance:Image = new Image(myTexture); instance.pivotX = instance.width / 2; instance.pivotY = instance.height / 2; addChild(instance); return instance; }
図003■小さいインスタンスを中央上部に加えた
新たに加えた小さいインスタンス(sub)も、回転(ROTATE)のイベントにリスナーメソッド(rotateAround())を定めて加えます。このインスタンスは、ステージの中心を軸にして、時計回りに位置を回転します。中心の座標を(x, y)、回転する円の半径をrとすると、角θ回転した位置(x', y')は、つぎの式で表されます。
x' = x + r cosθ
y' = y + r sinθ
小さいインスタンスのリスナーメソッド(rotateAround())は、この式にもとづいてインスタンスの位置を定めます。なお、回転の起点が時計の12時の方向なので、回転角は-π/2ラジアン(-90°)から始めます。
private const PI_HALF:Number = Math.PI / 2; private var radius:Number; // ...[中略]... private function createSub(myTexture:Texture):Image { var instance:Image = createImage(myTexture); // ...[中略]... radius = (stageHeight - instance.height) / 2; addEventListener(ROTATE, rotateAround); return instance; } // ...[中略]... private function rotateAround(eventObject:Event):void { var angle:Number = eventObject.data as Number; var radians:Number = angle - PI_HALF; sub.x = centerX + radius * Math.cos(radians); sub.y = centerY + radius * Math.sin(radians); }
前掲スクリプト003のStarlingルートクラス(MySprite)にこれまでご説明した処理を加えると、以下のスクリプト004のようになります。ステージ上でドラッグすると、ふたつのインスタンスが異なるリスナーメソッドによりそれぞれステージ中央を軸に回ります。ふたつ目に加えた小さなインスタンスは、中心座標から一定の半径(radius)で位置を変えますので、ひとつ目のインスタンスの惑星のような動きになります(図004)。
図004■水平ドラッグすると真ん中のインスタンスが自転して小さいインスタンスは周囲を回る
// ActionScript 3.0クラスファイル: MySprite.as
package {
import starling.display.Sprite;
import starling.display.Image;
import starling.textures.Texture;
import starling.events.Event;
import starling.events.Touch;
import starling.events.TouchEvent;
import starling.events.TouchPhase;
import flash.display.BitmapData;
public class MySprite extends Sprite {
private const ROTATE:String = "rotate";
private const PI_2:Number = Math.PI * 2;
private const PI_HALF:Number = Math.PI / 2;
private var center:Image;
private var sub:Image;
private var stageWidth:int;
private var stageHeight:int;
private var centerX:Number;
private var centerY:Number;
private var startAngle:Number = 0;
private var startX:Number;
private var radius: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);
stageWidth = stage.stageWidth;
stageHeight = stage.stageHeight;
centerX = stageWidth / 2;
centerY = stageHeight / 2;
center = createCenter(myTexture);
sub = createSub(myTexture);
stage.addEventListener(TouchEvent.TOUCH, onTouch);
}
private function onTouch(eventObject:TouchEvent):void {
var myTouch:Touch = eventObject.getTouch(stage);
if (!myTouch) return;
switch (myTouch.phase) {
case TouchPhase.BEGAN:
startX = myTouch.globalX;
break;
case TouchPhase.MOVED:
var angle:Number = startAngle + PI_2 * (myTouch.globalX - startX) / stageWidth;
angle %= PI_2;
dispatchEventWith(ROTATE, false, angle);
break;
case TouchPhase.ENDED:
startAngle += PI_2 * (myTouch.globalX - startX) / stageWidth;
startAngle %= PI_2;
trace(startAngle);
break;
}
}
private function createCenter(myTexture:Texture):Image {
var instance:Image = createImage(myTexture);
instance.x = centerX;
instance.y = centerY;
addEventListener(ROTATE, rotateAtCenter);
return instance;
}
private function rotateAtCenter(eventObject:Event):void {
var angle:Number = eventObject.data as Number;
center.rotation = angle;
}
private function createSub(myTexture:Texture):Image {
var instance:Image = createImage(myTexture);
instance.scaleX = instance.scaleY = 0.5;
instance.x = centerX;
instance.y = instance.height / 2;
radius = (stageHeight - instance.height) / 2;
addEventListener(ROTATE, rotateAround);
return instance;
}
private function rotateAround(eventObject:Event):void {
var angle:Number = eventObject.data as Number;
var radians:Number = angle - PI_HALF;
sub.x = centerX + radius * Math.cos(radians);
sub.y = centerY + radius * Math.sin(radians);
}
private function createImage(myTexture:Texture):Image {
var instance:Image = new Image(myTexture);
instance.pivotX = instance.width / 2;
instance.pivotY = instance.height / 2;
addChild(instance);
return instance;
}
}
}
このように、あるイベントに対して複数のインスタンスに異なる振舞いをさせたいとき、イベントリスナーの仕組みが役立ちます。もっとも、前掲スクリプト004の場合なら、わざわざイベントリスナーに加えなくても、ドラッグしているときにそれぞれのメソッドを直接呼出せば済みます。
イベントリスナーのよいところは、イベントを配信する側がリスナーの中身を知らなくてよいことです。今回のお題でいえば、「回転」をひたすら伝えるだけで、それぞれのインスタンス(centerとsub)がどう「回転」するかは気にとめません。そういうことでいえば、回転するインスタンスにはそれぞれクラスが定められていて、クラスによって異なるメソッドをリスナーとして加えるというのが本領を発揮する場面です。
前掲スクリプト004は、イベントの流れをひと目で見て、イベントリスナーの仕組みがわかりやすいように、あえてルートクラスにすべての処理を書きました。ただ、処理結果はとくに変わらないものの、前掲スクリプト003から書替えて、イベントリスナーはインスタンスをつくるメソッド(createCenter()とcreateSub())から登録するようにしました(インスタンスにクラスを定めるときは、このかたちになることが多いでしょう)。
private function initialize():void { var myBitmapData:BitmapData = new Pen(); var myTexture:Texture = Texture.fromBitmapData(myBitmapData); // ...[中略]... center = createCenter(myTexture); sub = createSub(myTexture); stage.addEventListener(TouchEvent.TOUCH, onTouch); // addEventListener(ROTATE, rotateAtCenter); } // ...[中略]... private function createCenter(myTexture:Texture):Image { var instance:Image = createImage(myTexture); // ...[中略]... addEventListener(ROTATE, rotateAtCenter); return instance; } // ...[中略]... private function createSub(myTexture:Texture):Image { var instance:Image = createImage(myTexture); // ...[中略]... addEventListener(ROTATE, rotateAround); return instance; }
[*3] さらに、定義済みActionScript 3.0では、 |
Starlingフレームワークでは、イベントリスナーは第2引数を受取ることもできます。EventDispatcher.dispatchEvent()
やEventDispatcher.dispatchEventWith()
メソッドでイベントを送ったときは、Event.data
プロパティの値が第2引数になります。したがって、前掲スクリプト004のふたつのリスナーメソッド(rotateAtCenter(()とrotateAround())は、つぎのように書くこともできます。
// private function rotateAtCenter(eventObject:Event):void { private function rotateAtCenter(eventObject:Event, angle:Number):void { // var angle:Number = eventObject.data as Number; center.rotation = angle; } // private function rotateAround(eventObject:Event):void { private function rotateAround(eventObject:Event, angle:Number):void { // var angle:Number = eventObject.data as Number; var radians:Number = angle - PI_HALF; sub.x = centerX + radius * Math.cos(radians); sub.y = centerY + radius * Math.sin(radians); }
定義済みのイベントについても、役に立つ第2引数を受取れるものがあります。たとえば、Event.ENTER_FRAME
では経過秒数[*4]、KeyboardEvent.KEY_DOWN
なら押したキーのコードが第2引数として渡されます。
addEventListener(Event.ENTER_FRAME, onEnterFrame); function onEnterFrame(event:Event, passedTime:Number):void { // 第2引数のpassedTimeで経過時間がわかる }
addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown); function onKeyDown(event:Event, keyCode:uint):void { // 第2引数のkeyCodeでキーコードがわかる }
[*4] 内部的には、第4回「StarlingフレームワークのTweenクラスにおける最適化とJugglerクラスの実装」04「Starlingオブジェクトに備わるJugglerオブジェクト」でご説明したとおり、StarlingオブジェクトがFlash Playerから すると、
|