サイトトップ

Director Flash 書籍 業務内容 プロフィール

Macromedia Flash非公式テクニカルノート

Starlingフレームワークで表示オブジェクトと座標の重なりを調べる

ID: FN1204004 Platform: All Version: CS5/ActionScript 3.0 Runtime: Flash Player 11/AIR 3.0

DisplayObjectクラス
パッケージ starling.display
継承 DisplayObject → EventDispatcher → Object
hitTest()メソッド
文法 public function hitTest(localPoint:Point, forTouch:Boolean = false):DisplayObject
概要

与えられた座標がインスタンスと重なっていればインスタンス自身、重なっていないならnullを返す。

引数

localPoint:Point − インスタンスに重なっているかどうかを調べる、インスタンスから見た座標のPointオブジェクト。

forTouch:Booleantrueを渡すと、マウスあるいはタッチ操作ができる表示されたインスタンスのみ、座標の重なりを調べる。デフォルト値はfalse

戻り値 インスタンスが座標と重なっていればそのインスタンス自身、そうでなければnullが返される。
DisplayObjectContainerクラス
パッケージ starling.display
継承 DisplayObjectContainer → DisplayObject → EventDispatcher → Object
hitTest()メソッド
文法 override public function hitTest(localPoint:Point, forTouch:Boolean = false):DisplayObject
概要

表示リストの中から、与えられた座標が重なる最前面の子インスタンスを返す。重なるインスタンスがなければ、戻り値はnull

引数

localPoint:Point − 子インスタンスに重なっているかどうかを調べる、参照もとの親インスタンスから見た座標のPointオブジェクト。

forTouch:Booleantrueを渡すと、マウスあるいはタッチ操作ができる表示された子インスタンスのみ、座標の重なりを調べる。デフォルト値はfalse

戻り値 座標と重なる最前面の子インスタンス。インスタンスがなければnullが返される。

説明
DisplayObject.hitTest()メソッドは、インスタンスから見た座標のPointオブジェクトを引数に渡すと、座標がインスタンスに重なっていればそのインスタンス自身、そうでなければnullを返します。座標との重なりは、インスタンスの矩形領域に含まれるかどうかで決まります。

DisplayObjectを継承するDisplayObjectContainerクラスは、SpriteやStageがサブクラスになります。これらDisplayObjectContainer(のサブクラス)のインスタンスは、子インスタンスがもてるものの、自ら表示するための要素をもちません(表001)。

表001■DisplayObjectを継承するおもなクラスとその機能
表示オブジェクトのクラス 機能
スーパークラス 表示する要素 子インスタンス
Sprite DisplayObjectContainer なし(子インスタンス) もてる
Quad DisplayObject カラーやアルファの設定 もてない
Image Quad テクスチャ もてない

そこで、DisplayObjectContainerクラスは、DisplayObject.hitTest()メソッドを再定義(オーバーライド)します。自らではなく、すべての子インスタンスに対して、与えられた座標を調べて、重なりが認められた最前面の子インスタンスを返します。オーバーライドですので、ふたつの引数の意味はDisplayObject.hitTest()メソッドと同じです。

ふたつのメソッドから返されるインスタンスが自身か子インスタンスかの違いはあるものの、nullでなければメソッドが参照したインスタンスに座標は重なっています。


実装
前掲ふたつのメソッドの実装を簡単にご説明します。第1に、DisplayObjectクラスの中でDisplayObject.hitTest()メソッドは、つぎのように定められています。

public class DisplayObject extends EventDispatcher {

  private static var sHelperRect:Rectangle = new Rectangle();

  public function hitTest(localPoint:Point, forTouch:Boolean = false):DisplayObject {
    if (forTouch && (!mVisible || !mTouchable)) return null;
    if (getBounds(this, sHelperRect).containsPoint(localPoint)) return this;
    else return null;
  }
}

まず、DisplayObject.hitTest()メソッドの第2引数(forTouch)としてtrueが渡されると、参照するインスタンスがマウスやタッチの操作ができない設定になっているか、表示されていないときにはnullを返します[*1]

そうでないときは、つぎに内部的にDisplayObject.getBounds()メソッドを呼出します。このメソッドは第1引数に渡したDisplayObjectオブジェクトから見た、参照するインスタンスの矩形領域をRectangleオブジェクトで返します。第2引数を与えるとそのRectangleオブジェクトにデータが設定され、与えなければ戻り値のRectangleオブジェクトは新たにつくられます[*2]

