図鑑には100種類のアイテムを登録したけれど、画面に出すのは選んだ1個だけ。その1個を見るために、まだ選んでいないモデルまで読み込む必要はあるでしょうか。
こうした読み込みを整理するのが ソフト参照 です。アセットの場所を覚えておき、使うことが決まってから読み込みます。さらに 非同期ロード を使えば、完了を待つ間も、ほかのゲーム処理を進められます。
この記事では、近づくと仮の箱が円錐へ変わる、小さな展示物を作ります。まず「後から読み込み、届いた結果で見た目を変える」流れをつかみ、その後で自分のゲームの重いモデルへ応用してみましょう。
この記事でわかること
- ハード参照とソフト参照で、読み込むタイミングがどう変わるか
- Async Load Assetの完了を受け取り、メッシュを差し替える手順
- 読み込み中の重複要求と、失敗した場合の扱い
- 参照関係と、実行中のメモリを分けて調べる方法
アセットの「場所」を覚えておく
参照 は、使いたい相手を指定するためのものです。たとえばStatic Mesh型の変数に円錐のアセットを設定すると、その変数から同じ円錐を指定できます。ここでいうアセットは、メッシュや画像、音など、コンテンツブラウザに保存された素材です。
ハード参照は、読み込みがつながる
Blueprintのメッシュ変数を Object Reference にし、初期値に特定のアセットを保存すると、アセットへの ハード参照 になります。そのBlueprintを読み込むと、必要な参照先も一緒に読み込まれます。
このつながりは、参照先のさらに先へ続くことがあります。展示物のBlueprintがメッシュを使い、メッシュがマテリアルを使い、そのマテリアルがテクスチャを使う、といった関係です。表示候補を全てハード参照で持つと、今は選ばな い候補まで読み込む原因になります。

一方、Soft Object Reference(ソフト参照) は、アセットの場所である パス を記録します。たとえば /Game/AsyncDemo/Meshes/SM_AsyncDemo という住所を覚えておき、その参照だけではメッシュを読み込みません。
| 比べる点 | ハード参照 | ソフト参照 |
|---|---|---|
| アセットを保存した側を読み込むと | 参照先も読み込まれる | その参照だけでは参照先を読み込まない |
| アセットを使うとき | 読み込まれた相手を指定する | ロードを要求し、結果を受け取って使う |
| 選ぶ目安 | 最初から使う、待たせたくないもの | 出番が後、または使うか未定のもの |
ソフト参照は、読み込みをなくす仕組みではなく、読み込む時期を選ぶ仕組みです。 別のBlueprintやレベルが同じメッシュをハード参照していれば、そちらから読み込まれることもあります。変数1個をソフト参照にしただけで、必ず未読込になるわけではありません。
ObjectとClassは何を選ぶかが違う
今回は「円錐のメッシュ」という素材を読み込 むので、Static MeshのSoft Object Referenceを使います。これに対し、後から敵のBlueprintを読み込んでSpawnしたい場合は、Soft Class Reference を使います。Classは「どの種類のActorを作るか」という設計図を指定するものです。
| 後から使いたいもの | 変数の参照の種類 | 読み込みノード |
|---|---|---|
| メッシュ、画像、音などのアセット | Soft Object Reference | Async Load Asset |
| SpawnするActorなどのBlueprintクラス | Soft Class Reference | Async Load Class Asset |
クラスを読み込むことと、Actorを生成することは別の処理です。後者ではロード完了後のClassを Spawn Actor from Class へ渡します。
非同期は、完了を後から受け取る
同期ロード は、読み込みが終わるまで、その場で待ってから次の処理へ進む方式です。Load Asset Blocking がこれに当たります。大きなアセットをプレイ中に読むと、1フレームが長くなり、画面が引っかかる原因になります。
非同期ロード は、読み込みを頼み、結果を後から受け取る方式です。Async Load Asset では、完了したときに Completed の実行出力から続きを動かします。

| Async Load Assetのピン | 役割 |
|---|---|
| Asset | 読み込みたいソフト参照を渡す |
| 上側の通常の実行出力(Out) | 完了を待たずに続ける処理。今回の実践では使わない |
| Completed | 読み込みの結果を使う処理へ進む |
| Object | 読み込めたアセットを受け取る。失敗時は有効な結果にならない |
メッシュを差し替える処理は Completedの先 に置きます。上側の実行出力につなぐと、まだ結果が届いていないうちに使おうとしてしまいます。
待つ長さは、アセットの大きさ、実行環境、すでに読み込まれているかで変わります。すぐ使える場合もあれば、時間がかかる場合もあります。「何秒待つ」と決め打ちするより、Completedを合図にするのが確実です。
なお、非同期にしても処理の負荷は残ります。読み込み後に大きなモデルを初期化したり、一度に多くの表示を更新したりすれば、そこで重くなることもあります。待ち方を変えたうえで、実際の重さも測る と考えてください。
実践:近づくと箱が円錐へ変わる展示物
ここからはBlueprintのThird Personテンプレートを使います。展示物の近くに入ると読み込みを始め、成功したら箱を円錐へ差し替えます。読み込めなければ箱を残し、画面へ失敗を表示します。

