2009年12月15日

Flashの描画速度をBitmapDataクラスを使って上げる方法

Flashでゲームなどを作る時に、動作の重さは悩みの種です。 軽い容量となめらかな拡大縮小が長所のFlashなのですが、その分、描画速度は犠牲になっていて、フルフラッシュサイトでCPUをガンガン食われてしまった経験もあると思います。

この重さを解決する方法の1つとして、BitmapDataを使うというものがあります。 これは素材をgifやjpegにする、という話ではなく、表示部分の扱いをビットマップデータにしてしまうというものです。 たとえ素材がgifでも、MovieClipとかに入れてたくさん表示しているととたんに重くなってしまいます。 これをBitmapDataとBitmapクラスによる表示に切り替えるだけで、軽くできることがよくあります。

ちょっとサンプルで体感してもらいましょう。 wonderflにはこういう実験作品があるのでちょっと拝借。(リンク先の画面右側の三角マークを押せばサンプルが見れます。)


BitmapData直描きにすれば残像付きでも超軽いよ | wonderfl build flash online

Flashで大量表示をやったことのある人なら驚く早さだと思います。 大量の表示物はFlashが苦手とするところですが、この作品はBitmapDataを使って高速化しています。

こういうことができるのでBitmapDataやってみたいなーって人も多いのですが、今までMovieClipやSpriteを中心に使ってきたFlash畑の人とかは結構な確率で躓きます。 クセが随分と違うので、そこで失敗して、なーんにも表示されない、とか結構あります。 なので、そういう人向けにBitmapDataの基本を書いていきたいと思います。

BitmapDataクラスはAS2.0から使用可能ですが、ここではAS3.0想定で書いていきます。 Bitmapdataと、Bitmapという単語が連続して出てきてわかりにくいので、それぞれ色分けをしてあります。

BitmapDataを表示する

登場するのはおなじみSprite(MovieClip)と、BitmapData、それとBitmapというクラスになります。 BitmapDataBitmapが最初混乱の元なのですが、BitmapDataはフィルムで、Bitmapがテレビ、みたいなものだと思ってください。 絵のデータそのものはBitmapDataですが、表示するにはBitmapの力を借りなければいけません。 BitmapはSpriteと同じく、DisplayObjectの一種なので、大体Spriteと同じように使えます。xとかyとかalphaとか設定できます。 なんでBitmapって名前なんでしょうね?BitmapSpriteとかBitmapDisplayとかだったらわかりやすいのに。

とりあずまずは塗りだけのBitmapDataを表示してみましょう。 (以後のコードは、ステージ上にあるSprite等を継承したクラスに記述することを前提とします)

var bitmapData:BitmapData = new BitmapData(200, 200, false, 0xff0000);  // 200x200の不透明赤色のBitmapDataを作る
var bitmap:Bitmap = new Bitmap();
bitmap.bitmapData = bitmapData; // new Bitmap(bitmapData)としてもいい。
addChild(bitmap);   // 配置する

とりあえずこれで、200x200の赤い四角が表示されれば成功です。 0xff0000の部分を変えれば他の色になると思います。 型が見つからないと言われる場合はちゃんとクラスをimportしておいてください。 addChildが未定義とか言われる場合は、表示クラスの中じゃないんだと思います。

これ見てもらえればわかると思いますが、BitmapのbitmapDataという変数に入れたBitmapDataが表示されることになります。(ややこしい)

BitmapDataは、最初に作るときにサイズを指定してやらなければいけません。(今回は縦横200) このサイズは途中では変えられないので、もし変える場合はBitmapDataを作り直さなければいけません。 BitmapDataは作り直すのに少しCPUを使うので、できれば作り直さなくていいように、必要な大きさをとっておくほうがいいです。 あまり大きすぎるとメモリを食ってしまいますが。

new BitmapData()の3番目の引数にfalseを指定していますが、これをtrueにすると透明持ちのBitmapDataになります。 で、透明持ちのBitmapDataは色の指定が8桁に増えます。 例えば、(赤)0xff0000→0xffff0000になります。 最初の2桁がアルファ値になるので、ffをくっつければ今までどおり、88くらいにしてやると半透明です。 今までBitmapDataを扱っていなかった人は8桁の色指定とか初めてだと思うので、ミスらないように気をつけましょう。

