こんにちは、Liseと申します。
みなさんはゲームのアセットをAIで作ろうとしたことはあるでしょうか?
X(Twitter)を見ていると怪しげな従量課金のゲームアセット生成サービスがポコポコ流れてくるのですが、ワンショットのアセットならともかく、大量にキャラクターのアセットが欲しい場合にどうも非効率というか向いていないように思えます。お金もかかりますし。
そこで彼らに出来るのなら自分でも作れるはずだと思い、自分で構築してみました、という備忘録です。
結論だけ先に言うと、 「キャラシート1式 → LoRA学習 → ポーズ生成 → 動画AIでモーション化 → 生成AIでドット絵変換 → 透過・正規化」 という完全なパイプラインが ComfyUI 上で組みこむという形になりました。
1キャラあたり7モーション・計58スプライトを、人間の作業は「実行ボタンを押す」「良いコマを選ぶ」だけで納品形式まで持っていけます。
最終的な打算・妥協もあり、100%納得出来る品質のものは作れませんでしたが、可能性を感じたので一旦整理するためにも記事にしようかなと思いました。
ここに至るまでに大量の落とし穴を踏みました。本記事はワークフローの紹介を主軸にしつつ、各工程で「試して捨てた方式」と「なぜ捨てたか」をコラムとして挟みます。むしろそちらが本体かもしれません。
この記事でわかること
- ComfyUI で「静止画生成 → 動画AI でモーション化 → フレーム選別 → ドット絵変換」を一気通貫させる構成
-
Codex CLI(ChatGPTサブスク)を ComfyUI のカスタムノードとして叩く方法
- 従量課金したくない!
- 生成AIでアニメフレームの一貫性を保つためのプロンプト設計(と、失敗パターン)
- 「AIが描いたドット絵風画像」を本物の1ドット=1ピクセルに正規化する方法
- 非決定的で高価な生成工程を安全に運用するための設計原則
そもそも ComfyUI とは
本題に入る前に、ComfyUI ってなんだという話だけします。
ComfyUI は、Stable Diffusion などの画像生成モデルをノードグラフ(箱を線でつなぐ形式)で動かすワークフローエンジンです。「モデルを読む」「プロンプトをエンコードする」「サンプリングする」「保存する」といった処理が全部ノードとして分解されていて、それを自由につなぎ替えられます。
画像生成のローカル環境としては Stable Diffusion WebUI(AUTOMATIC1111)の方が有名だと思うので、そちらとの違いで説明します。
| Stable Diffusion WebUI | ComfyUI | |
|---|---|---|
| 操作 | フォームに入力して生成ボタン | ノードをつないでグラフを作る |
| 得意なこと | 1枚絵の生成・ガチャ | 多段処理の自動化 |
| 処理の流れ | 基本は「生成→保存」の一発 | 生成→加工→選別→再生成…を1グラフに |
| 再現性 | 設定はUIの状態 | ワークフロー=JSONファイル(保存・共有・スクリプト生成可) |
| 拡張 | 拡張機能(Extension) | カスタムノード(Pythonクラス1つで自作可能) |
WebUI が「高機能な生成フォーム」だとすると、
ComfyUI は「画像処理のビジュアルプログラミング」です。
1枚絵を試行錯誤するだけなら WebUI の方が手軽ですが、本記事のような
- ControlNet で立ち絵生成 → 動画AIでモーション化 → フレーム選別 → ドット絵変換 → 透過・正規化
という多段パイプラインを組んで何度も回す用途では ComfyUI 一択になります。
- 途中に人間の選別を挟める(実行を一時停止して画像を選ばせるノードがある)
- 変更のなかったノードは再実行されない(キャッシュが効くので後段だけ何度でも調整できる)
- ワークフローがただの JSONなので、スクリプトで生成・量産できる(後述の量産アーキテクチャはこれが前提)
また、動画生成(Wan2.2 など)や自作 Python ノードのような「Stable Diffusion 以外」も同じグラフに混ぜられるのも便利です。
全体アーキテクチャ
技術スタック
| 役割 | 使用技術 |
|---|---|
| ワークフローエンジン | ComfyUI |
| キャラの同一性 | SDXL チェックポイント + 自作キャラ LoRA(kohya_ss で学習) |
| ポーズの固定 | ControlNet (depth) + ポーズ型画像 |
| モーション生成 | Wan2.2 I2V-A14B(画像→動画) |
| ドット絵変換 | Codex CLI の画像生成ツール(ChatGPT サブスクリプション) |
| 透過・正規化 | 自作カスタムノード(Python) |
パイプライン全体図
[キャラ準備(1回だけ)]
キャラシート生成 → 画風統一 → LoRA学習 → ポーズ型・ドット基準画像の用意
[モーションごとのワークフロー]
Step1 CN : ポーズ型+depth ControlNetで立ち絵候補を生成
Step2 SELECT : 人間が採用1枚を選ぶ
Step3 I2V : 採用画像を始点にWan2.2でモーション動画を生成(49フレーム)
Step4 SELECT : 動画から採用フレームを選ぶ(8〜12枚)
Step5 DOT : Codexでドット絵変換 + 透過/1:1正規化/クロップ → 納品
Attack / Dead のような「決めポーズが存在しないモーション」は Step1/2 を省略し、Idle の立ち絵に余白を付けたものを始点に I2V から始めます(後述)。
前提: キャラの同一性は LoRA で担保する
パイプラインの前提として、キャラの同一性はプロンプトではなく LoRA で担保しています。ChatGPT などで多視点のキャラシートを作り、ベースモデルの画風に img2img で寄せてから kohya_ss で学習——という王道の流れです(本記事の主題ではないので詳細は割愛)。
IP-Adapterという選択肢もあったのですが最終的に使いませんでした。
当初は IP-Adapter でその場転写を試みましたが、使用モデル(v-pred 系 SDXL)と相性が悪く崩壊。そもそも IP-Adapter は「ふんわり寄せる」用途で、ゲームアセットに求める厳密な同一性には LoRA の方が適任でした。「数枚 good を作る → LoRA 化 → 量産」が結局安定します。
Step1-2: ポーズ生成と選別
Idle / Walk は「正解のポーズ」があるので、既存スプライト(または手作りのポーズ画像)を depth ControlNet の型として使い、LoRA を効かせた txt2img で候補を複数枚生成します。
ポイント:
- depth はポーズだけでなく武器のシルエットも固定するので、武器が違うキャラに型を流用すると武器まで固定されます。キャラクターを生成する場合は武器ごとに型を持つ必要があります
- チビキャラ(2頭身)に openpose は使えません。骨格検出が壊れたのでおそらくdepth 一択です
- 生成候補から人間が1枚選ぶ工程は、ComfyUI の
Image Filter系ノード(実行を止めてポップアップで選択)で「実行→クリックで選ぶ」だけの操作にできます- ちょっと選択しにくいけど一番楽
攻撃ポーズはControlNetで作れない(循環問題)
攻撃の「振りかぶり」ポーズ型を作ろうとすると気付きます——振りかぶりの型を作るには振りかぶりの絵が要る。ニワトリと卵です。結論として、新規ポーズは ControlNet ではなくプロンプト駆動の txt2img で「キャスティング」(seed ガチャで良い1枚を選ぶ)するか、次節の動画AIに丸ごと任せるのが正解でした。
Step3-4: 動画AIでモーションを「発明」させる
ゲームアセットを作ろうと試したことがある人はよく分かると思うのですが、
画像生成AIは左右の脚の認識が難しく、中割りが安定して作ることが出来ません。
ここがこのパイプラインの面白いところで、モーションの中割りを人間が描かず、画像→動画AI(Wan2.2 I2V)に任せます。
採用した立ち絵1枚を始点に「She starts to walk / performs an attack combo...」というプロンプトで 49 フレームの動画を生成し、その中から良いフレームを選別します。歩行の足の交互運動も、攻撃の振りかぶり→振り下ろしも、動画モデルが勝手に作ってくれます。
実運用の要点:
- start-only(終点なし)が基本。始点と終点を両方固定する方式(FLF)は「終点の品質が全てを支配する」性質があり、終点に脚が描かれていないと全フレーム脚が消える、といった事故が起きます
- 攻撃のように始点の外に動きがはみ出すモーションは、始点画像に余白を付けます(キャラを縮めて下寄せ配置し、振り上げる武器のヘッドルームを確保)
- 生成は run 単位でフォルダに蓄積し(
run_00000.webp+ 全フレームPNG)、人間は webp を見て採用 run を決めてからフレームを選ぶ、という2段選別にします - 背面(後ろ姿)モーションは「振り返り」との戦いです。向きの固定は negative プロンプトで(
(turning around:1.6), (front view:1.6)...)、動きの形は positive で誘導します。動作を negative に入れると動きそのものが死にます
「歩行はキーフレーム不要」だが「ループには使えない」
Walk 用のポーズ型を作らず「Idle の立ち絵から直接歩かせる」実験もしました。歩行自体は見事に生成されるのですが、冒頭が必ず「idle→歩き出し」の遷移になるため、ループ前提の Walk アニメには使いにくく、ループさせたいモーションは予めループの一部の画像を先に生成するほうが安定します。
Step5: ドット絵変換で画像生成AIの細かいデザインのズレを誤魔化す
当初はドット絵ではなく通常のイラストアニメーションのアセットを予定していましたが、
LoRAによる細かいデザインの担保が難しく、20枚や30枚生成してやっと一貫性を保った画像が1枚生成されるか、という画像ガチャ状態になり、方針の変更を余儀なくされました。
細かい服装のデザインアニメーションのフレームごとに違うので、見る人によっては違和感が出てしまうでしょう。
私は今回ドット絵にすることで気にならないように回避しましたが、ゲーム画面上のキャラクターアセットの表示を小さくする=情報量を減らすといった方法でも回避出来るかもしれませんし、ローカルマシンを持て余しているのであればガチャを引きまくるという方法もアリだと思います。
今回は私が選択した「ドット絵変換で逃げる」という形の対応を記載します。
選別したフレーム(イラスト調)をドット絵に変換します。ここが最も試行錯誤した工程です。
機械変換では「ドット絵」にならない
最初に誰もが試すのが「縮小+減色」ですが、結果は 「縮小しただけの画像」 です。ドット絵の本質(意図を持った輪郭線、ベタ塗りの面、クラスタ単位の陰影)は解像度ではなく描き方なので、機械処理では生まれません。SDXL 系のピクセルアート LoRA も試しましたが、品質が実用に届きませんでした。
Codex CLI を ComfyUI ノードにする
決め手になったのは、Codex(ChatGPT のコーディングエージェント)に内蔵された画像生成ツールでした。単体テストで「本物のピクセルクラスタ・輪郭・キャラ同一性の維持」を確認できたため、ローカルの Codex CLI を子プロセスとして叩くカスタムノードを作りました。
# コンセプト(抜粋): codex exec を非対話モードで呼び、画像を添付して結果PNGを回収する
cmd = [codex, "exec",
"--skip-git-repo-check", "--ephemeral",
"-C", work_dir, # 作業フォルダ
"-s", "workspace-write", # サンドボックス
"-i", image_a_path, # 参照画像(後述)
"-i", image_b_path, # 変換対象
"-"] # プロンプトはstdinから
proc = subprocess.run(cmd, input=prompt.encode("utf-8"),
capture_output=True, timeout=timeout)
# → プロンプト内で「結果を result.png として保存せよ」と指示し、ファイルを回収する
ハマりどころ:
-
codex execは stdin がパイプだと入力を待ってハングします。プロンプトを-で stdin から渡して即クローズする方式で回避 - API キーは不要(ChatGPT サブスクのログインをそのまま使う)。ただしプランのレート制限を消費します。大量常用向きではなく、あくまで個人開発スケールの手法です
- seed がなく、毎回結果が変わります。この非決定性が後述の運用設計を要求します
一貫性の設計: リファレンス方式と「役割分離」プロンプト
フレームを1枚ずつ独立に変換すると、フレーム間でデザインが揺れます(武器の意匠、服の紋様、小物の有無がコマごとに別物になる)。対策は2段構えです。
① リファレンス方式: 毎フレームの変換時に、承認済みのドット絵1枚を「Image A(基準)」として一緒に添付し、「Image B(対象フレーム)を、Image A のスプライトセットの一員として変換せよ」と指示します。基準はモーション姿勢別に持ちます(立ちポーズ基準で攻撃フレームを変換すると精度が落ちる)。
② ポーズとデザインの役割分離プロンプト: これが最重要の学びです。当初「迷ったら Image A の見た目をコピーせよ」という強い追従指示を入れたところ、デザインは安定した代わりにポーズまで A からコピーされる事故が起きました(歩行アニメに直立ポーズが混入)。最終形は以下のような「何を A に従わせ、何を B に従わせるか」の明示分離です:
STRICT ROLE SPLIT (highest priority):
- Image B is the ONLY source of the POSE: body posture, limb positions,
leg stride, weapon angle. NEVER borrow the pose from Image A.
If Image B shows a mid-stride walking pose, your output MUST show
the same mid-stride walking pose, never a standing pose.
- Image A is the ONLY source of the DESIGN and rendering style:
pixel cluster size, outline thickness, palette, element designs.
- If you are unsure how a design detail should look, copy its DESIGN
from Image A — but always keep it at the pose dictated by Image B.
オプティカルフロー方式の使い分け
一貫性対策として「Frame0 だけドット絵化し、残りフレームは元動画のオプティカルフロー(RAFT)でドット絵のピクセルを物理的にワープさせる」方式も実装しました。
ピクセルが文字通り運ばれるので原理的に絵柄が揺れないIdle(微小な揺れ)では見事に機能し、本採用しています。
しかし AttackやWalk ではIdleと違ってキャラクターの姿勢が身体のパーツの位置が大きく変わるため、 この方法を利用するとIdleのポーズに引っ張られてAttackやWalkの正しい視線が反映されませんでした。(当たり前といえば当たり前)
「揺れ」るだけのアニメーションであればオプティカルフロー方式、そうでないのであれば素直に元の画像からドット絵変換したほうが良いでしょう。
サイズと位置の機械補正
ChatGPTに限らず、生成AIに「サイズを変えるな・位置を守れ」といくら指示しても、±数%のサイズ揺れや、酷いときはキャンバス比20%超の位置ズレが出ます。プロンプトでの抑制は限界があるので、変換後に元フレームとの bbox 比較で機械補正します。
- サイズ: キャラ bbox の高さ比のみでスケール補正。幅は髪の揺れや武器の角度で正当に変動するため、幅を混ぜると「幅+8%・高さ-8%」が平均0%に相殺されて補正がスキップされる罠があります(実際に踏みました)
- 位置: bbox 中心を元フレームの bbox 中心へ平行移動。元動画は真のモーション軌跡を持っているので、位置の正解は常にソース側にあります。足元アンカーのような姿勢仮定は空中ポーズで破綻するため、bbox 中心が安全です
Finalize: 「ドット絵風」を「本物のドット絵」にする
Codex の出力は「ドット絵風の高解像度画像」です(1ドットが4〜7ピクセルのクラスタで描かれている)。ゲームアセットには真の1ドット=1ピクセルが欲しいので、後処理で正規化します。
グリッドピッチの自動推定
「何%縮小すれば1:1になるか」は生成のたびに変わります(Codex の出力キャンバスサイズ自体が可変で、ドットピッチも 2.7px〜5.8px と揺れる)。決め打ちは不可能なので、画像からピッチを自動推定します。
- エッジ強度プロファイル(隣接画素差分の行/列平均)を取る
- その自己相関のピークからグリッド周期を推定(放物線補間でサブピクセル精度)
- 境界位置のエッジ加重ヒストグラムで位相(グリッドの開始オフセット)を推定
- 各セルの中心を nearest サンプリング → 真の1:1ドット画像
ハマりどころが2つ:
- 倍音への誤ロック: 自己相関はしばしば基本周期の2倍にもピークを持ち、ノイズ次第でそちらが僅かに勝ちます。X軸はピッチ2.8・Y軸は5.0にロックして縦だけ潰れた画像が出ました。対策は「グローバル最大」ではなく「最大値の85%以上の局所ピークのうち最小ラグ」を採る基本周期優先の選択と、X/Yピッチの強制統一(ドット絵の前提として正方ドット)
- 探索下限: キャンバスが小さいとピッチが探索範囲の下限を割り、基本周期が候補から消えて倍音しか残りません。下限は2.0まで開けておきます
透過は色キー、ただし「境界連結判定」つき
ドット絵の透過に AI セグメンテーション(RMBG系)は不向きでした。白背景と同色の武器装飾まで「背景」と誤解釈して透過してしまいます。採用したのは古典的な色キー抜き+2つの工夫です:
- 境界連結判定: 背景色に近い画素のうち「画像の縁に連結した領域」だけを背景とする。キャラ内部の白(髪飾り・ハイライト)が保護される
- 背景マスクの1px膨張: 生成画像のエッジは僅かにソフトで、しきい値に掛からない中間色が外周1pxに残ります。縮小しても白い斑点として残存するため、連結判定の後に1px膨張させて除去(順序が大事——先に膨張すると内部白を巻き込む)
クロップは「各フレームのアルファ最小枠」
納品スプライトは各フレームを自分のアルファ最小枠で切り抜きます。フレーム間の相対位置は失われますが(整列はUnity側のピボットで行う)、決定的な利点があります: ゴミドットが1つでも残っていればキャンバスが不自然に大きくなり、アセット段階で一目で発覚する。品質保証が前工程に移るのです。
妥協点: キャラの位置とサイズの統一はエンジン側に委ねた
正直に書くと、「位置とサイズを完全に揃えたアセット」は諦めました。ここは本パイプライン最大の妥協ポイントなので、項目として切り出して整理します。
「ズレ」には3つのレベルがあり、それぞれ扱いが違います。
| レベル | 何がズレるか | 対応 |
|---|---|---|
| ① フレーム内 | 生成AIが元フレームと違うサイズ・位置に描く | 機械補正で解決(前述の bbox 補正) |
| ② フレーム間 | アニメ再生時の各コマの相対位置 | アセットに持たせない(最小枠crop・Unityのピボットで整列) |
| ③ モーション間 | Idle と Walk でキャラのドット身長が違う |
一旦妥協 |
② フレーム間の整列: 意図的に捨てた
前節の通り、各フレームは自分のアルファ最小枠で切り抜くため、「キャラが画面のどこに立っていたか」という整列情報はアセットから消えます。
これは最終的にUnityアセットとしてはSpriteAtlasになるためこの情報は不要ですし、
「ジャンプして攻撃する」みたいなアニメーションの場合は画像認識で基準位置を特定出来ないため、どちらにしろ人間が調整する必要があります。
③ モーション間のサイズ: 揃えきれなかった
一方こちらは純粋な妥協です。Codex の出力はキャンバスサイズもドットピッチも実行ごとに揺れるため、1:1 正規化後のドット身長がモーションごとに変わります。実測では大半のモーションが身長190〜200ドットのクラスタに収まったものの、+12〜19% 大きいモーションと -12% 小さいモーションが残りました。このままアニメを切り替えると、キャラの見かけサイズがモーションの境目で跳ねます。
揃える手段は検討しましたが、どれも筋が悪いので一旦妥協しました。
ゲーム上でどうしても目立つようなら何か考えようと思います。
- パイプラインで基準身長に拡縮する → 1:1 ドット画像を非整数倍率でスケールするとドット格子が壊れる(1ドット=1ピクセルを守るなら整数倍しか許されず、±12% のような中途半端な補正は原理的に不可能)
- 外れ値モーションを再生成する → Codex は非決定的なので狙った身長が出るまでガチャになり、レート制限的にも品質的にも割に合わない
運用設計: 一貫性のない高価な画像生成AIとの付き合い方
Codex には seed がなく、実行のたびに結果が変わります。しかも ComfyUI の実行キャッシュはサーバー再起動で消えます。この組み合わせで「良い結果が出ていたのに、ノードを直して再起動→再実行したら全フレーム再抽選されて良品を失う」という事故を実際にやらかしました。対策として2つの設計原則に落ち着きました。
① 生成と後処理を同一ワークフローに同居させない
「高価で非決定的な工程(Codex生成)」と「無料で何度でもやり直したい工程(透過・正規化・クロップ)」を別ワークフローに分離します。生成物は必ずディスクに落とし、後処理はそれを読む。後処理のパラメータをいくら調整しても再抽選は起きません。
② フレーム単位の永続ディスクキャッシュ
生成ノード自体にも、(参照画像+対象画像+プロンプト)のハッシュ → 結果PNG の永続キャッシュを持たせました。効能は3つ:
- 途中失敗からの再開: レート制限で8フレーム中4フレーム目に落ちても、再実行時は成功済み3フレームをキャッシュから返し、残りだけ生成(消費済みクォータが無駄にならない)
- 再起動耐性: ComfyUI を何度再起動しても同一入力は同一結果
- 逆に意図的に再抽選したいときはキャッシュを消す(またはプロンプトを微修正する)という明示操作になる
量産アーキテクチャ: キャラ2人目を10分で立ち上げる
ワークフローが完成しても、キャラごとに全ワークフローを複製・修正するのでは破綻します。最終形は 「キャラ固有情報」と「共通処理」の分離です。
chars/alice.json ← キャラプロファイル(固有情報の正本)
├ lora: キャラLoRAファイル名
├ sd_identity: 静止画用タグ断片 ("トリガー語, 1girl, solo, holding scythe")
├ wan_identity: 動画用記述文 ("A chibi anime girl with silver hair...")
├ dot_style: ドット変換用の色・意匠規則
├ pose_templates / dot_reference / framed_keyframes: アセット参照
↓ ビルダースクリプトが一括生成
DI_Chara_Alice_IdleSE_Step1_CN.json ... (キャラ用ワークフロー29本)
↓ 処理本体は共通部品を参照
DI_Common_IdleSE_Step1_CN ... (サブグラフ=Blueprint 14部品)
- 共通部品はサブグラフ(Blueprint)として定義し、モデル・プロンプト定型文・cfg などの「文脈のないパラメータ」を焼き込む。キャラ固有情報(LoRA適用済みモデル、プロンプト断片、参照画像)は入力ソケットで外から受け取る
- プロンプトは
StringConcatenateで「キャラ断片(入力)+モーション定型文(焼き込み)」を合成。定型文にはshe/her/capeのような性別・服装語を書かない(キャラ非依存性の監査をスクリプト化して機械チェック) - サブグラフ定義は Python スクリプトを正本とし、ワークフロー JSON もライブラリ登録ファイルもすべてスクリプトから生成。ComfyUI の Blueprint は
user/default/subgraphs/*.jsonに置かれるだけなので、直接生成できます
フォルダ規約も量産前提で整理しました
-
input/Chara/{キャラ}/Common/= 人が用意した素材(ポーズ型・ドット基準・キーフレーム) -
output/Chara/{キャラ}/{モーション+方向}/Step{N}/= パイプライン生成物。各ワークフローは自分名義のフォルダにのみ書き込む(読み取りは自由)——生産者は消費者を知らない一方通行の依存 - 最終Stepの直下 = 納品物のみ。確認用(raw/透過フル/アニメwebp)はサブフォルダへ
コアのLoadImageはサブフォルダを読めない
ComfyUI コアの LoadImage は input 直下しか列挙・受理しません(combo検証で弾かれる)。階層フォルダ運用には「パス文字列で読む」自作ローダーが必要です。ついでに root(input/output)を選択式にすると、「素材=input / 成果物=output」という本来の意味論に沿った設計ができます。
設計原則まとめ
この開発を通じて確立した原則を列挙します。生成AIパイプライン全般に応用が利くはずです。
- 同一性はLoRA、構造はControlNet、変化はプロンプト、一貫性はリファレンス — 各手段の得意分野を混ぜない
- ポーズの正解とデザインの正解を分離して指示する — 参照画像への追従を強めるほどポーズ汚染のリスクが上がる
- 生成AIの出力は機械補正で締める — サイズ・位置はプロンプトで頼まず bbox で直す
- 数値検証は必要条件、最終判定は目視 — 平均誤差は回転破壊も黒領域のポーズ誤りも見えない
- 非決定的で高価な工程と、決定的で反復したい工程を分離する — 良品はディスクに落として資産化
- 書き込み所有権 — 各工程は自分名義のフォルダにのみ書く。デバッグと掃除が一気に楽になる
- 品質保証は前工程へ — 最小枠クロップのように「不良が見た目で分かる」形式を納品物に選ぶ
- 正本はスクリプト、UIは使い捨て — ワークフローJSONもBlueprintも生成物。手修正はしない
おわりに
ゲームキャラクターのアセットを生成する時に最初に躓くのが歩きアニメーションだと思っていたので、動画生成AIを経由してポーズを担保するというアイデアが実際に機能してよかったなと思います。
AIで生成する以上どうしても人間が目で品質を確認する手順が必要だったのですが、それもComfyUI上で実現出来てよかったです。
最終的にドット絵変換に逃げてしまいましたが、これはこれで学べることが多くやって良かったなと思います。
残課題は、妥協点の項に書いた Pixels Per Unit 補正が実機でどこまで通用するかの検証と、2キャラ目での再現性検証です。気が向いたらまた記事にします。
おまけ

