イベントグラフが Cast To ◯◯ ノードだらけになっている。最近ゲームの起動がやけに遅い。この2つには、同じ原因が隠れていることがあります。
Cast To は直感的で便利ですが、多用すると 見えない鎖 が増えていきます。この記事では、その鎖の正体と、鎖を断ち切る道具である Blueprint Interface の使い方を解説します。通信3方式の総論で「宛名のいらない手紙」と紹介したInterfaceを、今回はメモリとロード時間の観点まで含めて掘り下げます。
この記事でわかること
- Castの真の問題は速度ではなく ハードリファレンス(相手の荷物ごとロード)
- Interface= 実装の契約 。中身を持たず、依存も生まない
- 実践:Castだらけのダメージ処理を Interfaceに置き換える
Does Implement Interfaceの正しい使い方と、CastとInterfaceの使い分け
Castの真の問題:見えない鎖(ハードリファレンス)
まず誤解を1つ解いておきましょう。 Cast To の型チェック自体は高速 です。ポインタ比較程度の処理で、CPU負荷は無視できます。
本当の問題は、Cast To BP_Enemy と書いた瞬間に、そのBlueprintが BP_Enemy への ハードリファレンス(Hard Reference) を持つことです。

ハードリファレンスは鎖です。あなたのBlueprintがロードされるとき、鎖に繋がれた BP_Enemy も、BP_Enemy が参照するメッシュ・テクスチャ・サウンドも、 全部一緒にメモリへ引きずり込まれます(→ ハード/ソフトリファレンスの基礎)。参照が参照を呼ぶ「依存関係の爆発」が起きるわけです。これがCastの多用でロード時間とメモリを膨らませる仕組みで、いわゆる Blueprint Hard Reference問題 にあたります。
Blueprint Interfaceとは:実装の契約
Blueprint Interface は、機能の 契約(Contract) だけを定義するアセットです。C++の抽象インターフェースに相当します。

Interface自体は データも実装も持ちません 。「この契約を結んだアクターは、ApplyDamage という呼びかけに応答できる」というルールを定めるだけです。そのため、次の3つの利点が生まれます。
- Cast不要: 呼び出し側は相 手のクラスを知らずにメッセージを送れる
- 依存が生まれない: 契約書(Interface)への参照は軽く、相手の荷物を引きずり込まない
- 多態性: 同じメッセージに、スライムは点滅で、木箱は破砕で、タワーは崩壊で応える——反応は実装側の自由
実践:Castだらけのダメージ処理を置き換える
「プレイヤーの攻撃が当たった相手にダメージを与える」処理を題材に、Cast版からInterface版への引っ越しをやってみましょう。アクションの斬撃でも、シューティングの弾でも、この構図は同じです。
まずはBefore、つまりCast版を見てみましょう。よくあるのは次の形です。

悪い例:Castの階段
ヒットした相手 → Cast To BP_Enemy → 成功なら TakeDamage
→ 失敗 → Cast To BP_Crate → 成功なら Break
→ 失敗 → Cast To BP_Tower → ……(新しい壊せる物が増えるたびに階段が伸びる)
これをInterfaceで置き換えま す。
- 契約書を作る: コンテンツブラウザで右クリック → Blueprint → Blueprint Interface →
BPI_Damageableを作成。関数ApplyDamage(入力:DamageAmountFloat)を定義します - 契約を結ばせる:
BP_Enemyを開き、 Class Settings → Interfaces → Add でBPI_Damageableを追加。イベントグラフにEvent ApplyDamageが現れるので、そこに反応を実装します。 動作確認だけならPrint String("Enemy: 25ダメージ" など)で十分 です。本式の体力管理は 体力Componentの記事 の部品を挿せば、そのままComponentとInterfaceが合流します。BP_Crate・BP_Towerにも同じ契約を結ばせ、それぞれの壊れ方(木箱は破砕、タワーは崩壊)を書きます - 手紙を送る: プレイヤーの攻撃ヒット処理を、たった1ノードに置き換えます
良い例:Interfaceのメッセージ
ヒットした相手 → ApplyDamage(Message)(Damage Amount: 25.0)
Messageノード(封筒アイコン付き) は、相手が契約を結んでいれば実装を呼び、結んでいなければ 何もせず安全にスルー します。Castの成否分岐すら不要です。
仕上げに、効果を目で確かめましょう。プレイヤーのBlueprintを右クリック → Reference Viewer を開いてください。Cast版では BP_Enemy や BP_Crate への太い参照線が伸びていたはずが、Interface版では BPI_Damageable への細い線だけ になっています。この差が、その ままロード時間とメモリの差です。