DisplayObjectオブジェクト.getBounds(DisplayObjectオブジェクト, Rectangleオブジェクト)

そして、Rectangle.containsPoint()メソッドは、渡されたPointオブジェクトがRectangleオブジェクトの矩形領域内にあるかどうかをブール(論理)値で返します。その戻り値を確かめて、矩形領域内にあれば(true)参照したDisplayObjectオブジェクト自身、領域外(false)ならnullが返されます。

第2は、スーパークラスDisplayObjectのメソッドを再定義(オーバーライド)するDisplayObjectContainer.hitTest()メソッドです。まず、メソッドの第2引数(forTouch)としてtrueが渡されると、参照するインスタンスのDisplayObject.visibleまたはDisplayObject.touchableプロパティがfalseのときにnullを返すのは、DisplayObject.hitTest()メソッドと同じです。

public class DisplayObjectContainer extends DisplayObject {

  private static var sHelperMatrix:Matrix = new Matrix();
  private static var sHelperPoint:Point = new Point();

  public override function hitTest(localPoint:Point, forTouch:Boolean=false):DisplayObject {
    if (forTouch && (!visible || !touchable)) return null;
    var localX:Number = localPoint.x;
    var localY:Number = localPoint.y;
    var numChildren:int = mChildren.length;
    for (var i:int = numChildren - 1; i >= 0; --i) {
      var child:DisplayObject = mChildren[i];
      getTransformationMatrix(child, sHelperMatrix);
      transformCoords(sHelperMatrix, localX, localY, sHelperPoint);
      var target:DisplayObject = child.hitTest(sHelperPoint, forTouch);
      if (target) return target;
    }
    return null;
  }
}

つぎに、第2引数がfalse(デフォルトを含む)か、参照するインスタンスにマウスやタッチの操作ができて表示されていれば、forループで子インスタンスすべてをインデックスの大きい順、つまり重ね順の手前から取出して、DisplayObjectContainer.hitTest()メソッドの第1引数に受取ったPointオブジェクトのxy座標(localXとlocalY)との重なりを調べます。

forループの中では、内部的にDisplayObject.getTransformationMatrix()メソッドを呼出しています。すると、参照するインスタンスから第1引数のインスタンスに座標を変換するMatrixオブジェクトが得られます。なお、DisplayObject.getTransformationMatrix()に第2引数を与えるとそのMatrixオブジェクトが変換され、与えなければ戻り値となる変換行列のオブジェクトが新たにつくられます。

DisplayObjectオブジェクト.getTransformationMatrix(DisplayObjectオブジェクト, Matrixオブジェクト)

参照しているのはDisplayObjectContainerインスタンス自身で、第1引数には取出した子インスタンス(child)が与えられています。したがって、得られるMatrixオブジェクト(sHelperMatrix)は、DisplayObjectContainerインスタンスの座標を、子インスタンスから見た座標に変換します[*3]

そのMatrixオブジェクトを第1引数に渡してtransformCoords()関数を呼出すと、第2および第3引数のxy座標が変換されてPointオブジェクト(sHelperPoint)で返されます。第2および第3引数は、DisplayObjectContainer.hitTest()メソッドが受取った第1引数のPointオブジェクトから取出したxy座標値です。つまり、この座標を子インスタンスから見た値に変えたPointオブジェクトが得られるのです。

transformCoords(matrix:Matrixオブジェクト, x座標, y座標, Pointオブジェクト)

そのPointオブジェクト(sHelperPoint)を第1引数にして、子インスタンス(child)に対してhitTest()メソッドを呼出します。そのとき、子インスタンスがDisplayObjectContainerであれば、オーバーライドしたメソッドが呼出されて、さらにその子インスタンスと座標の重なりが調べられます。子インスタンスがDisplayObjectなら、座標とそのインスタンスとの重なりがDisplayObject.hitTest()メソッドにより確かめられることになるのです。

そうして、座標(sHelperPoint)に重なる子インスタンスが戻り値(target)として得られれば、ただちにそのインスタンスをDisplayObjectContainer.hitTest()メソッドから返します[*5]forループを抜けて、ついにオブジェクトが得られなかったときは、座標との重なりはないということでnullを返します。

