2017-06-02

[Flash] Starling Collision Detection

[Flash] Starlingの学習 Collision Detection 衝突判定

DrawingAPIとBitmapDataと不透明度

環境:

  • Starling 2.1
  • Adobe AIR SDK 22
  • FlashDevelop 5.2
  • Windows 7 64bit

[話者] Starlingの衝突判定について、書籍『Introducing Starling』で学ぶ。

drawing circle

まず、円を描く方法だ。

Main.as は前回と同じ。

package
{
    import flash.display.Sprite;
    import flash.display.StageAlign;
    import flash.display.StageScaleMode;
    import flash.events.Event;
    import starling.core.Starling;
    
    /**
     * ...
     * @author foo
     */
    [SWF(width="480",height="480",frameRate="60",backgroundColor="#002143")]
    public class Main extends Sprite 
    {
        private var stln:Starling;
        public static var frameRate:uint = 0;
        
        public function Main() 
        {
            if (stage) init();
            else addEventListener(Event.ADDED_TO_STAGE, init);
        }
        
        private function init(e:Event = null):void 
        {
            removeEventListener(Event.ADDED_TO_STAGE, init);
            // entry point
            stage.scaleMode = StageScaleMode.NO_SCALE;
            stage.align = StageAlign.TOP_LEFT;
            stage.stage3Ds[0].addEventListener(Event.CONTEXT3D_CREATE, onContext3dCreated);
            stln = new Starling(Game, stage);
            stln.showStats = true;
            stln.antiAliasing = 0;
            stln.start();
        }
        
        private function onContext3dCreated(e:Event):void 
        {
            stage.stage3Ds[0].removeEventListener(Event.CONTEXT3D_CREATE, onContext3dCreated);

            if (Starling.context.driverInfo.toLowerCase().indexOf("software") != -1) {
                // GPU disable
                Starling.current.nativeStage.frameRate = 30;
            } else {
                // GPU enable
            }
            frameRate = Starling.current.nativeStage.frameRate;
        }
    }
}

Game.as

package 
{
    import flash.display.Bitmap;
    import flash.display.BitmapData;
    import starling.display.DisplayObject;
    import starling.display.Image;
    import starling.display.Sprite;
    import starling.events.Event;
    import starling.textures.Texture;
    
    /**
     * ...
     * @author foo
     */
    public class Game extends Sprite 
    {
        private const NUM_CIRCLE:uint = 80;
        private var circles:Vector.<Image> = new Vector.<Image>(NUM_CIRCLE, true);
        
        public function Game() 
        {
            if (stage) init()
            else addEventListener(Event.ADDED_TO_STAGE, init);
        }
        
        private function init(e:Event = null):void 
        {
            removeEventListener(Event.ADDED_TO_STAGE, init);
            addEventListener(Event.ENTER_FRAME, onGetGpuStyle);
        }
        
        private function onGetGpuStyle(e:Event):void 
        {
            if (Main.frameRate <= 0) return;
            removeEventListener(Event.ENTER_FRAME, onGetGpuStyle);
            
            var sp:flash.display.Sprite = new flash.display.Sprite();
            var radius:uint = 20;
            //var color:uint = 0x808080 + Math.random() * 0x808080;
            var color:uint = 0xCCCCCC;
            sp.graphics.beginFill(color);
            sp.graphics.drawCircle(radius, radius, radius);
            sp.graphics.endFill()
            var bmd:BitmapData = new BitmapData(radius * 2, radius * 2, true, color);
            bmd.draw(sp);
            var textureCircle:Texture = Texture.fromBitmapData(bmd);
            
            for (var i:int = 0; i < NUM_CIRCLE; i++) {
                var img:Image = new Image(textureCircle);
                img.x = Math.random() * stage.stageWidth;
                img.y = Math.random() * stage.stageHeight;
                img.color = Math.random() * 0xFFFFFF;
                img.scaleX = img.scaleY = Math.random();
                
                addChild(img);
                circles[i] = img;
            }
        }
    }
}

円を描くのは、まずOffScreenのCanvasに、Vectorのshapeを作る。sp.graphics.drawCircleのとこだな。ここでは色はモノクロ、つまりグレーで塗っておく。

次に、BitmapDataを用意して、そこにVector shapeをdrawする。 そしてStarlingのTextureを、BitmapDataから作る。