円錐は軽いので、目に見える待ち時間や性能差は出なくても構いません。まずは、完了の通知から正しく見た目を変更できることを確かめます。
読み込む素材を用意する
コンテンツブラウザの設定で「Show Engine Content」をオンにし、/Engine/BasicShapes の Cone を見つけます。これをプロジェクト側の Content/AsyncDemo/Meshes へコピーし、SM_AsyncDemo と命名してください。フォルダへドラッグしたときは、移動ではなくコピーを選びます。
この複製を使うと、後でReference Viewerや計測結果を調べるとき、自分の検証用メッシュを名前で探せます。SM_AsyncDemo をレベルへ直接置く必要はありません。
展示物と、近づいたことを調べる範囲
親クラスをActorにして BP_AsyncDisplay を作ります。Components欄で、DefaultSceneRootの子に次の2つを追加してください。
| 名前 | コンポーネント | 設定 |
|---|---|---|
DisplayMesh | Static Mesh | Mesh = /Engine/BasicShapes/Cube、Location = (0, 0, 50)、Mobility = Movable、Collision Presets = NoCollision |
LoadZone | Box Collision | Location = (0, 0, 100)、Box Extent = (200, 200, 150)、Collision Presets = OverlapOnlyPawn、Generate Overlap Events = オン |

DisplayMeshは、画面に形を出す部品です。最初はCubeを割り当て、読み込みが済むまでの仮表示にします。Movable はゲーム中に変更できる設定、Overlap はぶつかって止める代わりに「範囲が重なった」と通知する仕組みです。
Box Extentは箱の中心から各面までの距離です。今回の範囲は幅400cm・奥行き400cm・高さ300cmになります。範囲に入る前の箱も見えるよう、Player Startから少し離して配置します。
ソフト参照と、2つの状態を用意する
「My Blueprint」で次の変数を作ります。TargetMeshは型の選択で Static Mesh を検索し、参照の種類を Soft Object Reference にしてください。Static Mesh Componentとは別の型です。

| 変数名 | 型 | 初期値 | 意味 |
|---|---|---|---|
TargetMesh | Static MeshのSoft Object Reference | SM_AsyncDemo | 後から読み込むメッシュの場所 |
bBusy | Boolean | false | 今、読み込みを待っている |
bApplied | Boolean | false | 読み込んだメッシュを適用できた |
変数を作ったらCompileし、TargetMeshの初期値でSM_AsyncDemoを選びます。今回は1種類だけ試すので、Instance Editableはオフのままで構いません。
DisplayMeshに最初からSM_AsyncDemoを割り当てない こともポイントです。TargetMeshをソフト参照にしていても、表示用コンポーネントへ直接設定すれば、そちらがハード参照になります。
近づいたら、読み込みを頼む
Event Graphで右クリックし、「Add Custom Event」から RequestDisplayMesh を作ります。入力は不要です。このイベントが、読み込み処理の入口になります。
今回は Event Graph上のCustom Event で組みます。Async Load Assetのように完了を後から受け取る処理を、通常のFunctionの中へ入れようとしないでください。
プレイヤーが入ったときだけ呼ぶ
LoadZoneを選び、Detailsのイベント欄から On Component Begin Overlap を追加します。Other Actorを Get Player Character(Player Index = 0)のReturn Valueと Equal (Object) で比べ、結果をBranchのConditionへつなぎます。

同じOverlapイベントからBranchへ白い実行線をつなぎ、True側から RequestDisplayMesh を呼びます。先ほどのEqual (Object)のReturn Valueを、このBranchのConditionへつなぎます。TargetはSelfです。Selfは「このBP_AsyncDisplay自身」を指します。False側は何もつなぎません。

これで、テンプレートのプレイヤーが入ったときだけ、自分の読み込みイベントへ進みます。今回は1人用の実験です。イベント名と同じ名前の呼び出しノードを検索して置き、赤いCustom Eventの入口と区別してください。
読み込み中と、適用済みを除く
RequestDisplayMeshの入口からBranchを置きます。bBusyとbAppliedのGetを OR Boolean へ入れ、その結果をConditionへつなぎます。

OR は、どちら かがtrueならtrueを返します。読み込み中、または適用済みなら、BranchのTrue側で何もせず終えます。False側を Set bBusy(true)へつなぎ、続いて Print String で Loading... と表示します。

