ビットマップフォント画像を他とまとめて1ドローコールを目指す


タイトルのような事がStarling2.0(1.xでも行けるかも)で、そこそこ簡単にできたので記事にまとめておきます。

概要

ドローコールは少しでも減らしたいですよね。しかしビットマップフォントを入れ込むとテキストを差し込んだ位置によっては1回2回でなく怒涛のようにドローコールが増えてしまいます。(画像、テキスト、画像、テキスト。。というような順番の繰り返しなっているとそのような事がおきる。)このような時にビットマップフォントデータを他の画像とともにまとめてしまうとドローコールが1つになって非常に都合がいいです。

昔作ったアプリでは、これを真面目にヘルパークラスやらユーティリティやらを作って実装していたのですが、この間やり直してみたら簡単にできてしまったので記事にまとめておきます。

まとめる前

赤いの(nums1.png, nums1.fnt)と

緑の(nums2.png, nums2.fnt)ビットマップフォントデータを用意。
XMLデータは割愛。(適当にテキストエディタで作成)


こんな単純な例でもドローコールが5回も!

sample1.as
// AssetManagerでの読み込み部分
_assetManager = new AssetManager();
_assetManager.enqueueWithName('app:/assets/nums1.png'); //フォント1画像
_assetManager.enqueueWithName('app:/assets/nums1.fnt'); //フォント1設定
_assetManager.enqueueWithName('app:/assets/nums2.png'); //フォント2画像
_assetManager.enqueueWithName('app:/assets/nums2.fnt'); //フォント2設定
_assetManager.enqueueWithName('app:/assets/pappey.png'); //ロボ画像

まとめた後

適当なテクスチャパッキングツールで(ここではTexturePacker4.1.0を利用)3枚の画像をまとめます。
そしてAssetManagerの記述を変えるだけ?です。


画像1枚 atlas.png + 設定XML1つ atlas.xml

sample2.as
// AssetManagerでの読み込み部分
_assetManager.enqueueWithName('app:/assets/atlas.png'); //アトラス画像
_assetManager.enqueueWithName('app:/assets/atlas.xml'); //アトラス設定
_assetManager.enqueueWithName('app:/assets/nums1.fnt'); //フォント1設定
_assetManager.enqueueWithName('app:/assets/nums2.fnt'); //フォント2設定

これで、全く同じ動きをします。なぜでしょう? それはBitmapフォントを作成するときに指定するテクスチャは1枚の画像である必要がなく、アトラスの1部分でも良いからです。AssetManagerの内部処理ではフォント設定に対応したテクスチャ名でテクスチャを取得しているだけなので、このような事ができます。もちろんAssetManagerを使わず手動でBitmapフォントを作成する場合でも同じです。
その場合はフォント画像の合成は動的に行ってもいいかもしれませんね。(コンテキストロスト時の処理が難しくなりますが。。)

ドローコールの数が減るか

動かしてStatsの表示を確認してみましょう。。

ドローコールの数が減って。。ない(笑)ですね。これは、どういう事かというとテキストフィールドの内部では1ドローコールを消費する代わりに、処理をまとめて効率化するバッチ処理が行われているからです。ざっとしか確認していないので間違っているかもしれませんが、テキストが更新された場合などの処理が効率良く書かれているように見えます。ドローコールが増えてもそのほうが速いという判断がなされているのではないでしょうか。

では、テキストのドローコールを減らすことはできないのでしょうか?結論をいうと、できました!


見事にドローコールが1です。何をしたのかというと、TextFieldクラスにはテキストを描画したSpriteを作ってくれる機能が存在するので、それを使いました。TextFiledをnewせず、以下のようにやります。

sample3.as
//TextField.getBitmapFont('フォント名').createSprite(横幅, 縦幅, '表示文字', フォーマット [,オプション]);
var sprite:Sprite = TextField.getBitmapFont('nums1').createSprite(200, 32, '00001111', new TextFormat('nums1', 32, 0xffffff));

このような感じです。Spriteの中身はただのイメージがたくさん置いてあるだけなので、更新のない静的なテキストであれば、これで問題ないでしょう。更新が多いテキストの場合、頻度にもよるでしょうが、素直にTextFieldを使ったほうが速いのではないでしょうか。Scoutなどでパフォーマンスを確認してジャッジしましょう。

さらなる考察

上記では文字ImageのつまったSpriteを生成しましたが、1文字のイメージ単体で生成したり、文字のテクスチャ自体を取り出すこともできます。

sample4.as
var char:BitmapChar = TextField.getBitmapFont(fontName).getChar(48); // 48 = '0'を取得
var image:Image = char.createImage(); // 直接イメージを作る
var texture:Texture = char.texture; // テクスチャだけ取り出す

テキストのアニメーション( Flashぽい! )を行う場合はこれを使うのが良いですね。また、スコアやタイム表示であれば、自分が以前に(starling1.x用ですが)作成した gotoAndPlayが使えるMovieClip( http://qiita.com/harayoki/items/2bf5e1bb8e6296a68858 )に各数字のイメージを突っ込んでおけば、mc.gotoAndFrame('1');などのように使えて便利かもしれません。一桁ごとにMovieClipを用意する感じです。ドローコール1が実現できて、スピードも落ちないはず。

おわりに

文字1つ1つでなく、フォントとしてまとめられた画像を入れ子にしてパックするので、余白を少なくするという点では無駄がありそうですが、TexturePackerであればある程度の余白はトリムしてくれるので、まあなんとかなるかもしれません。ところで、最近のTexturePackerはアルファ付き256色パレットpng(pngquant)も書き出してくれるんですね。すばらしい。