実Flash。操作不可。

[合いの手] これも操作できないの?

[話者] 操作するのは次に作るぞ。

hitTest BitmapData with transparent

衝突判定は、座標で範囲内か判定する簡易的なやつではなく、ピクセル単位で衝突したか判定するという高度なのをやる。

そのためにはアルファ(不透明度)つきの32bit画像ファイルが必要になる。 画像を用意した。24bit画像+アルファ8bitで色深度32bitだ。

PNG画像ファイルを読み込んでBitmapDataにする。BitmapDataが判定に必要だからだ。

ソースコードは3つ。

Main.asは先ほどと同じ。

Logger.as

package 
{
    import starling.display.Sprite;
    import starling.text.TextField;
    import starling.text.TextFormat;
    
    public class Logger extends Sprite
    {
        private var tf:TextField;
        private var tx:Vector.<String> = new Vector.<String>();
        private const fontsize:int = 12;
        
        public function Logger(w:int = 480, h:int = 480) 
        {
            tf = new TextField(
                w, h, '',
                new TextFormat('Tahoma', fontsize, 0xf0f0f0, 'left', 'top')
            );
            addChild(tf);
        }
        
        public function add(newtext:String = ''):void 
        {
            var dt:Date = new Date();
            var now:String =
                ('0' + dt.getHours()).substr( -2) + ':' +
                ('0' + dt.getMinutes()).substr( -2) + ':' +
                ('0' + dt.getSeconds()).substr( -2) + '.' +
                ('00' + dt.getMilliseconds()).substr( -3);
            tx.push(now + ' ' + newtext);
            var lines:int = tf.height / (fontsize * 1.2);
            while (tx.length >= lines) {
                tx.shift();
            }
            tf.text = tx.join("\n");
        }
    }
}

Game.as

package 
{
    import flash.display.Bitmap;
    import flash.display.BitmapData;
    import flash.geom.Point;
    import starling.display.DisplayObject;
    import starling.display.Image;
    import starling.display.Sprite;
    import starling.events.EnterFrameEvent;
    import starling.events.Event;
    import starling.events.Touch;
    import starling.events.TouchEvent;
    import starling.events.TouchPhase;
    import starling.textures.Texture;
    
    /**
     * ...
     * @author foo
     */
    public class Game extends Sprite 
    {
        private const NUM_CIRCLE:uint = 2;
        private var circles:Vector.<Image> = new Vector.<Image>(NUM_CIRCLE, true);
        private var pnt:Vector.<Point> = new Vector.<Point>(NUM_CIRCLE, true);
        private var bmd:Vector.<BitmapData> = new Vector.<BitmapData>(NUM_CIRCLE, true);
        // current dir 'Proj/src/'
        [Embed(source = "../bin/201706coldet01b.png")]
        private static const Dumbbell:Class;
        private var dest:Point = new Point();
        private var log:Logger;
        
        public function Game() 
        {
            if (stage) init()
            else addEventListener(Event.ADDED_TO_STAGE, init);
        }
        
        private function init(e:Event = null):void 
        {
            removeEventListener(Event.ADDED_TO_STAGE, init);
            addEventListener(Event.ENTER_FRAME, onGetGpuStyle);
        }
        
        private function onGetGpuStyle(e:Event):void  
        {
            if (Main.frameRate <= 0) return;
            removeEventListener(Event.ENTER_FRAME, onGetGpuStyle);
            
            const showStatWidth:int = 100;
            log = new Logger(stage.stageWidth - showStatWidth, stage.stageHeight);
            log.x = showStatWidth; //avoid showStat region
            log.y = 0;
            log.add("framerate per second = " + Main.frameRate);
            if (Main.frameRate <= 30) {
                log.add("GPU disabled.");
            }else{
                log.add("GPU enabled.");
            }
            addChild(log);
            
            addItem();
        }
        
        private function addItem():void 
        {
            var loadedBitmap:Bitmap = new Dumbbell();
            bmd[0] = new BitmapData(loadedBitmap.width, loadedBitmap.height, true, 0x00000000);
            bmd[0].draw(loadedBitmap);
            
            var sp:flash.display.Sprite = new flash.display.Sprite();
            var radius:uint = 20;
            var color:uint = 0xCCCCCC;
            sp.graphics.beginFill(color);
            sp.graphics.drawCircle(radius, radius, radius);
            sp.graphics.endFill()
            bmd[1] = new BitmapData(radius * 2, radius * 2, true, color);
            bmd[1].draw(sp);
            
            for (var i:int = 0; i < NUM_CIRCLE; i++) {
                var img:Image = new Image(Texture.fromBitmapData(bmd[i]));
                img.x = stage.stageWidth / (4 + i) * 3;
                img.y = (stage.stageHeight / 2) - (img.height / 2);
                pnt[i] = new Point(img.x, img.y);
                if (i != 0) img.color = 0xCCCCCC;
                addChild(img);
                circles[i] = img;
            }
            
            stage.addEventListener(TouchEvent.TOUCH, onTouch);
        }
        
        private function onTouch(e:TouchEvent):void 
        {
            var touchPoints:Vector.<Touch> = e.getTouches(stage);
            if (touchPoints.length != 1) return;
            dest = touchPoints[0].getLocation(stage);
            
            circles[0].x += (dest.x - circles[0].x) * 0.1;
            circles[0].y += (dest.y - circles[0].y) * 0.1;
            if (circles[0].x == dest.x && circles[0].y == dest.y) {
                return;
            }

            pnt[0].x = circles[0].x; pnt[0].y = circles[0].y;
            pnt[1].x = circles[1].x; pnt[1].y = circles[1].y;
            if (bmd[1].hitTest(pnt[1], 255, bmd[0], pnt[0], 255)) {
                log.add('touch');
                circles[1].color = 0xFF3333;
            } else {
                circles[1].color = 0xCCCCCC;
            }
        }
    }
}

