「Starlingフレームワークでインスタンスをクリックする」では、インスタンスの上でマウスクリックしたことをDisplayObject.touchイベント(定数TouchEvent.TOUCH)とDisplayObject.hitTest()メソッドの組合わせにより確かめました。ただし、DisplayObject.hitTest()メソッドは、ポインタとの重なりをインスタンスの矩形領域で調べます。本稿では、ビットマップについてマウスクリックをその形状で検知してみます。
01 ビットマップのインスタンスをクリックする
まずは、[ライブラリ]のビットマップからつくったインスタンスをステージに置いて、インスタンスにDisplayObject.touchイベントのリスナーメソッドを加えます。ビットマップには[ライブラリ]で、予めクラスを設定しておきます(図001)。
図001■[ライブラリ]のビットマップにクラスを設定する
以下のスクリプト001は、ステージに置かれたインスタンスをクリックするたびに、下に5ピクセルずつ動かします。クリックするのがビットマップイメージをもったImageインスタンスであることを除いて[*1]、マウスクリックの扱いは前出「Starlingフレームワークでインスタンスをクリックする」のスクリプト002と同じです。
スクリプト001■ビットマップイメージが含まれたインスタンスをクリックするたびに下に動かす
// ActionScript 3.0クラス定義ファイル: MySprite.as
- package {
- import flash.display.BitmapData;
- import flash.geom.Point;
- import starling.display.DisplayObject;
- 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;
- public class MySprite extends Sprite {
- private var myBitmapData:BitmapData;
- public function MySprite() {
- addEventListener(Event.ADDED_TO_STAGE, initialize);
- }
- private function initialize(eventObject:Event):void {
- myBitmapData = new Pen();
- var myTexture:Texture = Texture.fromBitmapData(myBitmapData);
- var ball:Image = new Image(myTexture);
- addChild(ball);
- ball.x = (stage.stageWidth - myTexture.width) / 2;
- ball.y = (stage.stageHeight - myTexture.height) / 2;
- ball.addEventListener(TouchEvent.TOUCH, dispatchMouseEvent);
- }
- private function onClick(eventObject:TouchEvent):void {
- var instance:DisplayObject = eventObject.currentTarget as DisplayObject;
- instance.y += 5;
- }
- private function dispatchMouseEvent(eventObject:TouchEvent):void {
- var myTouch:Touch = eventObject.getTouch(stage, TouchPhase.ENDED);
- if (myTouch) {
- var instance:DisplayObject = eventObject.currentTarget as DisplayObject;
- var myPoint:Point = myTouch.getLocation(instance);
- if (instance.hitTest(myPoint, true)) {
- onClick(eventObject);
- }
- }
- }
- }
- }
|
DisplayObject.hitTest()メソッドは、引数に渡されたPointオブジェクトの座標とインスタンスとの重なりを、インスタンスの矩形領域にもとづいて調べます(スクリプト001第34〜35行目)。ですから、前出「Starlingフレームワークでインスタンスをクリックする」で試したQuadインスタンスのように、イメージが矩形でしたらこのスクリプト001で問題ありません。けれど、ビットマップが矩形でないときは、イメージの外でも矩形領域の中ならマウスクリックを受止めてしまいます(図001)。
- var instance:DisplayObject = eventObject.currentTarget as DisplayObject;
- var myPoint:Point = myTouch.getLocation(instance);
- if (instance.hitTest(myPoint, true)) {
- onClick(eventObject);
- }
|
図002■ポインタがイメージ上になくても矩形領域内ならクリックできてしまう
02 BitmapData.hitTest()メソッドで座標との重なりを調べる
ActionScript 3.0定義済みのBitmapData.hitTest()メソッドを用いると、ビットマップイメージのアルファを識別した重なりが確かめられます。つまり、アルファを抜いた部分は、重なりの範囲から除くことができるのです。BitmapData.hitTest()メソッドは、つぎのような3つの構文でBitmapDataインスタンスとの重なりが調べられます。
- BitmapDataオブジェクト.hitTest(左上角Pointオブジェクト, アルファしきい値, 対象PointまたはRectangleオブジェクト)
- BitmapDataオブジェクト.hitTest(左上角Pointオブジェクト, アルファしきい値, 対象Bitmapオブジェクト)
- BitmapDataオブジェクト.hitTest(左上角Pointオブジェクト, アルファしきい値, 対象BitmapDataオブジェクト, 対象左上角Pointオブジェクト, 対象アルファしきい値)
いずれも、第1引数はインスタンスが配置された右上角を定めるPointオブジェクトで、第2引数がイメージの領域に含めるアルファのしきい値です。そして、第3引数には座標情報をPointまたはRectangleオブジェクトで示すか、対象とするBitmapまたは BitmapDataインスタンスを渡します(表001)。今回は、マウスポインタの座標をPointオブジェクトにして調べます。
表001■BitmapData.hitTest()メソッド
BitmapDataクラス |
パッケージ
|
flash.display
|
継承
|
BitmapData → Object
|
hitTest()メソッド |
文法
|
public function hitTest(firstPoint:Point, firstAlphaThreshold:uint, secondObject:Object, secondBitmapDataPoint:Point = null, secondAlphaThreshold:uint = 1):Boolean |
概要
|
参照するBitmapDataインスタンスと他のインスタンスとのピクセル単位の接触を、ブール(論理)値の判定結果として返す。接触を判定する対象となるのは、PointかRectangle、あるいは他のBitmapまたはBitmapDataインスタンス。
|
引数
|
firstPoint:Point − 配置された座標空間におけるBitmapDataインスタンスの左上角座標を示すPointインスタンス。
firstAlphaThreshold:uint − 参照するBitmapDataインスタンス上で接触の判定対象に含めるアルファの最低値(しきい値)を示す0から255までの整数。
secondObject:Object − 判定対象とするPoint、Rectangle、Bitmapまたは BitmapDataインスタンス。
secondBitmapDataPoint:Point − 前引数secondObjectの判定対象がBitmapDataインスタンスのとき、その左上隅座標を示すPointインスタンス。デフォルトはnull(指定なし)。
secondAlphaThreshold:uint − 前引数secondObjectの判定対象がBitmapDataインスタンスのとき、そのオブジェクト上で接触の判定対象に含めるアルファの最低値(しきい値)を示す0から255までの整数。デフォルト値は1。
|
戻り値
|
重なっているかどうかを示すブール値。
|
このBitmapData.hitTest()メソッドを使うため、実はすでに前掲スクリプト001では、BitmapDataインスタンスをローカル変数でなくプロパティに納めておきました(第13および第18行目)[*2]。
- private var myBitmapData:BitmapData;
- private function initialize(eventObject:Event):void {
- myBitmapData = new Pen();
- }
|
マウスポインタがビットマップイメージ上にあるかどうかを調べるメソッドは別に定めます(instanceIsTouched())。Imageインスタンスとマウスポインタの座標をそれぞれ調べるため、引数にはImage(DisplayObject)とTouchのオブジェクトを渡します(第35および第40〜44行目)。メソッドの戻り値は、座標が重なっているかどうかを示すブール(論理)値です。クラス定義(MySprite)全体はスクリプト002として後に掲げます。つぎの抜書きの行番号は、スクリプト002にもとづいています。
- private var zeroPoint:Point = new Point(0, 0);
- private function dispatchMouseEvent(eventObject:TouchEvent):void {
- var myTouch:Touch = eventObject.getTouch(stage, TouchPhase.ENDED);
- if (myTouch) {
- var instance:DisplayObject = eventObject.currentTarget as DisplayObject;
// var myPoint:Point = myTouch.getLocation(instance);
// if (instance.hitTest(myPoint, true)) {
- if (instanceIsTouched(instance, myTouch)) {
- onClick(eventObject);
- }
- }
- }
- private function instanceIsTouched(instance:DisplayObject, myTouch:Touch):Boolean {
- var mousePoint:Point = myTouch.getLocation(instance);
- var touched:Boolean = myBitmapData.hitTest(zeroPoint, 0x0, mousePoint);
- return touched;
- }
|
引数のImage(DisplayObject)とTouchのオブジェクトから、Touch.getLocation()メソッドでインスタンス上のマウス座標をPointオブジェクトで得ました(第40〜41行目)。BitmapData.hitTest()メソッドの第3引数にはこのインスタンスから見たマウス座標を渡しますので、第1引数のインスタンスの左上角は原点(0, 0)でよいことになります。第2引数のしきい値は、アルファが完全に抜けた0x0(0)としました(第42行目)。なお、原点のPointオブジェクトは、予めプロパティ(zeroPoint)に納めました(第14行目)。そして、BitmapData.hitTest()メソッドの戻り値を返します(第43行目)。
スクリプト002■マウスボタンを放した座標がイメージ上かどうかをBitmapData.hitTest()メソッドで調べる
// ActionScript 3.0クラス定義ファイル: MySprite.as
- package {
- import flash.display.BitmapData;
- import flash.geom.Point;
- import starling.display.DisplayObject;
- 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;
- public class MySprite extends Sprite {
- private var myBitmapData:BitmapData;
- private var zeroPoint:Point = new Point(0, 0);
- public function MySprite() {
- addEventListener(Event.ADDED_TO_STAGE, initialize);
- }
- private function initialize(eventObject:Event):void {
- myBitmapData = new Pen();
- var myTexture:Texture = Texture.fromBitmapData(myBitmapData);
- var ball:Image = new Image(myTexture);
- addChild(ball);
- ball.x = (stage.stageWidth - myTexture.width) / 2;
- ball.y = (stage.stageHeight - myTexture.height) / 2;
- ball.addEventListener(TouchEvent.TOUCH, dispatchMouseEvent);
- }
- private function onClick(eventObject:TouchEvent):void {
- var instance:DisplayObject = eventObject.currentTarget as DisplayObject;
- instance.y += 5;
- }
- private function dispatchMouseEvent(eventObject:TouchEvent):void {
- var myTouch:Touch = eventObject.getTouch(stage, TouchPhase.ENDED);
- if (myTouch) {
- var instance:DisplayObject = eventObject.currentTarget as DisplayObject;
- if (instanceIsTouched(instance, myTouch)) {
- onClick(eventObject);
- }
- }
- }
- private function instanceIsTouched(instance:DisplayObject, myTouch:Touch):Boolean {
- var mousePoint:Point = myTouch.getLocation(instance);
- var touched:Boolean = myBitmapData.hitTest(zeroPoint, 0x0, mousePoint);
- return touched;
- }
- }
- }
|
これでマウスボタンを放したことが、インスタンスの矩形領域ではなく、実際のビットマップイメージの上かどうかで検知されます。矩形領域内でもイメージがない位置では、インスタンスはクリックとして受取りません(前掲図002参照)。
[*2] Starlingフレームワークにおいて、ビットマップイメージのデータはTextureオブジェクトがもちます。ですから、今回のようにBitmapDataインスタンスを使う必要がなければ、ローカル変数に納めてメモリは開放するのが望ましいでしょう。
なお、BitmapData.dispose()メソッドは、ビットマップイメージのデータをオブジェクトから直ちに消し去ります。ただし、データを失ったBitmapDataオブジェクトそのものは残りますので、ガベージコレクションにかけます。
|
03 マウスボタンを押す操作も確かめる
前掲スクリプト002で、アルファを見切ったイメージ上のマウスクリックはとれているように感じられます。けれど、ひとつ穴があります。アルファの抜けた矩形領域内でマウスボタンを押し、イメージ上で放したときクリックと扱われてしまうのです(図003)。
図003■矩形領域内でマウスボタンを押してイメージ上で放すとクリックになる
インスタンスの矩形領域内でマウスボタンを押せば、DisplayObject.touchイベントが起こってしまいます(Touch.phaseプロパティの値はTouchPhase.BEGAN)。すると、マウスボタンを放したときのポインタ座標を調べただけでは、この問題はさけられません。つまり、マウスボタンを押したときも、ポインタがイメージ上にあるかどうか確かめなければならないのです。
そこで、つぎのような修正を加えます(行番号は後掲スクリプト003にもとづきます)。DisplayObject.touchイベントのリスナーメソッド(dispatchMouseEvent())で、扱いたいTouch.phaseプロパティの値がTouchPhase.BEGANとTouchPhase.ENDEDのふたつになりましたので、TouchEvent.getTouch()メソッドの第2引数は与えずにTouchオブジェクトを取出します(第33行目)。
Touch.phaseプロパティの値がTouchPhase.BEGANのとき(第37行目)、マウスポインタがビットマップイメージ上にあるかどうかを調べます(第38行目)。イメージ上であれば、フラグとしてvar宣言したプロパティ(pressed)をtrueにします(第39行目)。なお、プロパティの初期値はfalseです(第15行目)。
Touch.phaseプロパティの値がTouchPhase.ENDEDになったら(第42行目)、マウスポインタがイメージ上にあるだけでなく、フラグのプロパティ(pressed)はtrueであることを確かめてクリックとして扱います(第43〜44行目)。その後、プロパティの値はfalseに戻します(第46行目)。
- private var pressed:Boolean = false;
- private function dispatchMouseEvent(eventObject:TouchEvent):void {
// var myTouch:Touch = eventObject.getTouch(stage, TouchPhase.ENDED);
- var myTouch:Touch = eventObject.getTouch(stage);
- if (myTouch) {
- var instance:DisplayObject = eventObject.currentTarget as DisplayObject;
- switch (myTouch.phase) {
- case TouchPhase.BEGAN:
- if (instanceIsTouched(instance, myTouch)) {
- pressed = true;
- }
- break;
- case TouchPhase.ENDED:
- if (pressed && instanceIsTouched(instance, myTouch)) {
- onClick(eventObject);
- }
- pressed = false;
- break;
- }
- }
- }
|
これで、ビットマップイメージのアルファを識別したクリックが正しく捉えられます。Starlingフレームワークのルートクラス(MySprite)全体は、つぎのスクリプト003のとおりです。
スクリプト003■ビットマップイメージのアルファまで識別してクリックを捉える
// ActionScript 3.0クラス定義ファイル: MySprite.as
- package {
- import flash.display.BitmapData;
- import flash.geom.Point;
- import starling.display.DisplayObject;
- 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;
- public class MySprite extends Sprite {
- 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);
- var ball:Image = new Image(myTexture);
- addChild(ball);
- ball.x = (stage.stageWidth - myTexture.width) / 2;
- ball.y = (stage.stageHeight - myTexture.height) / 2;
- ball.addEventListener(TouchEvent.TOUCH, dispatchMouseEvent);
- }
- private function onClick(eventObject:TouchEvent):void {
- var instance:DisplayObject = eventObject.currentTarget as DisplayObject;
- instance.y += 5;
- }
- private function dispatchMouseEvent(eventObject:TouchEvent):void {
- var myTouch:Touch = eventObject.getTouch(stage);
- if (myTouch) {
- var instance:DisplayObject = eventObject.currentTarget as DisplayObject;
- switch (myTouch.phase) {
- case TouchPhase.BEGAN:
- if (instanceIsTouched(instance, myTouch)) {
- pressed = true;
- }
- break;
- case TouchPhase.ENDED:
- if (pressed && instanceIsTouched(instance, myTouch)) {
- onClick(eventObject);
- }
- pressed = false;
- break;
- }
- }
- }
- private function instanceIsTouched(instance:DisplayObject, myTouch:Touch):Boolean {
- var mousePoint:Point = myTouch.getLocation(instance);
- var touched:Boolean = myBitmapData.hitTest(zeroPoint, 0x0, mousePoint);
- return touched;
- }
- }
- }
|
04 複数のマウスインタラクションを扱う
まとめとして、複数のマウスインタラクションに動きを与えましょう。具体的には、インスタンス上でマウスボタンを押したときと放したとき(クリック)、インスタンス外で放したときの都合3つです。3つの操作に対するインスタンスのふるまいは、それぞれメソッドとして定めます。
- インスタンス上でマウスボタンを押したとき(onPress())
- インスタンス上でマウスボタンを放したとき(onClick())
- インスタンス外でマウスボタンを放したとき(onReleaseOutside())
3つの操作を扱うメソッドには、いずれも引数としてイベントオブジェクトを渡します。すると、イベントオブジェクトから操作対象のインスタンス(Event.currentTargetプロパティ)を取出すことが求められます。そこで、前掲スクリプト003を先に整理しておきましょう。
つぎのように新たに定めるメソッド(getInstance())で、引数のイベントオブジェクトから操作対象のインスタンスを返すことにします(第56〜59行目)。そこで、マウスポインタがビットマップイメージ上にあるかどうかを調べるメソッド(instanceIsTouched())も、引数にイベントオブジェクトを受取るように変えました(第51行目)。DisplayObject.touchイベントのリスナーメソッドも、それに応じて手直しします。
- private function dispatchMouseEvent(eventObject:TouchEvent):void {
- var myTouch:Touch = eventObject.getTouch(stage);
- if (myTouch) {
- // var instance:DisplayObject = eventObject.currentTarget as DisplayObject;
- switch (myTouch.phase) {
- case TouchPhase.BEGAN:
// if (instanceIsTouched(instance, myTouch)) {
- if (instanceIsTouched(eventObject, myTouch)) {
- pressed = true;
- }
- break;
- case TouchPhase.ENDED:
// if (pressed && instanceIsTouched(instance, myTouch)) {
- if (pressed && instanceIsTouched(eventObject, myTouch)) {
- onClick(eventObject);
- }
- pressed = false;
- break;
- }
- }
- }
// private function instanceIsTouched(instance:DisplayObject, myTouch:Touch):Boolean {
- private function instanceIsTouched(eventObject:Event, myTouch:Touch):Boolean {
var instance:DisplayObject = getInstance(eventObject);
- var mousePoint:Point = myTouch.getLocation(instance);
- var touched:Boolean = myBitmapData.hitTest(zeroPoint, 0x0, mousePoint);
- return touched;
- }
- private function getInstance(eventObject:Event):DisplayObject {
- var instance:DisplayObject = eventObject.currentTarget as DisplayObject;
- return instance;
- }
|
この直したスクリプトに、前述3つのマウス操作のメソッドを組込んだのが以下のスクリプト004です。DisplayObject.touchイベントのリスナーメソッド(dispatchMouseEvent())に、Touch.phaseプロパティとフラグのプロパティ(pressed)値に応じた3つのメソッドの呼出しが加えられています(第44〜59行目)。
スクリプト004■複数のマウス操作に応じてインスタンスのふるまいが変わる
// ActionScript 3.0クラス定義ファイル: MySprite.as
- package {
- import flash.display.BitmapData;
- import flash.geom.Point;
- import starling.display.DisplayObject;
- 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;
- public class MySprite extends Sprite {
- 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);
- var ball:Image = new Image(myTexture);
- addChild(ball);
- ball.x = (stage.stageWidth - myTexture.width) / 2;
- ball.y = (stage.stageHeight - myTexture.height) / 2;
- ball.addEventListener(TouchEvent.TOUCH, dispatchMouseEvent);
- }
- private function onPress(eventObject:TouchEvent):void {
- var instance:DisplayObject = getInstance(eventObject);
- instance.alpha = 0.7;
- }
- private function onClick(eventObject:TouchEvent):void {
- var instance:DisplayObject = getInstance(eventObject);
- instance.y += 5;
- instance.alpha = 1;
- }
- private function onReleaseOutside(eventObject:TouchEvent):void {
- var instance:DisplayObject = getInstance(eventObject);
- instance.alpha = 1;
- }
- private function dispatchMouseEvent(eventObject:TouchEvent):void {
- var myTouch:Touch = eventObject.getTouch(stage);
- if (myTouch) {
- switch (myTouch.phase) {
- case TouchPhase.BEGAN:
- if (instanceIsTouched(eventObject, myTouch)) {
- pressed = true;
- onPress(eventObject);
- }
- break;
- case TouchPhase.ENDED:
- if (pressed && instanceIsTouched(eventObject, myTouch)) {
- onClick(eventObject);
- } else {
- onReleaseOutside(eventObject);
- }
- pressed = false;
- break;
- }
- }
- }
- private function instanceIsTouched(eventObject:Event, myTouch:Touch):Boolean {
- var instance:DisplayObject = getInstance(eventObject);
- var mousePoint:Point = myTouch.getLocation(instance);
- var touched:Boolean = myBitmapData.hitTest(zeroPoint, 0x0, mousePoint);
- return touched;
- }
- private function getInstance(eventObject:Event):DisplayObject {
- var instance:DisplayObject = eventObject.currentTarget as DisplayObject;
- return instance;
- }
- }
- }
|
これで、インスタンスの上でマウスボタンを押すとアルファが下がり、そのまま放せば位置が下がってアルファは戻ります。インスタンスの外でマウスボタンを放すと、位置もアルファももとのままです(図004)。マウスポインタとの重なりは、アルファを識別したビットマップのイメージで評価されます。
図004■インスタンス上でマウスボタンを押すとアルファが下がり放すと戻る
作成者: 野中文雄
更新日: 2012年8月13日 03「マウスボタンを押す操作も確かめる」を加えるとともに、他の項のスクリプトや解説も加筆・補正した。
作成日: 2012年5月29日