[*1] DisplayObjectクラスのprivateプロパティmVisibleおよびmTouchableは、get/setアクセサメソッドとして定められたvisibleおよびtouchableの値を内部的に納めます。

private var mVisible:Boolean;
private var mTouchable:Boolean;

public function get visible():Boolean { return mVisible; }
public function set visible(value:Boolean):void { mVisible = value; }

public function get touchable():Boolean { return mTouchable; }
public function set touchable(value:Boolean):void { mTouchable = value; }

[*2] DisplayObject.hitTest()メソッドから内部的に呼出したDisplayObject.getBounds()メソッドには、第2引数として静的なprivateプロパティ(sHelperRect)のRectangleオブジェクトが与えられています。けれど、処理の結果だけ見ると、第2引数を渡さなければ、DisplayObject.getBounds()メソッドが新たなRectangleオブジェクトを返しますので、それを使えばよいはずです。

Rectangleオブジェクトを静的プロパティに置いて使い回すのは、Rectangleオブジェクトが毎回つくられる負荷を減らすためだと考えられます。実際、DisplayObjectクラスの中で静的プロパティのRectangleオブジェクト(sHelperRect)を用いるのは、DisplayObject.hitTest()メソッドのほか、get/setアクセサメソッドとして定められてやはりDisplayObject.getBounds()メソッドを内部的に呼出すDisplayObject.widthDisplayObject.heightだけです。

[*3] DisplayObject.getTransformationMatrix()メソッドの第2引数に渡したMatrixオブジェクトは、静的なprivateプロパティ(sHelperMatrix)の参照です。静的プロパティのオブジェクトを使い回すのは、前述注[*2]と同じく、オブジェクト生成の負荷を減らすためでしょう。

[*4] 第1引数に渡したPointオブジェクトは、静的なprivateプロパティ(sHelperPoint)です。Pointオブジェクトを使い回す理由は、前述注[*2]や[*3]と同じです。

[*5] 本文に述べたとおり、forループではインデックスの大きい手前のオブジェクトから順に重なりを調べています。したがって、ループを中断して返すオブジェクトは、最前面に位置します。



DisplayObject.hitTest()あるいはDisplayObjectContainer.hitTest()メソッドによりインスタンスと座標の重なりを確かめるスクリプトは、「Starlingフレームワークでインスタンスをクリックする」でご紹介しました。そのクラス定義の中でふたつのメソッドを使っているのが、つぎに抜書きしたDisplayObject.touchイベントのリスナーメソッド(dispatchMouseEvent())です。インスタンス上でマウスボタンを押し、かつその上で放したとき、クリック用のメソッド(onClick())を呼出します[*6]

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);
    }
  }
}

まず、TouchEventオブジェクトからTouchEvent.getTouch()メソッドにより、インスタンス上で押したマウスボタンが放されたとき(TouchPhase.ENDED)のTouchオブジェクト(myTouch)を得ます。そして、Touchオブジェクトが得られ(nullでなく)、マウスボタンを放したことがifステートメントで確かめられたら、Event.currentTargetプロパティから、マウス操作が加えられたインスタンスの参照(instance)を得ます。

つぎに、Touch.getLocation()メソッドで、インスタンスから見たポインタ座標(myPoint)を調べます。そのうえで、DisplayObject.hitTest()またはDisplayObjectContainer.hitTest()メソッドによりマウス座標の重なったオブジェクトを取出して[*6]、インスタンスが得られたときにクリック用のメソッド(onClick())を呼出しています。

[*6] 呼出されるのがDisplayObject.hitTest()DisplayObjectContainer.hitTest()メソッドかは、参照するインスタンスによって変わります。すると、マウスポインタが重なったときに返されるオブジェクトも、参照するインスタンス自身か子インスタンスか違ってきます。そのため、ifステートメントでは、インスタンスがあるかどうか(nullでないこと)だけを確かめているのです。


参考
[Starling Framework Reference] > [DisplayObject]「hitTest() method
[Starling Framework Reference] > [DisplayObjectContainer]「hitTest() method


作成者: 野中文雄
作成日: 2012年4月6日


Copyright © 2001-2012 Fumio Nonaka.  All rights reserved.