2フレーム待ちなさい −AS3でベクタデータをBitmapDataに描画する時の罠−

8 月 29, 2007 on 2:21 pm | In Flash.AS |

タダだからFlex SDK使ってるぜ!な人は多いと思います。
Flex SDKでゲームを作ってる人はもっと多いと思います。
むしろたくさんいてほしいと思うのは私の願望です。
みんなFlexでゲーム作ろうぜ!タダだから!(しつこい)

そんなこんなで高速なAS3の恩恵にあずかりつつ
コマンドラインで手軽にコンパイルできる事で有名なFlex SDKであり、
実際コマンドラインが便利でしかも軽いというのが
私が無償のFlex SDKを使っている主な理由なのですが、
今日の話は他のソフトで作ったswfファイルを持ってくる際の問題のようです。


とりあえず、適当に

SpriteDrawTest.as


package {
	import flash.display.*;

	public class SpriteDrawTest extends flash.display.Sprite {

		public static var inst:SpriteDrawTest;
		private var spriteDrawer:SpriteDrawer;
		private var spriteDrawer1:SpriteDrawer1;
		private var spriteDrawer2:SpriteDrawer2;
		[Embed(source="_src/sampleshape.swf")]private static var testShape:Class;

		public function SpriteDrawTest() {
			inst = this;
			spriteDrawer = new SpriteDrawer(new testShape());
			spriteDrawer1 = new SpriteDrawer1(new testShape());
			spriteDrawer2 = new SpriteDrawer2(new testShape());
			var bitmap:Bitmap;
			bitmap = new Bitmap(spriteDrawer.bitmapData, "always", false);
			bitmap.x = 0;
			this.addChild(bitmap);
			bitmap = new Bitmap(spriteDrawer1.bitmapData, "always", false);
			bitmap.x = 100;
			this.addChild(bitmap);
			bitmap = new Bitmap(spriteDrawer2.bitmapData, "always", false);
			bitmap.x = 200;
			this.addChild(bitmap);
		}
	}
}

みたいなクラスを作るとする。

  • このクラスには1フレームのswfが埋め込まれ、
    そのベクタデータはコンストラクタの内部でnewされる。
  • newされたスプライトはSpriteDrawerのコンストラクタに渡される。
  • そして、このクラスは3つのBitmapを横並びに配置し、
    そのBitmapDataはさっき作ったSpriteDrawerから取ってくる。

そして、渡されたスプライトについてこれをBitmapDataに描画して
そのBitmapDataを保持する以下のようなクラスがあったとする。

SpriteDrawer.as


package {
	import flash.display.*;

	public class SpriteDrawer {

		public var bitmapData:BitmapData;

		public function SpriteDrawer(shape:IBitmapDrawable) {
			this.bitmapData = new BitmapData(100, 100, true, 0xffffffff);
			this.bitmapData.draw(shape);
		}
	}
}

一見これで動きそうなのだが、ダメである。
どうやら、newしたフレームの間はグラフィックが存在しないため、
BitmapData.draw()で描画しようとしても無駄なようである。

というわけで、1フレーム待ってみた。

SpriteDrawer1.as


package {
	import flash.display.*;
	import flash.events.*;

	public class SpriteDrawer1 {

		private var shape:IBitmapDrawable;
		public var bitmapData:BitmapData;

		public function SpriteDrawer1(shape:IBitmapDrawable) {
			this.shape = shape;
			this.bitmapData = new BitmapData(100, 100, true, 0xffffffff);
			SpriteDrawTest.inst.addEventListener(Event.ENTER_FRAME, this.initBitmaps);
		}

		private function initBitmaps(evt:Event):void {
			SpriteDrawTest.inst.removeEventListener(Event.ENTER_FRAME, this.initBitmaps);
			this.bitmapData.draw(this.shape);
			this.shape = null;
		}
	}
}

辛抱強く待ったのが報われそうだが、これも不正解である。
意図した動作を行なうには以下のスクリプトが正解。
なんじゃこりゃ。

SpriteDrawer2.as


