最終回の今回は、Starlingフレームワークでインスタンスのクリックをどう扱うか解説したい。インスタンスのクリックは、もっとも基本的なマウスインタラクションだ。ところが、この扱いというのが、定義済みActionScript 3.0と勝手が違うだけでなく、思いのほかやっかいだったりする。
マウスボタンを放す操作はTouchPhase.ENDED
前回の第62回「Starlingフレームワークで動的に置いたビットマップをドラッグする 」では、インスタンスのドラッグについて学んだ。このときのスクリプト3「Starlingフレームワークでインスタンスをドラッグする」( 図1 )を、クリックのサンプルに書替えてみよう。
図1 Starlingフレームワークでインスタンスをドラッグするルートクラスの定義
まず、マウスボタンを放す操作のTouch.phase プロパティ値は、定数TouchPhase.ENDED で定められる(再掲第62回表2 ) 。すると、TouchEvent.getTouch() メソッドの第2引数にこの定数TouchPhase.ENDED を渡せば、マウスボタンを放す操作が捉えられる(前掲図1 参照) 。
第62回表2 TouchPhaseクラスの定数とタッチスクリーンおよびマウスの操作(再掲)
TouchPhaseクラスの定数 操作
タッチスクリーン マウス
BEGAN 画面に触れる マウスボタンを押す
ENDED 画面から指を離す マウスボタンを放す
HOVER - マウスポインタを重ねる
MOVED 画面に触れた指を動かす ボタンは押したままマウスを動かす
STATIONARY 画面に触れたまま動かさない ボタンを押したままマウスは動かさない
インスタンスをクリックしたら、一定の角度回すことにしよう。すると、第62回スクリプト3のDisplayObject.touch イベント(定数TouchEvent.TOUCH )のリスナーメソッド(mouseOperated())につぎのような手を加えればよさそうだ。
private var angle:Number = 15 * Math.PI / 180;
private function mouseOperated(eventObject:TouchEvent):void {
// var myTouch:Touch = eventObject.getTouch(instance, TouchPhase.MOVED);
var myTouch:Touch = eventObject.getTouch (instance, TouchPhase.ENDED );
if (myTouch) {
/*
var nMoveX:Number = myTouch.globalX - myTouch.previousGlobalX;
var nMoveY:Number = myTouch.globalY - myTouch.previousGlobalY;
instance.x += nMoveX;
instance.y += nMoveY;
*/
instance.rotation += angle;
}
}
試してみると、確かにインスタンスをクリックすれば回転する。ただし、TouchPhase.ENDED というのは、マウスボタンを放すのがインスタンス上でなくともよい。つまり、外でマウスボタンを放してもインスタンスは回ってしまうのだ(図2 ) 。しかし、インスタンスの外でマウスボタンを放したのはクリックではない。
図2 インスタンスの外でマウスボタンを放してもTouchPhase.ENDEDとみなされる
マウスボタンがインスタンス上で放されたのかを調べる
マウスボタンをどこで放しても、TouchPhase.ENDED が拾ってしまう。そのため、マウスボタンを放したときのポインタの座標が、インスタンス上かどうかは改めて確かめなければならない。このとき用いるのが、DisplayObject.hitTest() メソッド だ。
DisplayObjectオブジェクト.hitTest(Pointオブジェクト, マウス操作対象に限定)
第1引数には、参照するDisplayObjectオブジェクトから見た座標を、Pointオブジェクトで定める。第2引数にtrue を渡すと、マウス(またはタッチ)操作できる表示されたインスタンスだけを対象にして、座標の重なりが調べられる(デフォルト値はfalse ) 。戻り値は、座標と重なるDisplayObjectインスタンスだ。また、重なりはインスタンスの矩形領域(Rectangleオブジェクト)の内側かどうかで決まる。座標が重ならないときはnull が返される。
そこで、つぎのスクリプト1は、DisplayObject.touchイベントのリスナーメソッド(mouseOperated())で、マウスボタンを放したときのポインタの座標が、インスタンス上にあるかどうか調べるようにした。TouchPhase.ENDED のTouchオブジェクトがTouchEvent.getTouch() メソッドで得られたら、さらにDisplayObject.hitTest() メソッドでマウスポインタの座標がインスタンス上にあるのを確かめたうえで、インスタンスを回している。Touch.getLocation() メソッドは、引数に渡したインスタンスから見たマウスポインタの座標を返す。
スクリプト1 Starlingフレームワークでインスタンスのクリックを扱う
// 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;
import starling.events.TouchEvent;
import starling.events.Touch;
import starling.events.TouchPhase;
import flash.geom.Point;
public class MySprite extends Sprite{
private var instance:Image;
private var angle:Number = 15 * Math.PI / 180;
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);
addChild(instance);
instance.addEventListener(TouchEvent.TOUCH , mouseOperated);
instance.x = stage.stageWidth / 2;
instance.y = stage.stageHeight / 2;
instance.pivotX = instance.width / 2;
instance.pivotY = instance.height / 2;
}
private function mouseOperated(eventObject:TouchEvent):void {
var myTouch:Touch = eventObject.getTouch (instance, TouchPhase.ENDED );
if (myTouch) {
var mousePoint:Point = myTouch.getLocation (instance);
if (instance.hitTest (mousePoint, true)) {
instance.rotation += angle;
}
}
}
}
}
これで、マウスボタンをインスタンス上で押し、かつその上で放したとき、つまりクリックしたときだけインスタンスの角度が変わる。インスタンスが矩形であればこれでよい。しかし、前述のとおり、インスタンスのかたちにかかわらず、DisplayObject.hitTest() メソッドは矩形領域で座標との重なりを評価する。矩形でないインスタンスをどう扱えばよいか、次項で考えてみよう。
矩形でないビットマップのクリックを捉える
Starlingフレームワークでは、インスタンスと座標の重なりは、矩形領域で評価される。定義済みActionScript 3.0と異なり、矩形でないかたちについてピクセル単位で調べることはできない。だが、望みはある。定義済みActionScript 3.0が使えればよいのだ。ビットマップなら、まさにその手が使える。そこで、[ライブラリ]にアルファで抜いたビットマップを用意して、クラスを定めよう(図3 ) 。
図3 [ライブラリ]の矩形でないビットマップにクラスを定める
もちろん、前掲スクリプト1をそのまま使うと、矩形領域内であればアルファで抜かれたピクセル上もクリックを捉えてしまう(図4 ) 。
図4 矩形領域内はアルファで抜かれていてもクリックを捉える
しかし、BitmapDataクラスにはBitmapData.hitTest() メソッド があり、アルファを識別した座標の重なりが確かめられる。このメソッドにはいくつかの使い方がある[1] 。今回は、つぎの構文で用いる。
BitmapDataオブジェクト.hitTest(左上角Pointオブジェクト, アルファしきい値, 対象Pointオブジェクト)
第1引数はインスタンスの左上角座標を、Pointオブジェクトで渡す。BitmapDataインスタンスはタイムラインに直には置けないため、xy座標をプロパティとしてもたないからだ。第2引数は、インスタンスのピクセルの有無を切り分けるアルファの値(しきい値)で、256階調(0~255)の数値によって定める。第3引数が、重なりを調べる座標のPointオブジェクトだ。
前掲スクリプト1に手を加えて、つぎのようにBitmapData.hitTest() メソッドを呼出してみよう。メソッドの第3引数には、インスタンスから見たマウスポインタの座標をPointオブジェクト(mousePoint)で渡した。すると、第1引数の左上角は原点(0, 0)(zeroPoint)でよい。第2引数のアルファは、完全に抜けたピクセルの値として0x0(0)を渡している。なお、BitmapDataインスタンスはリスナーメソッドからも参照するため、ローカル変数でなく、プロパティ(myBitmapData)としてvar 宣言した。
private var myBitmapData:BitmapData;
private var zeroPoint:Point = new Point(0, 0);
private function initialize(eventObject:Event):void {
// var myBitmapData:BitmapData = new Pen();
myBitmapData = new Pen();
// ...[中略]...
}
private function mouseOperated(eventObject:TouchEvent):void {
var myTouch:Touch = eventObject.getTouch(instance, TouchPhase.ENDED);
if (myTouch) {
var mousePoint:Point = myTouch.getLocation(instance);
// if (instance.hitTest(mousePoint, true)) {
if (myBitmapData.hitTest (zeroPoint, 0x0, mousePoint)) {
instance.rotation += angle;
}
}
}
これで、アルファの抜けた矩形領域内でマウスボタンを放しても、クリックとは捉えられないようになる。ただし、アルファの抜けた矩形領域でマウスボタンを押して、ビットマップイメージ上で放すとクリックになってしまう(図5 ) 。これは矩形領域内でマウスボタンを押す操作が、DisplayObject.touch イベント(Touch.phase プロパティはTouchPhase.BEGAN )を起こすからだ。
図5 矩形領域でマウスボタンを押してビットマップイメージ上で放すとクリックになる
つまり、マウスボタンを押したとき(Touch.phase プロパティがTouchPhase.BEGAN )も、アルファを識別してポインタ座標がビットマップ上かどうかを確かめなければならない。つぎのスクリプト2は、TouchEvent.getTouch() メソッドの第2引数なしにTouchオブジェクトを取出し[2] 、switch ステートメントでTouch.phase プロパティがTouchPhase.BEGAN とTouchPhase.ENDED の処理をそれぞれ定めた。
スクリプト2 ビットマップのアルファも識別してクリックを捉える
// 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;
import starling.events.TouchEvent;
import starling.events.Touch;
import starling.events.TouchPhase;
import flash.geom.Point;
public class MySprite extends Sprite{
private var instance:Image;
private var angle:Number = 15 * Math.PI / 180;
private var myBitmapData:BitmapData;
private var zeroPoint:Point = new Point(0, 0);
private var pressed:Boolean = false;
public function MySprite() {
addEventListener(Event.ADDED_TO_STAGE, initialize);
}
private function initialize(eventObject:Event):void {
myBitmapData = new Pen();
var myTexture:Texture = Texture.fromBitmapData(myBitmapData);
instance = new Image(myTexture);
addChild(instance);
instance.addEventListener(TouchEvent.TOUCH, mouseOperated);
instance.x = stage.stageWidth / 2;
instance.y = stage.stageHeight / 2;
instance.pivotX = instance.width / 2;
instance.pivotY = instance.height / 2;
}
private function mouseOperated(eventObject:TouchEvent):void {
var myTouch:Touch = eventObject.getTouch (instance);
if (myTouch) {
switch (myTouch.phase) {
case TouchPhase.BEGAN :
if (instanceIsTouched(myTouch)) {
pressed = true;
}
break;
case TouchPhase.ENDED :
if (pressed && instanceIsTouched(myTouch)) {
instance.rotation += angle;
}
pressed = false;
break;
}
}
}
private function instanceIsTouched(myTouch:Touch):Boolean {
var mousePoint:Point = myTouch.getLocation(instance);
var touched:Boolean = myBitmapData.hitTest (zeroPoint, 0x0, mousePoint);
return touched;
}
}
}
座標がアルファを識別したビットマップイメージ上かどうかは、別に定めた関数(instanceIsTouched())で調べることにした。DisplayObject.touch イベントのリスナーメソッド(mouseOperated())は、インスタンスの矩形領域でマウスボタンを押したとき(Touch.phase プロパティがTouchPhase.BEGAN ) 、ポインタの座標がビットマップ上ならフラグのプロパティ(pressed)をtrue にする。そして、マウスボタンを放したとき(Touch.phase プロパティはTouchPhase.ENDED ) 、このプロパティ値がtrue かつマウスポインタの座標がビットマップ上であることを確かめたうえで、クリックとして扱っている。
これで、ビットマップイメージのアルファも識別したマウスクリックが捉えられる。さて、少しばかり手間がかかってしまった。Starlingにかぎらず、フレームワークを使うときには、こうした得意・不得意や特徴を理解しておくことが大切だ。
最後に
Flash Player 9から始まった 5年を超える本連載は今回で閉じことになる。5年間でFlashの技術も取り巻く状況も、大きく変わった。また、今なお変わり続けている。Flashを始めとするWebの技術については、筆者のサイトFumioNonaka.com で引続き発信していく。折に触れて、ご覧いただければ幸いだ。最後に改めて、長い間のご愛読に感謝したい。