bBusyは、待っている間に同じ要求を重ねないための印です。bAppliedは、成功した後に同じ差し替えを繰り返さないための印です。「頼んだ」と「できた」を別々に記録しておけば、失敗した場合の扱いも分かりやすくなります。
届いたメッシュを適用する
Print Stringの後に Async Load Asset を置き、TargetMeshのGetをAssetへ接続します。上側の通常の実行出力は使わず、Completedから Set bBusy(false)へつないでください。

Completedへ進んだら、その要求の待ち時間は終わりです。ただし、それだけで 目的のメッシュを使えるとは限りません。 結果を調べてから差し替えます。
成功した場合は、表示用の部品へ渡す
Cast to Static Mesh を追加します。Async Load AssetのObjectをCastのObjectへ、Set bBusyの実行出力をCastの実行入力へつなぎます。
ここでの Cast は、「受け取った相手をStatic Meshとして使えるか」という確認です。別のメッシュへ変換したり、このノードがアセットを新たに読み込んだりするわけではありません。結果が空ならCast Failedへ進みます。

Cast成功側の実行出力から Set Static Mesh を呼びます。Components欄のDisplayMeshをグラフへドラッグし、その参照をTargetへ。CastのAs Static Meshは、New Meshへつないでください。

Targetは「見た目を変える部品」、New Meshは「その部品に表示させる素材」です。どちらも青い線ですが、指定する相手が違います。
Set Static Meshの後に Set bApplied(true)、さらに Print String(Ready)をつなぎます。DisplayMeshが読み込んだアセットを使い続けるので、表示に必要な参照もここで保持されます。
失敗した場合は、箱を残す
Cast Failedは Print String へつなぎ、Load failed と表示します。Set Static Mesh もSet bAppliedも通りません。最初の箱が残り、bBusyとbAppliedは両方falseになります。
これならプレイヤーが一度範囲を出て、再び入ったときに読み込みを試せます。ただし、アセットを未設定にしたままでは、試し直しても失敗します。原因を直す必要があります。
処理全体は次の形です。分割した図は、全て同じEvent Graphの続きです。
RequestDisplayMesh(Custom Event)
→ Branch(bBusy OR bApplied)
True → 何もしない
False → Set bBusy(true)
→ Print String(Loading...)
→ Async Load Asset(Asset = TargetMesh)
通常の実行出力 → 未接続
Completed → Set bBusy(false)
→ Cast to Static Mesh(Object = ロードのObject)
成功 → Set Static Mesh
Target = DisplayMesh
New Mesh = As Static Mesh
→ Set bApplied(true)
→ Print String(Ready)
Cast Failed → Print String(Load failed)
成功と失敗を確かめる
BlueprintをCompileして保存し、BP_AsyncDisplayをレベルへ1個置きます。床の上に箱が見え、Player StartがLoadZoneの外になるようにしてください。距離は500cmほど離すと試しやすくなります。
- Playし、まず箱が見えることを確認します。
- 箱へ近づきます。
Loading...の後にReadyが出て、箱が円錐に変われば成功です。 - 範囲から出て、もう一度入ります。適用済みなので、Loading...を繰り返さないことを確認します。
- Playを止め、TargetMeshの初期値を
NoneにしてCompileします。もう一度Playして近づくと、Load failedが出て箱が残ることを確認します。 - 再び範囲を出入りし、失敗後にも要求できることを確認します。最後にTargetMeshをSM_AsyncDemoへ戻して保存します。
小さな円錐や読込済みの素材なら、ほとんど待たずに差し替わります。それでも問題ありません。差し替えが見えたことは、Completedから表示を更新できた確認です。未読込だったことや、省メモリの証明とは分けて考えます。

| 症状 | 確認する場所 |
|---|---|
| 最初から円錐になっている | DisplayMeshの初期メッシュ。ハード参照だけで見た目が勝手に変わるわけではない |
| Loading...が出ない | LoadZoneのOverlap設定、プレイヤー側のGenerate Overlap Events、Player Startが範囲外か |
| Loading...の後に進まない | CompletedからSet bBusyへつながっているか |
| Load failedになる | TargetMeshの初期値、アセットが存在するか、パッケージへ含まれているか |
| Readyは出るが見た目が変わらない | Set Static MeshのTargetがDisplayMeshか、New MeshがCastの結果か、MobilityがMovableか |
| 2回目以降に失敗を試せない | 新しいPlayで試しているか。成功後はbAppliedで要求を止める例になっている |
Overlapの調べ方はコリジョンの記事、ログの読み方はPrint StringとOutput Logの記事も参考になります。
本当に読み込む量が減ったか調べる
ここからは、完成したゲームへ応用するときの確認です。先ほどの小さな円錐だけでは、差が小さくても自然です。確認する道具を、答えてくれる疑問で分けます。
