ロード処理 イレギュラーな形状のボタン

2011.09.17kickbaseActionScript3.0


今回は変形ボタンのロードについての記事です。2011.9.17に開催された盛岡ひよこの会(第1回勉強会)で発表させていただいた資料となります。テーマは「ロード処理について語り合おう!」でした。

ボタンの形は、必ずしも四角とは限りません。三角だったり丸だったり、穴が空いていたりすることもあるでしょう。そんなとき、Flash IDEを使えば簡単に実装できます。様々な方法があると思いますが、例として

  • ヒット領域の形状をした透明MovieClipを用意し、それにイベントリスナーを登録、インタラクションとして画像を操作する
  • 画像をくるんだボタンシンボルを作成し、ヒット領域を指定する方法(SimpleButtonクラスのhitTestStateプロパティを使う)
  • さすがIDEだけあってグラフィカルで分かりやすいのですが、元から形状がわかっている決め打ちのボタンにしか対応できない点がネックです。そこで、形状がわからない画像をロードしたときにも対応できるよう実装してみました。

    ▼発表資料

    This movie requires Flash Player 9

    ※SWF領域を一回クリックすることでスライドを操作できます。クリックで次へ、キーボードの左右で前と次へ移動できます

    01 : まずは失敗作から。
    SWFを操作するとわかるのですが、画像の透明部分でも当たり判定が出てしまいます。問題はマウスイベント周辺にありそうです。

    Main(ドキュメントクラスは失敗版/改良版共に同じです)

    package 
    {
    	import flash.display.Sprite;
    	import flash.display.StageAlign;
    	import flash.display.StageScaleMode;
    	import flash.events.Event;
    	
    	/**
    	 * ...
    	 * @author kickbase
    	 */
    	[SWF(width = "640", height = "480", frameRate = "30", backgroundColor = "#FFFFFF")]
    	 
    	public class Main extends Sprite 
    	{
    		public function Main():void 
    		{
    			if (stage) init();
    			else addEventListener(Event.ADDED_TO_STAGE, init);
    		}
    		
    		private function init(e:Event = null):void 
    		{
    			removeEventListener(Event.ADDED_TO_STAGE, init);
    			
    			//ステージ設定
    			stage.scaleMode = StageScaleMode.NO_SCALE;
    			stage.align = StageAlign.TOP_LEFT;
    			
    			var btn:Button = new Button();
    			addChild(btn);
    		}
    	}
    }
    

    Button(失敗版)

    package  
    {
    	import caurina.transitions.properties.ColorShortcuts;
    	import caurina.transitions.Tweener;
    	import flash.display.Loader;
    	import flash.display.Sprite;
    	import flash.events.Event;
    	import flash.events.IOErrorEvent;
    	import flash.events.MouseEvent;
    	import flash.net.URLRequest;
    	
    	/**
    	 * ...
    	 * @author kickbase
    	 */
    	public class Button extends Sprite
    	{
    		private var loader:Loader = new Loader();
    		
    		public function Button():void 
    		{
    			if (stage) init();
    			else addEventListener(Event.ADDED_TO_STAGE, init);
    		}
    		
    		private function init(e:Event = null):void 
    		{
    			removeEventListener(Event.ADDED_TO_STAGE, init);
    			
    			ColorShortcuts.init();//ボタンの演出用
    			
    			loader.load(new URLRequest("import/btn.png"));
    			loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onComplete);
    			loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, onIOError);
    		}
    		
    		private function onIOError(e:IOErrorEvent):void
    		{
    			trace( "e.text : " + e.text );
    		}
    		
    		private function onComplete(e:Event):void 
    		{
    			addChild(loader);
    			loader.contentLoaderInfo.removeEventListener(Event.COMPLETE, onComplete);
    			loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, onIOError);
    			
    			//単純に実装すると、透明部分までボタン領域になってしまう
    			buttonMode = true;
    			addEventListener(MouseEvent.MOUSE_OVER, onOver);
    			addEventListener(MouseEvent.MOUSE_OUT, onOut);
    			addEventListener(MouseEvent.MOUSE_DOWN, onDown);
    			addEventListener(MouseEvent.MOUSE_UP, onUp);
    		}
    		
    		private function onOver(e:MouseEvent):void 
    		{
    			Tweener.addTween(this, { _brightness:0.4, time:0.2 } );
    		}
    		
    		private function onOut(e:MouseEvent):void 
    		{
    			Tweener.addTween(this, { _brightness:0, time:0.8 } );
    		}
    		
    		private function onDown(e:MouseEvent):void 
    		{
    			Tweener.addTween(this, { _brightness: -0.1, time:0.1 } );
    			trace("押したよ");//クリック時のインタラクション
    		}
    		
    		private function onUp(e:MouseEvent):void 
    		{
    			Tweener.addTween(this, { _brightness:0.4, time:0.1 } );
    		}
    	}
    }
    

    02 : 修正版
    修正点は以下のとおりです。

  • カーソルと画像の当たり判定をBitmapData.hitTestメソッドで行う
  • 当たり判定をチェックし続けるためマウスオーバー/アウトイベントではなく、エンターフレームで実行
  • マウスクリック時のイベントハンドラであるonDownとonUpメソッド内で当たり判定を調べ、ヒットしていなかったら抜ける処理を追加
  • エンターフレームでマウスオーバー/アウトを拾っている関係上、マウスクリック時の演出はthisではなくloaderに対して行う
  • Button(修正版)

    package  
    {
    	import caurina.transitions.properties.ColorShortcuts;
    	import caurina.transitions.Tweener;
    	import flash.display.Bitmap;
    	import flash.display.BitmapData;
    	import flash.display.Loader;
    	import flash.display.Sprite;
    	import flash.events.Event;
    	import flash.events.IOErrorEvent;
    	import flash.events.MouseEvent;
    	import flash.geom.Point;
    	import flash.net.URLRequest;
    	
    	/**
    	 * ...
    	 * @author kickbase
    	 */
    	public class Button extends Sprite
    	{
    		private const POINT_ZERO:Point = new Point(0, 0);
    		private var loader:Loader = new Loader();
    		private var isOn:Boolean = false;//ボタンモード(指カーソル)のトグル用フラグ 
    		private var bmd:BitmapData;
    		
    		public function Button():void 
    		{
    			if (stage) init();
    			else addEventListener(Event.ADDED_TO_STAGE, init);
    		}
    		
    		private function init(e:Event = null):void 
    		{
    			removeEventListener(Event.ADDED_TO_STAGE, init);
    			
    			ColorShortcuts.init();//ボタンの演出用
    			
    			loader.load(new URLRequest("import/btn.png"));
    			loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onComplete);
    			loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, onIOError);
    		}
    		
    		private function onIOError(e:IOErrorEvent):void
    		{
    			trace( "e.text : " + e.text );
    		}
    		
    		private function onComplete(e:Event):void 
    		{
    			addChild(loader);
    			loader.contentLoaderInfo.removeEventListener(Event.COMPLETE, onComplete);
    			loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, onIOError);
    			
    			addEventListener(Event.ENTER_FRAME, loop);//エンターフレームで当たり判定を拾う
    			addEventListener(MouseEvent.MOUSE_DOWN, onDown);
    			addEventListener(MouseEvent.MOUSE_UP, onUp);
    			
    			//Loaderから内部のBitmapDataにアクセス
    			bmd = Bitmap(loader.contentLoaderInfo.content).bitmapData;
    		}
    		
    		private function loop(e:Event):void 
    		{
    			//マウスカーソルとBitmapDataの当たり判定を行う
    			isOn = bmd.hitTest(POINT_ZERO, 255, new Point(mouseX, mouseY));
    			
    			buttonMode = isOn;//指カーソルの適応
    			(isOn)? onOver() : onOut();
    		}
    		
    		private function onOver():void 
    		{
    			Tweener.addTween(this, { _brightness:0.4, time:0.2 } );
    		}
    		
    		private function onOut():void 
    		{
    			Tweener.addTween(this, { _brightness:0, time:0.8 } );
    		}
    		
    		private function onDown(e:MouseEvent):void 
    		{
    			if (!isOn) return;//カーソルと画像が重なっていなければ処理をせず抜ける
    			Tweener.addTween(loader, { _brightness: -0.4, time:0.1 } );//thisではなくloaderの輝度を操作する
    			
    			trace("押したよ");//クリック時のインタラクション
    		}
    		
    		private function onUp(e:MouseEvent):void 
    		{
    			if (!isOn) return;//カーソルと画像が重なっていなければ処理をせず抜ける
    			Tweener.addTween(loader, { _brightness:0, time:0.1 } );//thisではなくloaderの輝度を操作する
    		}
    	}
    }
    

    総括 : わざわざこんなことしなくても、ビルトインの機能で実装できるはず!
    非常に遠回りな感があり、納得の行かない実装となってしまいました。もっと美しい実装があると思うので、ご存じの方はご教示ください!

    サンプルのダウンロード


    ページトップへ