埋め込みビットマップをBitmapDataにしているのはここだ。

bmd[0] = new BitmapData(loadedBitmap.width, loadedBitmap.height, true, 0x00000000);

この第3引数をtrueにすればtransparentが使用可能になるから、ピクセル単位の衝突判定ができるぞ。

第4引数の色指定も 0xRRGGBB じゃなくて 0xAARRGGBB (AはAlpha)と指定できる。もっともAlphaが 0x00 (不透明度が0%→透明度が100%→完全に透明)の場合「AA」の部分は省略しても同じだ。逆に Alphaを 0xFF (不透明度100%→透明度0%→まったく透けない)と指定すると、埋め込みビットマップの周りが透けないで、黒とかの四角形になってしまうんだな。

参考:

実Flash。操作可。

[合いの手] PCではマウス動かすだけで鉄アレイを動かせるんだね。

[話者] SmartPhoneの場合、鉄アレイをタップしてドラッグする必要があるだろうな。

ちなみに埋め込みビットマップじゃなくて外部ファイル読み込みなら、以下のように変更する。

--- /r/201706drawing/src/Game.as    2017-06-04 11:21:58.857666600 +0900
+++ /r/201706drawing4/src/Game.as   2017-06-04 10:57:32.807813300 +0900
@@ -4,3 +4,5 @@
    import flash.display.BitmapData;
+   import flash.display.Loader;
    import flash.geom.Point;
+   import flash.net.URLRequest;
    import starling.display.DisplayObject;
@@ -25,5 +28,4 @@
        private var bmd:Vector.<BitmapData> = new Vector.<BitmapData>(NUM_CIRCLE, true);
-       // current dir 'Proj/src/'
-       [Embed(source = "../bin/201706coldet01b.png")]
-       private static const Dumbbell:Class;
+       private var loader:Loader;
        private var dest:Point = new Point();
@@ -59,9 +61,14 @@
            addChild(log);
            
-           addItem();
+           loader = new Loader();
+           // current dir 'Proj/bin/'
+           loader.load(new URLRequest("201706coldet01b.png"));
+           loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoadComplete);
        }
        
-       private function addItem():void 
+       private function onLoadComplete(e:Object):void 
        {
-           var loadedBitmap:Bitmap = new Dumbbell();
+           loader.contentLoaderInfo.removeEventListener(Event.COMPLETE, onLoadComplete);
+           var loadedBitmap:Bitmap = loader.content as Bitmap;
            bmd[0] = new BitmapData(loadedBitmap.width, loadedBitmap.height, true, 0x00000000);

0 件のコメント:

コメントを投稿

人気記事