package {
	import flash.display.*;
	import flash.events.*;

	public class SpriteDrawer2 {

		private var shape:IBitmapDrawable;
		public var bitmapData:BitmapData;

		public function SpriteDrawer2(shape:IBitmapDrawable) {
			this.shape = shape;
			this.bitmapData = new BitmapData(100, 100, true, 0xffffffff);
			SpriteDrawTest.inst.addEventListener(Event.ENTER_FRAME, this.waitOneFrame);
		}

		private function waitOneFrame(evt:Event):void {
			SpriteDrawTest.inst.removeEventListener(Event.ENTER_FRAME, this.waitOneFrame);
			SpriteDrawTest.inst.addEventListener(Event.ENTER_FRAME, this.initBitmaps);
		}

		private function initBitmaps(evt:Event):void {
			SpriteDrawTest.inst.removeEventListener(Event.ENTER_FRAME, this.initBitmaps);
			this.bitmapData.draw(this.shape);
			this.shape = null;
		}
	}
}

埋め込んだムービークリップのグラフィック要素は
newした後に最初に呼び出されるenterFrameイベントの後に
描画されるって解釈でいいのだろうか?

■内部的な処理の流れ(想像)
◆コンストラクタ
new SpriteDrawer2()
・スプライトもnewされる
 ↓
◆最初のフレーム
enterFrameイベント中にSpriteDrawer2.waitOneFrame()
・スプライトの最初のフレームのシェイプが描画される
 ↓
◆次のフレーム
enterFrameイベント中にSpriteDrawer2.initBitmaps()
・シェイプが描画されているので、BitmapData.draw()することができる


どうやら私はカオスに足を踏み入れてしまったようです。
自分でもよく分かりませんでしたが、
少なくとも、newしたてのSprite次のフレームまで空っぽですよ、
ということみたいでした。
例えば、こんな事をするとスプライトは全く表示されません。

SpriteDrawerTest.as


package {
	import flash.display.*;
	import flash.events.*;

	public class SpriteDrawTest extends flash.display.Sprite {

		[Embed(source="_src/sampleshape_v9.swf")]private static var testShape:Class;

		public function SpriteDrawTest() {
			this.addEventListener(Event.ENTER_FRAME, this.onEnterFrame);
		}

		private var spr:Sprite;
		private function onEnterFrame(evt:Event):void {
			if (spr != null) this.removeChild(spr);
			spr = new testShape();
			spr.x = 100;
			spr.y = 100;
			this.addChild(spr);
		}
	}
}

また、埋め込んだ1フレームのswfのバージョンが9だと2フレーム待つけど
バージョン8だと1フレーム待つだけで表示されるみたいです。

(バージョンはバイナリをいじって変えただけなので、
 シンボル埋め込みの際にはswfのバージョン情報だけ見ているようです。
 swf埋め込みの際にはActionScriptは全て取り除かれるため、
 swfファイルのActionScriptバージョンは関係ありません)

2フレーム以上のswfを埋め込んだりしたらどうなるのか
考えるだけで夜も眠れません。

実際に動かしてみるのが一番分かりやすいと思います。
ソースとか生成物とか(lzh)

 

※まとめ

  • [Embed]メタタグなどで埋め込んだベクタデータをnewしてから
    それがBitmapData.draw()メソッドで描画できるようになるまでは、
    2フレームかかってしまう場合がある。
  • 1フレーム待てば十分な場合もあるみたいである。
  • つまり、画像を容量の少ないベクタデータを持っておいて
    内部でビットマップにキャッシュして速度も稼ごうという目論みは
    そうとうめんどくさい

少なくとも、newしたてのスプライトは
BitmapData.draw()しても描画されないようです。
(もっとも、newしたてのスプライトでもgraphics.lineTo()などを駆使して
 ASで描画した分は、直後に呼び出したBitmapData.draw()にも反映されますが
 そういう使い方はあまりしないような気がします)

埋め込みビットマップではこのようなことは起こらなかったので
Flexでゲームを作るならフルビットマップマジお勧めです。
フラッシュっぽさのかけらもありません。

No Comments yet »

このコメント欄の RSS フィード TrackBack URI

コメントをどうぞ

XHTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Powered by WordPress with Pool theme design by Borja Fernandez.
Entries and comments feeds. Valid XHTML and CSS. ^Top^