グラフィックをBitmapDataにする

次に絵を表示していきます。 BitmapDataにグラフィックを判子のようにペタペタ貼り付けるイメージです。 絵の表示に使うのは、主にBitmapDataのdraw()とcopyPixels()です。 それぞれの特徴は

draw()

  • 重い
  • SpriteとかMovieClipとか、比較的なんでも描画できる。フィルタも反映できる。
  • 回転とか色の変更とかしながら描画できる

copyPixels()

  • 超軽い
  • BitmapDataからしか描画できない
  • 回転とか色の変更とかできない

drawの「重い」というのは、どれくらい重いかと言うと、Flashが普段描画しているくらい重いです。 なので、毎フレーム画面全体をdraw()してたら意味無いです。むしろ重くなります。 copyPixels()は無茶苦茶軽いです。びっくりします。 つまり、Flashの描画をBitmapDataで軽くしようというのは、いかにcopyPixels()を使うかということになります。 ところが、copyPixels()はBitmapDataからしか描画できません。 元素材がgifなどの場合はcopyPixels()のみでやっていけますが、Flashで描いた絵や拡大縮小、回転などをするときはどうしてもdraw()が必要になります。

draw()の使い方

var sprite:Sprite = new Sprite();   // とりあえずSpriteを用意。ゲームを作る時は、あらかじめ絵の入ったものを用意する
// テスト用に円を描く
sprite.graphics.beginFill(0xff0000);
sprite.graphics.drawCircle(0, 0, 20);
// bitmapDataとbitmapを用意する
var bitmapData:BitmapData = new BitmapData(200, 200, true, 0x00000000); // 透明なBitmapDataを用意
var bitmap:Bitmap = new Bitmap();
addChild(bitmap);   // 配置する
var matrix:Matrix = new Matrix();   // 表示位置はMatrixで指定する
matrix.translate(50, 50);   // (50, 50)の位置に描画する設定にする
bitmapData.draw(sprite, matrix);    // spriteをbitmapDataに描画する
bitmap.bitmapData = bitmapData; // bitmapDataをbitmapに登録する

drawで描画すると、そのままの場合左上に描画されてしまいます。 位置の指定はMatrixで行うのですが、Matrixというのもここで初めて扱う人が多くて、よく混乱してしまいます。 慣れないうちはMatrixを使わず、描画したいものをさらにSpriteの中に入れてしまい、xやy、rotateなどで表示位置を調節して、外側のSpriteを描画するようにしたほうが楽です。っていうか僕は今でもそうすることが多いです。

copyPixels()の使い方

var parts:BitmapData = new BitmapData(100, 100, true, 0xffff0000);  // とりあえず赤色の100x100のビットマップデータを作る
var bitmapData:BitmapData = new BitmapData(200, 200, true, 0x00000000); // こっちは表示用の透明なBitmapData
var bitmap:Bitmap = new Bitmap();
addChild(bitmap);   // 配置する
var point:Point = new Point(50, 50);    // 表示位置はPointで指定する
var rect:Rectangle = new Rectangle(0, 0, 100, 100); // 描画範囲をRectangleで指定する。
// ※↑たまに間違ってnew Rectangle(100, 100)とかにしてしまい、描画範囲0になってはまる人がいます。引数4つなのでお忘れなく。
bitmapData.copyPixels(parts, rect, point);  // 描画する
bitmap.bitmapData = bitmapData; // bitmapDataをbitmapに登録する

copyPixels()では、位置にPointを使い、表示範囲としてRectangleを使います。 Pointはともかく、Rectangleは使ったことが無い場合が多いですが、そんなに難しくはないと思います。 ただし、第3引数と第4引数は省略すると0になってしまい、描画がまったくされなくなってしまうので注意してください。 エラーが出ないのではまりやすいです。 それほど描画範囲に拘らなければ、Pointは基本的に(0, 0)でよく、Rectangleは(0, 0, ステージサイズ, ステージサイズ)にしておけばいいです。 このPointとRectangleのセットはよく使うので、僕はよくprivate変数にして使いまわしています。

