Starlingフレームワークについては、すでに2つの記事「ActionScript 3.0のフレームアクションをStarlingフレームワークに移行する」と「Starlingフレームワークのスプライトシートを使ったアニメーション」でその基本からムービークリップシンボルを使った簡単なアニメーションまでご紹介しました。本稿では、インスタンスのドラッグ&ドロップについてご説明します。Starlingフレームワークにおけるマウスイベントの扱いは、ActionScript 3.0定義済みクラスを使う場合とは少し勝手が異なります。

01 [ライブラリ]のビットマップをImageオブジェクトに入れてステージに置く

ドラッグするのは、[ライブラリ]のビットマップをテクスチャとして納めたImageオブジェクトにします。[ライブラリ]のビットマップには、あらかじめ[クラス]を定めておきます(図001)。

Starlingフレームワークのルートクラス(MySprite)を、まずは以下のスクリプト001のように定義しました。[ライブラリ]のビットマップから作ったテクスチャをImageオブジェクトに納めて、ステージの中央に置きます。ASファイルと同じ階層にあるFLAファイルには、次のフレームアクションが書かれているものとします。ここまでのスクリプトは、前出「ActionScript 3.0のフレームアクションをStarlingフレームワークに移行する」で作ったサンプルと基本的に変わりません。

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.Image; import starling.textures.Texture; import starling.events.Event; public class MySprite extends Sprite { 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 instance:Image = new Image(myTexture); addChild(instance); instance.x = (stage.stageWidth - instance.width) / 2; instance.y = (stage.stageHeight - instance.height) / 2; } } }

02 Starlingフレームワークでインスタンスをドラッグする

ActionScript 3.0定義済みクラスでは、マウスイベントはInteractiveObjectクラス(定数はMouseEventクラス)に定められています。ドラッグの場合には、次のように3つのマウスイベントを組合わせて扱うのが普通です。

  1. InteractiveObject.mouseDown(MouseEvent.MOUSE_DOWN)
    • InteractiveObject.mouseMoveイベントにリスナーを登録
  2. InteractiveObject.mouseMove(MouseEvent.MOUSE_MOVE)
    • リスナー関数でインスタンスの位置をマウスポインタの座標に追随させる
  3. InteractiveObject.mouseUp(MouseEvent.MOUSE_UP)
    • InteractiveObject.mouseMoveイベントからリスナーを削除

これに対して、Starlingフレームワークでは、全てのマウス操作がDisplayObject.touchイベント(定数TouchEvent.TOUCH)として受取られます。そして、マウス操作の違いは、DisplayObject.touchイベントのリスナーが引数に受取るTouchEventオブジェクトから調べます。TouchEvent.getTouch()メソッドによりTouchオブジェクトを取出し、そのTouch.phaseプロパティで確かめるのです。

Touch.phaseプロパティがとる文字列の値は、TouchPhaseクラスに定数として定められています。Touchクラスという名前からもおわかりのように、マウスだけでなくタッチスクリーンのインタラクションも同じように扱えます。TouchPhaseクラスの定数とタッチスクリーンおよびマウスの操作は、次表001のとおりです。

表001 TouchPhaseクラスの定数とタッチスクリーンおよびマウスの操作
TouchPhaseクラスの定数 操作
タッチスクリーン マウス
BEGAN 画面に触れる マウスボタンを押す
ENDED 画面から指を離す マウスボタンを放す
HOVER マウスポインタを重ねる
MOVED 画面に触れた指を動かす マウスポインタを重ねる
STATIONARY 画面に触れたまま動かさない ボタンを押したままマウスは動かさない

マウスのドラッグに当たるTouch.phaseプロパティの値は、TouchPhase.MOVEDです。ActionScript 3.0定義済みのInteractiveObject.mouseMoveイベントと違って、インスタンスの上でマウスボタンを押したまま動かしていることを示します。ですから、この値だけでドラッグが捉えられ、別にマウスボタンを押したか放したかは調べずに済むのです。

さて、Touch.phaseプロパティを調べるには、Touchオブジェクトを得なければなりません。DisplayObject.touchイベントのリスナーが受取ったTouchEventオブジェクトからTouchオブジェクトを取出すのはTouchEvent.getTouch()メソッドです。引数は2つあります。

TouchEventオブジェクト.getTouch(最上位のDisplayObjectオフジェクト, TouchPhaseクラス定数)

