開発ノウハウ

Blueprint→C++変換で実際に踏んだ誤検知——「_C」サフィックスという小さな罠

gracon_papa

Blueprint→C++変換のツールを自作していると聞くと、「変換ロジックを作るのが大変そう」と思われがちです。しかし実際に手を動かしてみると、本当に神経を使うのは変換そのものより、「変換結果が正しいかどうかをどう機械的に検証するか」でした。この記事では、その検証ツール(バリデーター)に自分で仕込んだ「型不一致検出」チェックが、正しく変換されたコードを誤って警告してしまった実際のバグと、その原因・修正・そこから得た教訓を共有します。

先に結論を書きます。検証ツールを作るということは、検証ツール自身の検証が必要になるということでした。完璧なチェックツールを作ったという体裁は取りません。むしろ、実データで動かして初めて気づいた誤検知をそのまま記録しておくほうが、同じ道具を作ろうとしている人の役に立つはずです。

前提:型不一致検出チェック(E1)とは何か

このツールのバリデーターは、Blueprintの解析JSON(変数・関数・依存関係などの構造化データ)と、生成されたC++コードを突き合わせて、複数のカテゴリのチェックを機械的に実行します。そのうちの1つが「E1: 変数型不一致検出」です。

やっていることはシンプルです。

  • Blueprint側の変数定義から、型のカテゴリ(bool・int・object・structなど)と型の詳細パスを読み取る
  • 生成されたC++コード側のUPROPERTY宣言から、実際に使われている型名を正規表現で抜き出す
  • 両者を突き合わせ、bool型のはずがFStringになっている、といった不一致があればWARNを出す

bool・int・floatのようなプリミティブ型は対応表で単純比較できますが、BPアセット(他のBlueprintクラスやウィジェット)を参照する変数は少し複雑です。Blueprint側の型情報には「型の詳細パス」(どのアセットを指しているかのフルパス)が入っているので、そこからアセット名の短縮形を取り出し、C++側の型名と部分一致するかどうかで判定します。

何が起きたか:正しい変換にWARNが出た

実際のBlueprint(ここでは「ポップアップ管理クラス」と呼びます)を対象に、変換からバリデーターの実行までを一気通貫で試したときのことです。このクラスは3つの変数でBPアセットを参照していました。

  • ポップアップ表示用ウィジェット
  • 画面構成ファクトリー参照
  • トースト通知ウィジェット

これらはいずれも、C++側では対応するクラス名の型として正しく変換されていました。ところが検証を実行すると、この3変数すべてにE1のWARNが出ました。

[WARN] E1: 変数 "PopupWidgetRef" の型が一致しません
  期待される型(Blueprint側): PopupWidget系アセット
  実際の型(C++側): UPopupWidget*

一見すると型は合っています。UPopupWidget*という宣言は、誰がどう見ても「ポップアップ表示用ウィジェット」を正しく参照するC++コードです。それなのに、なぜバリデーターは不一致だと判定したのでしょうか。

原因:UE5の生成クラス命名規則「_C」

調査の結果、原因はBlueprint側の「型の詳細パス」の末尾にありました。UE5では、Blueprintアセットからコンパイル時に生成される実行時クラスの名前に、慣例として_Cというサフィックスが付きます。つまり、Blueprint側の解析データに入っている型パスは、次のような形になっていたのです。

/Game/UI/WBP_Popup.WBP_Popup_C

バリデーターは、このパスの末尾(WBP_Popup_C)をそのままアセット名として扱い、C++側の型名(UPopupWidget)と部分一致するかを見ていました。ところが開発者がC++で書く型名は、慣習的に_Cを含みません。UEのBlueprint生成クラス特有の内部命名規則を、開発者が普段C++コードを書く感覚でそのまま持ち込むことはまずないからです。つまり、

  • Blueprint側の型パス末尾: _Cあり(UE5の生成クラス命名規則に従う)
  • C++側で開発者が書く型名: _Cなしが通常

という食い違いが常に発生し、BPアセット参照型の変数は正しく変換していても機械的には必ず不一致と判定される状態になっていました。UE内部の命名規則を知らなければ絶対に気づけない類の落とし穴です。

修正:「_C」のあり/なし両方を許容する

修正の方針はシンプルです。BPアセット参照型のアセット名を比較する際、末尾の_Cを取り除いた形と、取り除かない形の両方でC++側の型名と照合するようにしました。

修正前(「_C」込みの名前でしか一致しない)

# typeObjectPath: "/Game/UI/WBP_Popup.WBP_Popup_C"
short_name = "WBP_Popup_C"  # そのまま比較 → C++側 "UPopupWidget" とは絶対に一致しない

修正後(「_C」の有無を許容して比較)

# typeObjectPath: "/Game/UI/WBP_Popup.WBP_Popup_C"
short_name = "WBP_Popup_C"
short_name_without_suffix = "WBP_Popup"  # "_C" を末尾から除去した形も候補に追加

# C++側の型名と、どちらか一方でも部分一致すればOK
is_match = (short_name in cpp_type) or (short_name_without_suffix in cpp_type)

この修正により、3変数のWARNはすべて解消しました。あわせて回帰テストを追加し、「BPアセット参照型で「_C」サフィックス付きの型パスが渡された場合に、「_C」なしのC++型名でも一致と判定される」ケースを固定化しています。今後、型パスの扱いに手を入れる際にこのケースが壊れれば、テストが即座に検知してくれます。

教訓:検証ツールも検証が要る

このバグの厄介なところは、「チェックロジックのコード自体にバグがあったわけではない」という点です。ロジックは書いた通りに正しく動いていました。問題は、UE5の内部命名規則というチェックの設計時には想定していなかった前提を、実データで動かして初めて発見できたことです。

単体テストだけを書いて満足していたら、このバグには気づけなかったはずです。テスト用に自分で用意したダミーデータには、当然ながら自分が想定した形式の型パスしか登場しないからです。実際のBlueprintプロジェクトのデータで一気通貫の検証を回して、初めて「あれ、正しいはずなのに警告が出る」という違和感に行き着きました。

バリデーターを作る目的は「変換ミスを人の目に頼らず機械的に見つけること」でした。しかし今回わかったのは、バリデーター自身も、実データでの検証というもう1段階のチェックなしには信用できないということです。検証ツールを作ったら、その検証ツールを検証する工程を必ず組み込む——当たり前のようで、実際にバグを踏んで初めて身にしみた教訓でした。

まとめ

  • Blueprint→C++変換の検証ツールに実装した「型不一致検出(E1)」が、正しく変換されたBPアセット参照型の変数を誤って警告する不具合を起こした
  • 原因は、BlueprintのアセットパスがUE5の生成クラス命名規則で末尾に「_C」を持つのに対し、開発者が書くC++の型名は「_C」なしが通常という食い違い
  • 修正は「_C」あり/なし両方の名前で照合するという単純な対応だが、実データで一気通貫の検証を回さなければ発見できなかった
  • 検証ツールを作ることは、検証ツール自身の検証が必要になることでもある

Blueprint→C++変換ツールの「検証」機能は、実はここで紹介した以外にも実データで初めて気づいた不具合がいくつかありました。次回は、変換元のデータ形式が変わった際にチェックロジックが暗黙のうちに機能しなくなっていた、別の落とし穴についても紹介する予定です。

関連記事:この検証の前提になっている変換作業自体の実体験は「UE5「Blueprint→C++変換」の本当の壁は”置き換え手順”じゃなく”依存関係の芋づる”だった」で、UE6でのBlueprint廃止方針の全体像は「UE6でBlueprintは廃止される?」で解説しています。

ABOUT ME
記事URLをコピーしました