高速更新テキストフィールド作りました(ビットマップフォント用)


この記事の続き的なやつです。
ビットマップフォント画像を他とまとめて1ドローコールを目指す

更新が多いテキストの場合、頻度にもよるでしょうが、素直にTextFieldを使ったほうが速いのではないでしょうか。

と書いたのですが、文字のレイアウト更新が無い状況下では、テキスト内容の更新時に、各文字のレイアウト情報は再計算せず文字のテクスチャのみ切り替えれば良いので、元記事のテクニックと合わせて使えば、ドローコール1を維持しつつ高速に処理できる事に気がつきました。と、いう事で専用クラスを作ってみたので紹介します。スコアやタイム表示に良さげですが、改行もサポートするので、RPGで1文字ずつ表示されていくようなメッセージ表示処理にも使えそうです。


毎フレーム更新でも高速ぽい

文字レイアウトを変えずにテキスト内容だけ更新

FixedLayoutBitmapTextControllerというクラスを作りました。リンクは最後に。最初にフォーマットとして描画したテキストレイアウトのままテキスト内容のみアップデートします。その仕組み上、等幅フォントで使うのが望ましいですが、必須では無いです。(後述)

  • 基本的な使い方

デフォルトでは、左寄せで足りないテキストは空白埋めされます。下記の例では00000000を表示する文字レイアウトでテキストを更新していきます。右寄せ設定にもできます。なお、FixedLayoutBitmapTextController自体はaddChildできません。テキストが配置されているdisplayObjectを取得でき、通常はSpriteを返します。

sample.as
var tbc:FixedLayoutBitmapTextController =
    FixedLayoutBitmapTextController.getInstance("フォント名", "00000000");
tbc.align = Align.LEFT; // or Align.RIGHT
addChild(tbc.displayObject);
tbc.setTextWithPadding("544");
  • スペース以外で余白を埋める

好きな文字でテキストの未指定部分を埋められます。スコア表示むけですね。

sample.as
var tbc:FixedLayoutBitmapTextController =
    FixedLayoutBitmapTextController.getInstance("フォント名", "00000000");
tbc.align = Align.RIGHT;
tbc.paddingChr = "0"; // これで埋める
addChild(tbc.displayObject);
tbc.setTextWithPadding("544");
  • 幅が違う文字をまぜる

仕組み上、幅が違う文字がまざるとテキスト更新時に文字が重なったりしてしまうのですが、幅と文字の関係が崩れなければ混ざっても大丈夫です。ただしこの場合はスペースの自動文字埋めがつかいずらいので、直接テキスト指定するメソッド(setText)を用います。下記例では ":"の文字が他の数字より狭い幅指定となっています。

sample.as
var tbc:FixedLayoutBitmapTextController =
    FixedLayoutBitmapTextController.getInstance("フォント名", "00:00");
addChild(tbc.displayObject);
tbc.setText("69:69"); // 隙間埋めなしで直接テキストを指定するメソッド


フォーマット文字と文字内容更新時の文字の幅の関係が間違っていると、こんな風に文字が重なってしまうので注意です。これは仕様となります。

本当に速いのか?

StarlingのStatだけでは、詳細までわからないので、Adobe Scoutを使って計測してみました。計測対象は一番上のキャプチャ画像のように、たくさんのテキストを表示して、全てを毎フレーム更新するものです。改行を含むメッセージフィールドも置いてあります。(薄いグレー表示されたラベル文字部分は、BitmapFont. createSpriteして作成されたものです。)

  • 今回のクラスを利用


たまに値が跳ねる時がありますが、おおむね安定して高速に動いているようです。(赤い線が60fpsラインで、グラフの青い部分がActionScriptによる負荷です。)前回記事のテクニックにより、ドローコールは1で抑えられています。
 

  • 通常テキストフィールドを利用

今回作った仕組みをつかわず、通常テキストフィールドを利用してみます。高速化のためのバッチ処理が適用され、ドローコールが増えてしまい、この例の場合は12になりました。頻繁な更新が入る場合はやはり遅くなってしまうようですね。


 

  • batchableなテキストフィールドを利用

テキストフィールドには表示順序で隣接する他のテキストフィールドとフォント指定が同じビットマップフォントの場合にまとめてバッチ処理させるbatchableというプロパティーが存在します。これを適用してみます。自分も知らなかったのですが、フォントでなく、同じリソース画像から作られたImageが並んでいてもバッチ共通化が走るようで、今回の場合もドローコール1となりました。高速に動くでしょうか。

通常テキストフィールドの場合とあまり変わりませんね。Starlingのエンジニアのダニエルさんが、たくさんのテキストフィールドをbatchableさせるとドローコールが減ってもかえって遅くなるよと言われているのですが(Starling1.xの頃)、グラフの形状がbatchable=falseのものとほぼ同じであるから判断するに、やはりレイアウトの再計算コストがボトルネックとなっているように見えます。

という事で、自分が作成したクラスは通常のテキストフィールドよりも無事、高速化されているようです。Starlingのビットマップフォントのテキストレイアウト処理を見ると最適化の跡(苦労)がたくさんみて取れ、通常はそれで問題なく高速に動くのですが、あまり最適化してなくとも単純な事しかしない方がやはり速いという事ですね。

--

なお、FixedLayoutBitmapTextControllerには比較用に通常のテキストフィールドを使う設定ができるようになっています。状況によってはこちらの方が速いこともありうるので、実測した結果で、切り替えて使うことができます。displayObjectプロパティーがTextFieldを返すようになります。

sample.as
// 通常の初期化(普通にnew FixedLayoutBitmapTextControllerしてもよい)
FixedLayoutBitmapTextController.getInstance(
    "fontName", "formatString", color, size, width, height);
// テキストフィールドを使う場合
FixedLayoutBitmapTextController.getInstanceWithForGeneralTextField(
    "fontName", "formatString", color, size, width, height);
// batchableなテキストフィールドを使う場合
FixedLayoutBitmapTextController.getInstanceWithForGeneralTextField(
    "fontName", "formatString", color, size, width, height, true);

ぼやき

すぐ作れるかと思ったのですが、文字のオフセット対応と、余白(frame)がある文字テクスチャ対応をするのに時間がかかりました。通常はビットマップフォント作成ツールを使うと余白設定はオフセット値として設定され、文字画像設定側には余白がなくなるのですが、自分が作ったTextureAtlas画像から動的にフォントを生成するやつ( テクスチャアトラスをビットマップフォントへ動的に変換する )を使う場合は、さらにAtlas画像側にも余白設定が入り込んでいるので、厄介でした。まあ通常はそんなフォントがないのでしょうけど。

実リンク

BitmapFontTextFieldFixedLocationクラス本体
https://github.com/harayoki/-qiita1/blob/BitmapFontTextFieldFixedLocation/src/harayoki/starling/FixedLayoutBitmapTextController.as

デモメイン
https://github.com/harayoki/-qiita1/blob/BitmapFontTextFieldFixedLocation/src/StarlingMain.as