第1引数は、DisplayObject.touchイベントの受取りを調べる表示リストの頂点となるDisplayObjectインスタンスです。TouchEvent.getTouch()メソッドは、この引数に定めたインスタンスとその表示リスト下層の子インスタンスからTouchオブジェクトを探します。さらに第2引数のTouchPhaseクラス定数で、DisplayObject.touchイベントの操作を絞り込めます。デフォルト値はnullで、全てのDisplayObject.touchイベントになります。2つの引数で定まるTouchオブジェクトが見つかればそのオブジェクト、なければnullが返ります。

もっとも、DisplayObject.touchイベントを扱うリスナーは、EventDispatcher.addEventListener()メソッドで決まります。それに、TouchEvent.getTouch()メソッドの第2引数を組合わせれば、第1引数を絞り込まなければならないことは多くありません。その場合、第1引数は表示リスト最上位のStageオブジェクトを渡します。

すると次に、インスタンスの位置をマウスポインタの座標に追随させるにはどうするかです。Starlingフレームワークには、マウス操作されたインスタンスにはマウスポインタの座標を調べるプロパティ(ActionScript 3.0定義済みのDisplayObject.mouseXとDisplayObject.mouseY)がありません。マウスポインタ座標はTouchオブジェクトから、Touch.getLocation()メソッドで取出します。

Touchオブジェクト.getLocation(座標空間を定めるDisplayObjectオフジェクト)

引数には座標空間の基準となる(DisplayObject)インスタンスを渡し、座標は(ActionScript 3.0定義済みの)Pointオブジェクトで受取ります。したがって、Touch.getLocation()メソッドにDisplayObject.parentプロパティを渡せば、親インスタンスから見たマウスポインタの座標が得られます。そのxy座標値をインスタンスに設定すると、インスタンスがマウスポインタの位置に動きます。

では、前掲スクリプト001に、ドラッグのためのステートメントを加えます。クラス定義(MySprite)全体は、後にスクリプト002として掲げます。その中で、新たに書き加えたスクリプトは、次のとおりです。

import flash.geom.Point; import starling.display.DisplayObject; import starling.events.TouchEvent; import starling.events.Touch; import starling.events.TouchPhase; public class MySprite extends Sprite { private function initialize(eventObject:Event):void { instance.addEventListener(TouchEvent.TOUCH, mouseOperated); } private function mouseOperated(eventObject:TouchEvent):void { var myTouch:Touch = eventObject.getTouch(stage, TouchPhase.MOVED); if (myTouch) { var instance:DisplayObject = eventObject.currentTarget as DisplayObject; var currentPoint:Point = myTouch.getLocation(parent); instance.x = currentPoint.x; instance.y = currentPoint.y; } } }

まず、初期化のメソッド(initialize())で、ドラッグするインスタンスのDisplayObject.touchイベント(定数TouchEvent.TOUCH)にマウス操作のリスナーメソッドを加えます。なお、必要なクラスはimport宣言しています。

次に、リスナーメソッド(mouseOperated())は、イベントオブジェクトからTouchEvent.getTouch()メソッドでTouchオブジェクト(myTouch)を取出します。メソッドの第2引数にはTouchPhase.MOVEDを渡しましたので、インスタンスをドラッグしたときだけTouchオブジェクトが得られ、それ以外の操作ではnullになります。ifステートメントで、Touchオブジェクトがあることを確かめます。

そして、Touchオブジェクトがあったらドラッグを行いますので、Event.currentTargetプロパティから操作するインスタンス(instance)、Touch.getLocation()メソッドでマウス座標のPointオブジェクト(currentPoint)を得ます。後は、インスタンスの位置をマウスポインタの座標に合わせれば、ドラッグになります。

スクリプト002 ステージに置いたインスタンスをドラッグする(002_drag)

// ActionScript 3.0クラス定義ファイル: MySprite.as package { import flash.display.BitmapData; import flash.geom.Point; import starling.display.Sprite; import starling.display.DisplayObject; import starling.display.Image; import starling.textures.Texture; import starling.events.Event; import starling.events.TouchEvent; import starling.events.Touch; import starling.events.TouchPhase; public class MySprite extends Sprite { 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 instance:Image = new Image(myTexture); addChild(instance); instance.x = (stage.stageWidth - instance.width) / 2; instance.y = (stage.stageHeight - instance.height) / 2; instance.addEventListener(TouchEvent.TOUCH, mouseOperated); } private function mouseOperated(eventObject:TouchEvent):void { var myTouch:Touch = eventObject.getTouch(stage, TouchPhase.MOVED); if (myTouch) { var instance:DisplayObject = eventObject.currentTarget as DisplayObject; var currentPoint:Point = myTouch.getLocation(parent); instance.x = currentPoint.x; instance.y = currentPoint.y; } } } }

