dbones2cpp: DragonBones のアーマチュアを C++ コードに変換する

bin/dbones2cpp は DragonBones 5.x のデータ (*_ske.json と画像、追加の *.dbani) を、 ボーンアニメーション (rig) で動かせる static const データ一式のヘッダに変換します。

python3 -m pip install -r bin/requirements.txt      # Pillow, numpy
bin/dbones2cpp chara_ske.json chara.hpp             # 名前空間 chara
bin/dbones2cpp --scale 0.5 chara_ske.json walk.dbani chara.hpp
bin/dbones2cpp --out-format rgb565_swapped --out-key '#FF00FF' chara_ske.json chara.hpp

引数は入力の _ske.json、追加のアニメーション (.dbani など、0 個以上)、出力ヘッダの順です。

オプション

オプション

既定値

説明

--namespace NS

入力名から

生成物の名前空間 (chara_ske.json → chara)

--armature NAME

先頭

変換するアーマチュア

--skin NAME

先頭

変換するスキン (他のスキンは警告して無視)

--scale S

1

画像と全座標 (ボーン、display、キーの位置、境界) の縮尺。角度と倍率は変わらない

--anim-scale auto|S

auto

追加アニメーションの位置の縮尺。auto はボーンの length の比の中央値 (1% 以内なら 1)。検出値を表示する

--in-key COLOR

なし (α を使う)

入力画像でこの色のピクセルを透明にしてから処理する

--out-format F

argb4444

argb4444 / rgb565_swapped / rgb565 / auto。rgb565 系はキーカラーで透明を表す。auto は画像ごとに選ぶ (下記)

--auto-alpha PERCENT

5

auto で、縁以外の半透明ピクセルが可視ピクセルのこの割合を超える画像を argb4444 に残す

--out-key COLOR

#FF00FF

キーカラー出力で透明部分を塗る色。Armature::colorKey に入る

--alpha-threshold N

128

キーカラー出力で不透明とみなす α (0..255)

--dither D

none

画素変換のディザ (none / diffusion / pattern)

--atlas-width auto|N|0

auto

アトラスの幅 (64〜2048 の 2 の冪)。auto は面積が最小の幅。0 は画像ごとに Texture を出す

--texture-dir DIR

<name>_texture/

フォルダ形式の画像の場所

--fit-rotate

なし

各画像を、不透明部分が矩形にいちばん収まる向きに回して余白を落とす (1 回の bicubic 再サンプリング)。面積の削減が --fit-min-gain 未満の画像は回さない

--fit-min-gain PERCENT

3

回転する価値があるとみなす面積の削減率

--hull N

8

不透明部分を囲む凸多角形の最大頂点数 (3〜16)。描画はこの内側だけを走査する。0 で出さない

--preview FRAMES

なし

0,12,24.5 のようにフレームを並べると、最初のアニメーションのその姿勢を <出力名>_preview_<frame>.png に描く

--dump-pose FRAMES

なし

全アニメーションの姿勢を <出力名>_pose.json に書く (テストの期待値)

COLOR は #FF8000、F80、orange などの書式です。

入力

  • アニメーションの形式: 5.0 (ボーンごとに全チャネルを持つ frame) と 5.5 以降 (translateFrame / rotateFrame / scaleFrame、スロットの displayFrame / colorFrame) の 両方を読みます。描画順の zOrder タイムラインも読みます。

  • 画像: <name>_tex.json と <name>_tex.png (テクスチャアトラス、トリミング・回転したサブテクスチャ可) が あればそれを、なければ <name>_texture/<display の path>.png を使います。

  • ボーン は親が前に来るよう並べ替え、スロット は z で並べます (これが基本の描画順)。

  • カーブ: curve (3 次ベジェ、区分ベジェ) と tweenEasing を 17 点の表にし、同じ表は共有します。 tweenEasing も curve もないキーは次のキーまで保持します (DragonBones と同じ)。

  • 全キーがバインドポーズと同じチャネルは省きます。

  • .dbani は同じアーマチュア (ボーン名とスロット名が同じ) のアニメーションとして取り込み、ske のアニメーションの 後ろに並べます (同名は置き換え)。位置の縮尺が違うファイルは --anim-scale auto が合わせます。

透過の扱い

入力と出力を別々に指定します。

  • 入力: 画像の α をそのまま使うか、--in-key の色を透明にします。

  • 出力: argb4444 (既定) は α を 4 ビットで持ちます。rgb565_swapped / rgb565 は α が --alpha-threshold 未満のピクセルを --out-key の色で塗り、Armature::colorKeyEnabled を立てます (Instance::draw() が描画中だけそのキーカラーを設定します)。不透明なピクセルが量子化でキーカラーと 一致したときは青の最下位ビットを反転して逃がします (件数を警告)。

縮小は乗算済み α で行うので、透明部分の色がにじみません。

