本ポストは@umiboseから頂いた質問の回答です。(質問ありがとうございました!)
Starlingフレームワークは独自タッチイベントを実装し、オブジェクトのシェイプ内にタッチがあった場合にイベントを発生します。ただしオブジェクトのタッチされた部分のテキスチャーが透明だった場合にそのタッチを無視したい場合が多くありますが、標準Starlingではその実装が不可能です。(なぜかというと、Starlingはテキスチャーの画像データをGPUに投げたら保持しませんので、タッチが行った時にはどこが透明だったかは認識しませんから。)したがって、StarlingのImageクラスを継承し、テキスチャーを保持するように変更すれば希望のヒットテストできます。この方法、そしてメモリー負担を抑える改善、を以下解説します。
まずは、Image
に紐付くテキスチャーデータを保持するようにstarling.display.Image
クラスを継承します。
package {
import flash.display.Bitmap; import flash.display.BitmapData; import starling.display.Image; import starling.textures.Texture;
public class CachingImage extends Image {
private var cachedTexture:BitmapData;
public function CachingImage(_bitmap:Bitmap) { cachedTexture = _bitmap.bitmapData; super(Texture.fromBitmap(_bitmap)); } } }
|
次の課題は、タッチイベントを発生すべきかどうかのチェックを、テキスチャーの透明度を認識する事です。Starlingはちょうどこのためにヒットテストのメソッドを実装しています、hitTest()
がnull
を返せばタッチイベントがなかった事になって、当DisplayObjectを返すとタッチイベントが発生します。従ってタッチされた座標でテキスチャーの色を取得して、アルファーが低ければnull
を返すようにします。
public var alphaCutoff:int = 20;
public override function hitTest(localPoint:Point, forTouch:Boolean=false):DisplayObject { if (forTouch && (!visible || !touchable)) { return null; }
if (! getBounds(this).containsPoint(localPoint)) { return null; }
var color:uint = cachedTexture.getPixel32(localPoint.x,localPoint.y); if (Color.getAlpha(color) > alphaCutoff) { return this; } else { return null; } }
|
これで、テキスチャーの透明な部分をタッチしてもイベントが発生しない実装ができました。しかしこの実装ですとテキスチャーそれぞれがActionScriptメモリーに保持され、メモリー使用量の上昇が懸念します。しかしよく考えますと、透明度のヒットテストは通常、完璧にピクセル毎に行う必要がありません。テキスチャーを保持する前に縮小しておけば、ヒットテストが少々アバウトになりますがメモリー使用量の負担はかなり緩和されます。テキスチャーによってサイズを10分の1に減らしてもタッチイベントに違和感を感じません。
*実際のテキスチャー*
*メモリーに保持される画像*
*ヒットテストの正確さは許容範囲内*
そして画像の縮小の実装は、bitmapData.draw()
を呼んでFlashのレンダラーに負かせば数行のコードでできます。この改善を下記のように実装できます:
package {
import flash.display.Bitmap; import flash.display.BitmapData; import flash.geom.*; import starling.display.*; import starling.textures.Texture; import starling.utils.Color;
public class SmartImage extends Image {
private var scaling:Number;
private var bitmapCache:BitmapData;
public var alphaCutoff:int = 32;
public function SmartImage(_bitmap:Bitmap, _scaling:Number=0.5) { scaling = Math.max(_scaling, .01); scaling = Math.min(scaling, 1); cacheData(_bitmap); super( Texture.fromBitmap(_bitmap) ); }
private function cacheData(bmp:Bitmap) { var w:int = Math.ceil(bmp.width * scaling); var h:int = Math.ceil(bmp.height * scaling); bitmapCache = new BitmapData(w,h,true,0);
var scaleMatrix:Matrix = new Matrix(); scaleMatrix.scale(scaling,scaling); bitmapCache.draw(bmp, scaleMatrix); }
public override function hitTest(localPoint:Point, forTouch:Boolean=false):DisplayObject { if (forTouch && (!visible || !touchable)) { return null; }
if (! getBounds(this).containsPoint(localPoint)) { return null; }
var color:uint = bitmapCache.getPixel32(localPoint.x*scaling, localPoint.y*scaling); if (Color.getAlpha(color) > alphaCutoff) { return this; } else { return null; } } } }
|
本クラスSmartImageを実装する簡単なテストプロジェクトはこちらです:
Starling ヒットテストプロジェクト
(Flash CS6のFLAが入っています。Flash Builderから確認するにはDocument.as
のロジックを新プロジェクトに加えてください。)