実際の流れ

実際にFlashで作品を作る時の流れです。 あくまで一例ですが

初期準備

  • 表示用のBitmap、表示用の空のBitmapData変数を用意、
  • 表示用と同じ大きさの背景のBitmapDataの用意(背景が無い場合でも透明のBitmapDataを用意しておく)
  • 各パーツのBitmapDataをdraw()で用意

毎フレームの描画処理

  • 背景BitmapDataをclone()して、表示用のBitmapDataにする(全面を塗り替える場合、copyPixels()よりもclone()が早い)
  • 各パーツのBitmapDataを表示用のBitmapDataにcopyPixels()する。
  • 表示用のBitmapDataを、Bitmap.bitmapDataに代入する

パーツですが、アニメーションや回転はあらかじめ必要な分をBitmapDataに変換しておくと処理が早いです。 上のほうにあった矢印がいっぱい表示される作品ですが、copyPixels()では回転ができないため、それぞれの方向のBitmapDataのパーツをあらかじめ用意してあるんです。 なので、よく見ると完全に全ての方向が表示されているわけではないのです。 それでも無理な場合はdraw()で。

透明つきBitmapDataをcopyPixels()する方法

displayBitmapData.copyPixels(partsBitmapData, rect, point, null, null, true);

透明を有効にするための引数は第6引数です。 で、その前の2つは、アルファ用のBitmapDataとその描画位置を指定するものなのですが、これnullでOKです。 なんか、アルファ用のBitmapDataを指定することなんて殆ど無くて、アルファつきのBitmapDataの描画のほうが機会多いと思うんですけど、どうしてこの引数の順番なんでしょうね。

lock()とunlock()で高速化

BitmapDataクラスにあるlock()は、Bitmapに登録されている場合、その更新を抑える効果があります。 早い話が、Bitmapに登録してあるBitmapDataは、何度もcopyPixelsする前にlock()しておかないと重くなります。 終わったらunlock()しないと、表示が更新されないので注意しましょう。 でも、上のほうで書いた流れの場合、copyPixels()されるBitmapDataは、Bitmapに登録されていないのでlock()の意味は薄いかもです。 でもまあ、一応やっておいてもいいかもです。

draw()しようとするとサンドボックス侵害が出る場合

別ドメインの画像やswfは、読み込んで表示するだけなら結構セキュリティ甘いのですが、内容を参照したり弄ったりしようとするととたんに厳しいです。 外部画像を読み込んでdraw()しようとした途端にセキュリティサンドボックス侵害が出る場合は、crossdomain.xmlとか、URLLoaderDataFormat.BINARYとかを検索してみてください。

トラックバックURL

このエントリーのトラックバックURL:
http://sipo.jp/mt/mt-tb.cgi/7

コメント[7]

文中1箇所bitmapDayaになってますよー
気になっちゃいました

wonderflでも比較されていますが、drawやcopyPixelsより、beginBitmapFillが速いです(描画対象は、BitmapDataではないですが)

copyPixelsを使っていたところを、shapeへのbeginBitmapFillに差し替えたところ、爆速になって驚きました。

bitmapDayaなおしましたw

>匿名さん
1枚だけだとそうですけど、ゲームみたいな複数の透明つきパーツのあるコンテンツでも使えます?
アルファのマージとかどうやるのかな。

copyPixels()の使い方内の

var parts:BitmapData = new BitmapData(100, 100, true, 0xffffffff); // とりあえず赤色の100x100のビットマップデータを作る

の色指定が白になってますよ?。

赤にならなくて悩みました…。

なおしましたー

とても勉強になりました。
知りたい事を全て教えてくれました!ありがとうございます。

すごい勉強になりました。

今、パックマンのゲームを個人的に作ってて、
調べないといけなかったことがほとんど解消されました。

その事を書きたいので、自分のブログにリンクを貼ってもいいでしょうか?

コメントする