--out-format auto は画像ごとに形式を選びます。半透明のピクセル (4 ビット量子化後の α が 1〜14) のうち、 透明なピクセルに隣接するものはアンチエイリアスの縁で、キーカラー化するとくっきりするだけです。隣接しないものは 透けて見せるためのものなので、それが可視ピクセルの --auto-alpha (5%) を超える画像は argb4444 のまま、 残りは rgb565_swapped + キーカラーにします (rgb_chan では tie と bracelet の 8 枚が argb4444、33 枚が キーカラー)。キーカラーの画像はブレンドでなくコピーで描かれるので速く (回転描画の 1 ピクセルが透明・不透明とも 約 20 命令。ARGB4444 は 25 / 50 / 70)、そのぶん縁のアンチエイリアスがなくなります。 形式が混ざるとアトラスは atlas (ARGB4444) と atlasKeyed の 2 枚になり、--atlas-width 0 なら テクスチャごとの形式になります。Instance::draw() はキーカラーのテクスチャを描く間だけキーを設定します。 --preview はキーカラーの画像の縁を実機と同じように硬くして描きます。

アトラス

縮尺後の画像を高さの順に棚詰めし (隙間なし)、幅は面積が最小になる 2 の冪を選びます。 行ストライドが 2 の冪になるので、RP2040 / RP2350 では全パーツが SHAPOGFX2D_RP2_INTERP の描画パスに乗ります。

余白の削減

回転・拡大した drawImage() は矩形の内側を 1 ピクセルずつ歩くので、透明な余白にも走査と α 判定のコストが かかります (x86-64 で透明 1 ピクセルあたり約 25 命令。不透明は約 50)。詰めるためにアトラスに入れる前の 各画像に次の処理をします。

  1. トリミング (常に): 不透明なピクセル (argb4444 では α ≥ 9、キーカラー出力では --alpha-threshold 以上) を囲む矩形で切り出し、ずれは Attachment::local に畳み込みます。

  2. 回転 (--fit-rotate): 不透明ピクセルの凸包の、面積最小の外接矩形を求め (回転キャリパー法)、 それが水平になる角度に画像を回して余白を落とします。縮小と回転を 1 回のアフィン変換 (bicubic、乗算済み α) で元画像から再サンプリングし、bicubic のリンギングで縁から 1 ピクセルより外に出た薄いピクセルは消します。 回転は local に畳み込むので、ボーンやアニメーションのデータは変わらず、見た目の位置も変わりません。 面積の削減が --fit-min-gain に満たない画像はぼけるだけなので回しません。

  3. 凸包 (--hull): 残った画像の不透明ピクセルの凸包を、隣り合う辺の交点で辺を置き換える (増える面積が 最小のものから) 方法で N 頂点以下に単純化し、重心から外向きに整数に丸め、全ての不透明ピクセルを含むことを 確かめてから hull_<画像名> として出力します (矩形の 98% 以上を占めるものは出しません)。 Instance::draw() は drawImage() にこの多角形を渡し、その内側だけを走査します。

demorig の rgb_chan では、凸包で 320x240 のフレームが 5.5% (640x360 で 8.6%、2 倍ズームで 10%) 軽くなり、 --fit-rotate でアトラスが 338 KB から 296 KB に減ります。効果は画像の形によります。 ヘッダ先頭のコメントに、削減後のピクセル数、回した画像の数、凸包の数と面積を書き出します。

出力の構成

名前

内容

atlasData, atlas

アトラスのピクセル配列と Texture (--atlas-width 0 では tex_<画像名>)

attachments_<スロット名>

スロットのアタッチメント配列

bones, slots, armature

ボーン、スロット、rig::Armature

anim_<名前>

rig::Animation。キー配列 (anim_<名前>_bone<i>_rotate など)、タイムライン、描画順、カーブ表がその前に並ぶ

hull_<画像名>

int16_t[]。画像の不透明部分を囲む凸多角形の頂点 (x, y の組)。Attachment::hull から参照

animations[], ANIMATION_COUNT

全アニメーションへのポインタ配列とその数

識別子にできない名前は _ に置き換え、重複には番号を付けます。先頭のコメントに入力ファイル、縮尺、出力形式、 アトラスの大きさ、概算バイト数、警告の一覧が入ります。

対応していない機能

メッシュ・FFD、IK、入れ子のアーマチュア、イベント、スロットの RGB の色変換、追加の回転数 (clockwise / tweenRotate)、回転や拡大を継承しないボーン、複数のスキンは警告して無視します。 メッシュなど画像以外の display は、添字がずれないよう「描かないアタッチメント」として残します。

性能のためのオプションの選び方

描画コストはほぼ「歩いたピクセル数 × ピクセルの種類ごとの単価」で決まり (x86-64 で透明 25、不透明 50、 半透明 70 命令程度)、それに加えてパーツをフラッシュから読むキャッシュミスがかかります。 数字は demorig の rgb_chan (縮尺 0.8、29 ボーン 41 スロット) の 320x240 のフレームでの実測です。