これで、インスタンスはドラッグできるようになります。ところが、インスタンスの真ん中にマウスポインタを置いてドラッグしても、マウスを動かした途端にインスタンスの左上角がポインタの座標に動いてしまいます。これは、インスタンスの基準点がデフォルトでは左上角にあり、その点がマウスポインタに追随するからです(図002)。

03 マウスでドラッグし始めたポインタの位置を保つ

ドラッグするとき、初めにマウスボタンを押したインスタンス上のポインタの位置を保つようにしましょう。その場合の方法として1つは、マウスボタンが押されたインスタンス上の座標を控えておいて、その(オフセット)分ずらすという処理がよく使われます。けれどもう1つ、マウスポインタがどう移動したか調べて、インスタンスを同じように動かすというやり方もあります。

TouchクラスにはTouch.getPreviousLocation()メソッドがあり、1つ前のイベント時のマウスポインタ座標が調べられます。シンタックス(文法)はTouch.getLocation()メソッドと同じです。すると、後者のやり方を採るのが簡単でしょう。インスタンスのドラッグ&ドロップを加えたStarlingルートクラス()の定義は、次のスクリプト003のようになります。

スクリプト003 初めにマウスボタンが押されたインスタンスの位置を保ったドラッグ(003_drag_refined)

// ActionScript 3.0クラス定義ファイル: MySprite.as package { import flash.display.BitmapData; import flash.geom.Point; import starling.display.Sprite; import starling.display.DisplayObject; import starling.display.Image; import starling.textures.Texture; import starling.events.Event; import starling.events.TouchEvent; import starling.events.Touch; import starling.events.TouchPhase; public class MySprite extends Sprite { 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 instance:Image = new Image(myTexture); addChild(instance); instance.x = (stage.stageWidth - instance.width) / 2; instance.y = (stage.stageHeight - instance.height) / 2; instance.addEventListener(TouchEvent.TOUCH, mouseOperated); } private function mouseOperated(eventObject:TouchEvent):void { var myTouch:Touch = eventObject.getTouch(stage, TouchPhase.MOVED); if (myTouch) { var instance:DisplayObject = eventObject.currentTarget as DisplayObject; var currentPoint:Point = myTouch.getLocation(parent); var previousPoint:Point = myTouch.getPreviousLocation(parent); var movedPoint:Point = currentPoint.subtract(previousPoint); instance.x += movedPoint.x; instance.y += movedPoint.y; } } } }

マウス操作のリスナーメソッド(mouseOperated())で、現在のマウスポインタの座標に加えて、Touch.getPreviousLocation()メソッドで1つ前のポインタ座標を調べました。そして、Point.subtract()メソッドにより2つの座標の差を求めると、マウスポインタがどれだけ動いたかわかります。そのxy座標値を現在のインスタンスの位置に加えれば、マウスポインタと同じように動くのです(図003)。

04 異なるマウス操作(Touch.phaseプロパティ)を扱う

最後に、マウスのドラッグだけでなく、マウスボタンを押したり、放したりという異なる操作、つまり複数のTouch.phaseプロパティを扱ってみます。前の記事「Starlingフレームワークのスプライトシートを使ったアニメーション」で作ったスクリプト003「MovieClipインスタンスをJugglerオブジェクトに加えたStarlingルートクラスの定義」にマウス操作を加えましょう。

このスクリプトでは、フレームアニメーションするMovieClipインスタンスを、水平にスクロールさせました(図004)。このインスタンスをアニメーション途中でドラッグできるようにしてみます。

まず、DisplayObject.touchイベント(定数TouchEvent.TOUCH)にリスナーメソッドを加えるところまでは、前掲スクリプト003と変わりません。必要なクラスはimport宣言しておきます。

import flash.geom.Point; import starling.events.TouchEvent; import starling.events.Touch; import starling.events.TouchPhase; public class MySprite extends Sprite { private function initialize(eventObject:Event):void { instance.addEventListener(TouchEvent.TOUCH, mouseOperated); } }