参照線の本数は「つながり」の確認です。重さそのものを数字で見たいなら、 アセットを右クリック → Size Map を開くと、そのアセットが連れてくる参照先の合計サイズがMB単位で分かります。Cast版とInterface版でこの数字を記録して比べると、鎖を断った効果が実測で見えます。
ポイントは2つです。
- 「壊せる物」が増えても、攻撃側は無修正: 新しい破壊対象は契約を結ぶだけで仲間入りします。Castの階段を伸ばす作業はもう不要です
- Reference Viewerで検証する癖を: 疎結合化の効果は目に見えます。「参照線が減ったか」をビフォーアフターで確かめると、設計改善が実感に変わります
よくある間違いとベストプラクティス
| 間違い | ベストプラクティス |
|---|---|
| Interfaceに関数を詰め込みすぎる | 1つの目 的に特化させる(BPI_Damageable・BPI_Interactable のように分割) |
| Interface呼び出しの前にCastする | 実装チェックは Does Implement Interface ノードで。Castしたら本末転倒 |
| Interfaceにデータを持たせようとする | Interfaceは 機能の契約 。データは各アクターが持つ |
| 複雑な双方向のやり取りに使う | 「通知」が主目的ならEvent Dispatcherの方が適切なことが多い |

「相手が対応しているか事前に知りたい」場面(インタラクト可能ならUIに「調べる」を表示する等)では、Does Implement Interface で契約の有無を判定します。 調べるのは型ではなく契約 です。この一貫性が、疎結合を保ちます。
おまけ:先に知っておくと良いこと
- Messageノードは「不発でも安全」: 契約未実装の相手に送っても、エラーにならず静かに何も起きません。この寛容さは便利ですが、「実装したのに動かない」と感じたら、Class SettingsへのInterface追加と
Event ◯◯の実装忘れを疑ってください - C++でも同じ思想: C++では
IDamageableのようなInterfaceクラスを作り、ImplementsInterface()で判定します。Blueprintよりさらにオーバーヘッドが小さく、パフォーマンス最適化の定石です - Castが正解の場面もある: 自分の子クラス・同一機能グループ内など、 設計上すでに一心同体の相手 ならCastで構いません。ロード面でも、相手がどのみち常時ロードされるクラス(GameModeなど)ならハードリファレンスの実害はありません
- 鎖を断ってもまだ重いなら: Interface化で不要な連鎖ロードは消せますが、そもそも必要なアセット自体が大きい場合は別の手が要ります。ロードのタイミングを遅らせる 非同期ロードとソフト参照 が次の一手です
まとめ:CastとInterfaceの使い分け
| 項目 | Casting | Blueprint Interface |
|---|---|---|
| 依存関係 | ハードリファレンス発生(荷物ごとロード) | 契約への軽い参照のみ |
| 結合度 | 高い(特定クラスに固定) | 低い(契約にのみ依存) |
| 拡張性 | 相手が増えるたびCastが増える | 契約を結ばせるだけ |
| CPU速度 | 高速(ポインタ比較) | わずかに遅い(実害はほぼ無し) |
指針はシンプルで、 一心同体の相手はCast、種類が増えうる相手はInterface です。迷 ったら、Reference Viewerを開いて鎖の本数を数えてください。
Interfaceと対をなす「放送」の使いこなしはEvent Dispatcherの記事へ。Castを減らした先の設計全体像は、Blueprintグラフを整理する10のテクニックで仕上げましょう。
あなたのプレイヤーBlueprintのReference Viewerを今開いたら、何本の鎖が伸びているでしょうか。