まず、テクスチャがキャッシュに収まるかどうかを見てください。パーツはフレームごとに 1 回ずつ読まれるので、 全パーツの合計がフラッシュの手前のキャッシュより大きいと、毎フレーム全てをフラッシュから読み直すことになり、 命令数の削減と同じくらい効きます。M5Stack Tab5 (ESP32-P4、L2 256 KB) では、アトラスの行が触れる 321 KB では 30 fps、画像ごとのテクスチャで 216 KB になると 42 fps でした。CoreS3 (ESP32-S3、64 KB) にはどちらも収まらず 変化なしです。合計を減らす手段は --scale (面積に比例)、--fit-rotate (余白)、--atlas-width 0 (行の余白) で、目安はヘッダ先頭のコメントの「pixels」× 2 バイトです。

オプション

効くところ

効きやすい状況

代償

--hull (既定 8)

透明なピクセルの走査を省く。フレーム −5.5% (640x360 で −9%、2 倍ズームで −10%)

パーツの形が矩形から遠いほど (手足、髪、斜めのもの)。矩形のパーツには付かないので損はしない

アタッチメント 1 つあたり 4 バイト × 頂点数と、パーツ 1 枚につき行ごとの積和が数十命令

--fit-rotate

余白を落としてアトラスを縮める (338 KB → 296 KB)。時間は --hull があるとズーム時に 1% 程度

細長いパーツが斜めに描かれている絵。フラッシュやキャッシュに収めたいとき

回したパーツは 1 回再サンプリングされてわずかにぼける。元の解像度が低いと目立つので、アセットを表示の 2 倍で描いて --scale を半分にするとよい (demorig はそうしている)

--atlas-width 0

行の余白がなくなり、フレームあたりのキャッシュラインが 3 分の 1 減る (64 バイト 5133 → 3464 ライン)。ヘッダも小さい (296 KB → 227 KB)

パーツがフラッシュにあり、詰めた合計がキャッシュに収まるとき。M5Stack Tab5 (L2 256 KB) では 30 → 42 fps、CoreS3 (64 KB、どちらも収まらない) では変化なし

RP2040 / RP2350 では SHAPOGFX2D_RP2_INTERP の経路 (ストライドが 2 の冪) から外れる

--out-format auto

縁以外に半透明のないパーツをキーカラーにする。不透明なピクセルがブレンドでなくコピーになり、フレーム −30% (3.90M → 2.73M 命令、640x360 で −35%)。実機では CoreS3 24 → 27 fps、Tab5 42 → 56 fps

速度が最優先で、縁のギザギザを許せるとき。半透明のパーツ (rgb_chan の tie や bracelet) は ARGB4444 のまま残る

キーカラーになったパーツは縁のアンチエイリアスが消える (α が --alpha-threshold 未満は透明、以上は不透明)。demorig では画質のため採用していない

--out-format rgb565_swapped

全パーツをキーカラーに。フレーム −33%

半透明のパーツがない絵、速度が最優先のとき

半透明のパーツが表現できない

--scale

フラッシュとキャッシュの使用量 (面積に比例)。描画時間は画面上のピクセル数で決まるので、ほぼ変わらない

キャラクタを画面に等倍で出す縮尺に合わせる。キャッシュに収めるために少し小さくするのも手

ズームインしたときに粗くなる

組み合わせの目安:

  • ESP32-S3 / ESP32-P4: --fit-rotate --atlas-width 0。速さが画質に優先するなら --out-format auto も。

  • RP2040 / RP2350: アトラス (既定) のまま --fit-rotate。interpolator の経路を保つ。

  • キャッシュのないマイコンや RAM に置ける小さなキャラクタ: 並べ方は速度に影響しない。

並べ方 (描画順に沿って並べる、縦に積むなど) は効きません。キャッシュは 64 バイト単位でしか読まず、 1 フレームに全パーツを 1 回ずつ読むので、パーツ同士の位置関係より、行の端で余計に読むバイト数と合計が キャッシュに収まるかどうかで決まります。

ワークフローの例

demorig のキャラクタは次のコマンドで作られています (make -C example/wasm/demorig model)。

bin/dbones2cpp --scale 0.4 --fit-rotate assets/2d/rgb_chan/rgb_chan_ske.json \
    assets/2d/rgb_chan/Armature_animtion0.dbani example/common/demorig/model/rgb_chan.hpp
bin/dbones2cpp --scale 0.4 --fit-rotate --atlas-width 0 assets/2d/rgb_chan/rgb_chan_ske.json \
    assets/2d/rgb_chan/Armature_animtion0.dbani example/common/demorig/model/rgb_chan_sep.hpp  # M5Stack 版

アセットは表示の 2 倍の解像度で描かれており、--scale 0.4 で 480x320 の画面の大きさになります。 --fit-rotate の再サンプリングが細かい元絵からになるので、回したパーツのぼけが目立ちません。

変換結果は --preview の PNG で確認できます。プレビューは変換後のデータ (量子化した角度・倍率・カーブ) から 描くので、実機の姿勢と一致します。