次に、リスナーメソッド(mouseOperated())の中のインスタンスをドラッグする処理そのものは、基本的に前掲スクリプト003とほぼ同じです。ただし、これだけでは、ドラッグしているときも水平スクロールのアニメーションが続いてしまいます。つまり、ドラッグしている間はインスタンスを水平スクロールするアニメーションは止めなければなりません。

private function mouseOperated(eventObject:TouchEvent):void { // var myTouch:Touch = eventObject.getTouch(stage, TouchPhase.MOVED); var myTouch:Touch = eventObject.getTouch(stage); var phase_str:String = myTouch.phase; // if (myTouch) { var instance:DisplayObject = eventObject.currentTarget as DisplayObject; // trace(phase_str); switch (phase_str) { case TouchPhase.BEGAN: instance.removeEventListener(Event.ENTER_FRAME, scroll); break; case TouchPhase.MOVED: var currentPoint:Point = myTouch.getLocation(parent); var previousPoint:Point = myTouch.getPreviousLocation(parent); var movedPoint:Point = currentPoint.subtract(previousPoint); instance.x += movedPoint.x; instance.y += movedPoint.y; break; case TouchPhase.ENDED: instance.addEventListener(Event.ENTER_FRAME, scroll); break; } }

DisplayObject.touchイベントのリスナーメソッド(mouseOperated())では種類の異なるマウス操作を扱いますので、TouchEvent.getTouch()メソッドに第2引数を与えていません。そのため、取出したTouchオブジェクト(myTouch)からTouch.phaseプロパティの値を得ています。

その値はswitchステートメントで振分け、TouchPhase.BEGANのときDisplayObject.enterFrameイベントからリスナーメソッド(scroll())を外し、TouchPhase.ENDEDで戻すという処理を加えました。前述のとおり、Touch.phaseプロパティの値がTouchPhase.MOVEDの場合の処理は、前掲スクリプト003と基本的に変わりません。クラス定義(MySprite)全体は、次のスクリプト004のとおりです。

スクリプト004 インスタンスのドラッグ&ドロップを加えたStarlingルートクラスの定義(004_TouchPhase)

// ActionScript 3.0クラス定義ファイル: MySprite.as package { import flash.display.BitmapData; import flash.geom.Point; 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; import starling.events.TouchEvent; import starling.events.Touch; import starling.events.TouchPhase; 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.addEventListener(TouchEvent.TOUCH, mouseOperated); 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; } private function mouseOperated(eventObject:TouchEvent):void { var myTouch:Touch = eventObject.getTouch(stage); var phase_str:String = myTouch.phase; var instance:DisplayObject = eventObject.currentTarget as DisplayObject; switch (phase_str) { case TouchPhase.BEGAN: instance.removeEventListener(Event.ENTER_FRAME, scroll); break; case TouchPhase.MOVED: var currentPoint:Point = myTouch.getLocation(parent); var previousPoint:Point = myTouch.getPreviousLocation(parent); var movedPoint:Point = currentPoint.subtract(previousPoint); instance.x += movedPoint.x; instance.y += movedPoint.y; break; case TouchPhase.ENDED: instance.addEventListener(Event.ENTER_FRAME, scroll); break; } } } }

これで、アニメーションしながら水平スクロールするインスタンスが、ドラッグできるようになります(図005)。ドラッグするとMovieClipインスタンスの中のフレームアニメーションは動き続けるものの、水平スクロールさせるメソッド(scroll())はDisplayObject.enterFrameイベントのリスナーから外れます。そして、マウスボタンを放してドロップすると、再びイベントリスナーが加えられて、水平スクロールし始めます。

このサンプルのドラッグを何度か試していると、気になるところがあるかもしれません。それは、DisplayObject.touchイベントがインスタンスの矩形領域で捉えられることです。そのため、インスタンスのイメージがない箇所でも、矩形領域内ならドラッグできてしまいます(図006)。そして、Starlingフレームワークの中には、残念ながら本稿執筆時点では対処の手段が用意されていません。したがって、この問題については別途工夫しなければなりません[*1]。

[*1] もっとも、今回のサンプルのようにMovieClipインスタンスそのものがアニメーションする場合、もし切り替わるイメージに合わせてマウスイベントを捉える領域(ヒット領域)が変わると不都合なこともあります。そのため、アニメーションにかかわらずヒット領域は固定することも多いので、矩形領域でも必ずしも問題になるとはかぎりません。

なお、ActionScript 3.0定義済みのBitmapDataクラスを用いた工夫として、「Starlingフレームワークでビットマップ上のクリックを検知する」をご参照ください。