【Unreal Engine】World Partitionとレベルストリーミング:読み込む量を減らす

作成: 2025-12-12最終更新: 2026-07-20

UE5では広いマップはWorld Partitionが既定で、手動のLevel Streamingは屋内切り替えなどの用途に残ります。グリッド分割の仕組み、One File Per Actorと常駐アクター、Data Layersでの出し分け、Load Stream Levelの使い方、屋外から屋内へレベルを切り替える実践まで解説。

マップを広く作り込んでいくと、ある時点でエディタの起動が遅くなり、プレイ開始までの待ち時間が伸びていきます。原因ははっきりしていて、 レベル全体を一度にメモリへ読み込んでいる からです。プレイヤーが立っているのはマップの片隅なのに、反対側の街も森も全部メモリに載っています。

解決の方向は1つです。 必要な範囲だけを読み込み、離れたら捨てる 。UE5にはこれを自動でやる World Partition と、自分のタイミングで切り替える Level Streaming の2つがあります。この記事では、UE5における2つの現在地と使い分け、そして屋外から屋内へレベルを切り替える実装までを解説します。

広いマップのうち、プレイヤーの周囲のグリッドだけが色づき、遠くのグリッドが空白になっている俯瞰図

この記事でわかること

  • UE5の現在地: 広いマップはWorld Partitionが既定
  • World Partitionの仕組み( グリッドに区切って近くだけ読む
  • Data Layers で昼夜やイベントごとに出し分ける
  • 手動の Level Streaming が今も有効な場面
  • 実践:屋外マップから 建物内へ入るときにレベルを切り替える

Sponsored

なぜ分割するのか:DrawではなくメモリとCPUの話

前の記事 LOD・カリング・オクルージョン で扱ったのは、 レベルに置いてあるものを描かない 技術でした。カリングで除外されたオブジェクトは描画されませんが、 メモリには載ったまま です。

レベルに読み込まれた全アクターの中で、描画されるのはごく一部だという入れ子の図。カリングは描画を減らすがメモリは減らない

読み込んだままだと、次のコストが残ります。

残るコスト内容
メモリメッシュ・テクスチャ・マテリアルがすべて常駐する
CPUTickするアクターは画面外でも動き続ける
初期ロード時間プレイ開始前に全部を読み終える必要がある
エディタの重さレベルを開くたびに全アクターを読み込む

ストリーミングは、この4つに効きます。 カリングが「描かない」なら、ストリーミングは「持たない」 。担当する領域が違うので、どちらか一方では代わりになりません。


UE5の現在地:World Partitionが既定

ここは古い情報が多く出回っている部分なので、先に整理します。

UE4では、大きなマップを扱う方法は World Composition(タイル状のレベルを並べる)か、 手動のLevel Streaming(サブレベルを自分でロードする)でした。どちらもレベルを人の手で分割する前提です。

UE5.0で World Partition が導入され、この前提が変わりました。 レベルは1つのまま扱い、分割はエンジンが自動で行います 。World Compositionは非推奨となり、大きなマップの標準的な作り方はWorld Partitionになりました。

UE4のWorld Composition/手動Level StreamingとUE5のWorld Partitionを並べた対比図。前者は人が分割し、後者はエンジンが自動で区切る

新規レベルを File → New Level から作るとき、 Open World または Empty Open World テンプレートを選ぶと、World Partitionが有効な状態で作られます。BasicEmpty Level では有効になりません。

テンプレートWorld Partition
Open World有効(地形とライティングつき)
Empty Open World有効(中身は空)
Basic無効
Empty Level無効

そして重要な制約が1つあります。 World Partitionが有効なレベルでは、従来のLevelsパネルによるサブレベル管理は使えません。 Window → Levels で永続レベルにサブレベルを足していく操作は、World Partitionのレベルでは行えません。

その代わりに使うのが Level Instance(レベルをアクターのようにレベル内へ配置する仕組み)と、次に説明する Data Layers です。 両方式を1つのレベルで混ぜることはできない ので、マップを作り始める前にどちらで行くかを決めます。

Sponsored

World Partitionの仕組み

World Partitionは、レベルを グリッド(格子) に区切り、 プレイヤーの周囲のセルだけを読み込みます

グリッドに区切られたマップ上で、プレイヤーを中心にLoading Rangeの円が重なるセルだけが読み込まれている図

動きは単純です。

  1. レベル全体を、一定サイズのセルに区切る(既定は一辺 12,800uu=128メートル)
  2. 各アクターは、自分の位置するセルに属する
  3. プレイヤーを中心に Loading Range(既定 25,600uu=256メートル)の範囲を取る
  4. その範囲にかかるセルだけを読み込み、外れたセルは捨てる

設定は World SettingsWorld Partition Setup から確認できます。セルサイズを小さくすると読み込みが細かくなり、メモリは減りますが切り替えの頻度が上がります。まずは既定値のままで進めて、メモリが足りなくなってから調整するのが順当です。

動いているところを見る

グリッドと読み込み状況は、プレイ中に可視化できます。コンソールで次を実行してください。

wp.Runtime.ToggleDrawRuntimeHash2D

画面に俯瞰のグリッドが表示され、 読み込まれているセルが色つき、そうでないセルが暗く 表示されます。歩き回ると、プレイヤーを追いかけてセルが入れ替わっていくのが見えます。 World Partitionが実際に何をしているかは、この画面を1分眺めるのがいちばん早い理解です。

もう一度同じコマンドを打つと消えます。

エディタ側の読み込み

World Partitionのレベルでは、エディタも同じ考え方で動きます。レベルを開いたとき、既定では アクターが読み込まれていません 。ビューポートで作業したい範囲を World PartitionエディタWindow → World Partition)で選び、右クリックから Load Selected Cells で読み込みます。

「レベルを開いたのに何も表示されない」というのは、故障ではなく この仕様 です。巨大なマップを開いても数秒で立ち上がるのは、これが理由です。


Actorの扱いが変わる:OFPAと常駐アクター

World Partitionを使うと、レベル内のアクターの 保存され方 も変わります。ここはGitでの共同開発と、常駐させたいものの扱いに直結します。

One File Per Actor(OFPA)

World Partitionのレベルでは、レベルに配置した各アクターのデータが 1つずつ別々のファイル に保存されます(__ExternalActors__ フォルダ)。従来は1つの巨大な .umap に全アクターが詰まっていましたが、World Partitionではアクターごとにファイルが分かれます。これはエディタ上・ソース管理向けの保存方式で、パッケージ化(Cook)のときにはレベルへまとめられます。

従来は1つのumapに全アクターが入るが、OFPAではアクターごとに別ファイルになる様子を対比した図

これが効くのは 複数人での開発 です。1つの .umap を全員で奪い合う代わりに、別々のアクターを触っている限りファイルがぶつからないので、競合が減ります。ただしアクターが大量の小さなファイルになるので、Gitで管理するなら .gitignoreとLFSの設定 を整えておくのが推奨です。

常に読み込んでおきたいもの

World Partitionは距離でアクターを出し入れします。ところが、距離に関係なく常に居てほしいもの があります。ゲーム全体を管理するマネージャ、スコアを持つアクター、BGMを鳴らすアクターなどです。これらが遠くへ行くたびにアンロードされると困ります。

こうしたアクターは、詳細パネル(World Partitionカテゴリ)の Is Spatially Loaded をオフにします。すると、そのアクターはグリッドの距離ストリーミングの対象から外れ、常に読み込まれた状態 になります。GameModeやGameStateは、そもそもレベルに配置せずプレイ開始時に生成されるので対象外ですが、自作のマネージャActorをレベルに置くときは、これを忘れると「たまに消えるマネージャ」になって、原因の分かりにくいバグにつながります。


Data Layers:距離以外の条件で出し分ける

グリッドは 距離 で読み込みを決めます。ですが、距離では表せない出し分けもあります。昼と夜で街の様子が変わる、ストーリーの進行で瓦礫が増える、イベント中だけ特設ステージが現れる。こうしたものです。

同じ街のグリッドに対して、昼レイヤーと夜レイヤーが差し替わるイメージ図

Data Layers は、アクターを距離とは別の軸でグループ化し、まとめて読み込み状態を切り替える仕組みです。Window → World Partition → Data Layers から作成し、レベル内のアクターを割り当てます。

Data Layerには2種類あります。

種類用途
Editor Data Layerエディタでの表示・非表示の整理だけ。ゲームには影響しない
Runtime Data Layer実行中に読み込み状態を切り替えられる

ゲーム中に切り替えたいなら Runtime Data Layer を作ります。実行時の状態は3つです。

状態意味
Unloaded読み込まれていない
Loadedメモリにあるが、ゲームには参加していない
Activated読み込まれ、ゲーム内で動いている

Blueprintからは Set Data Layer Runtime State ノードで切り替えます。Loaded を経由してから Activated にすると、 読み込みを先に済ませておいて、必要な瞬間に一気に出す ことができます。イベント開始と同時にカクつくのを避けたいときに効く手です。

Sponsored

手動のLevel Streamingが有効な場面

World Partitionが既定になった今も、 手動のLevel Streamingは消えていません 。得意分野が違うからです。

場面向いている方式
広い屋外を歩き回るWorld Partition(距離で自動)
建物・ダンジョンへの出入り手動 Level Streaming
ストーリー進行でエリアを差し替える手動 Level Streaming または Data Layers
ロード画面を挟んだ完全なマップ移動Open Level(ストリーミングではない)

判断の軸ははっきりしています。 切り替えの条件が「距離」ならWorld Partition、「出来事」なら手動 です。ドアをくぐった、スイッチを押した、ボスを倒した。こうした瞬間に切り替えたいものは、グリッドでは表現できません。

手動方式では、常駐する 永続レベル(Persistent Level) に、出し入れする サブレベル をぶら下げます。

  1. サブレベルにしたいレベルを別途作成する(例:SubLevel_Interior
  2. 永続レベルを開き、Window → Levels でレベルパネルを開く
  3. Levels → Add Existing でサブレベルを追加する
  4. サブレベルを右クリックし、Change Streaming Method → Blueprint を選ぶ

Blueprint にしておくと、 自分で Load Stream Level を呼ぶまで読み込まれません 。もう1つの Always Loaded は常時読み込みで、ストリーミングとしては働きません。

繰り返しになりますが、この手順は World Partitionが無効なレベルでのみ 使えます。


実践:屋外から建物内へレベルを切り替える

RPGで宿屋に入る、ホラーゲームで屋敷の玄関をくぐる、探索アクションでダンジョンの入口へ降りる。 「外から中へ入ると、中身が読み込まれる」 はどのジャンルでも出てくる形です。ここではその1本を、ドアの手前のトリガーから最後まで組みます。

プレイヤーがドア前のトリガーに入ると屋内アセットが読み込まれ、離れると消える様��子を左右に並べた図

再現条件を作る

この実践は、World Partitionが無効なレベル(Basic テンプレート)で行います。 World Partitionのレベルでは Levels パネルが使えないためです。

項目設定
永続レベルFile → New Level → Basic で作成し、L_Outdoor として保存
サブレベル同様に作成し、SubLevel_Interior として保存
サブレベルの中身家具や小物のStatic Meshを 1,200個 配置(1個あたり三角形2,000程度)
永続レベル側建物の外観と、その入口に Box Collision を1つ
トリガーのサイズBox Extent = X:200 / Y:400 / Z:150(入口の前を覆う大きさ)

ステップ1:サブレベルを登録する

L_Outdoor を開き、Window → Levels でレベルパネルを開きます。

Levels → Add Existing... から SubLevel_Interior を選びます。追加されたら 右クリック → Change Streaming MethodBlueprint を選択します。

この時点でプレイすると、屋内の1,200個は 読み込まれていません

ステップ2:トリガーにActorを用意する

新しいActor Blueprint(BP_LevelStreamTrigger)を作り、コンポーネントに Box Collision を追加します。詳細パネルで Box Extent を上記の値にし、Collision PresetsOverlapAllDynamic にします。

変数を1つ作ります。

変数名既定値
TargetLevelNameNameSubLevel_Interior

型を Name にしておくのは、Load Stream LevelLevel Name ピンが Name 型だからです。ここを変数にしておけば、同じBlueprintを別の建物にも使い回せます。

ステップ3:ロードを組む

BP_LevelStreamTrigger のイベントグラフに、次のグラフを組みます。

On Component Begin OverlapからCast To Characterを経てLoad Stream Levelへ繋がるノードグラフ。Should Block on LoadはFalse、Completedピンから通知処理へ伸びる
BP_LevelStreamTrigger(イベントグラフ)

On Component Begin Overlap (Box)
  → Other Actor から Cast To Character
  → (成功)Load Stream Level (by Name)
       Level Name          : TargetLevelName
       Make Visible After Load : True
       Should Block on Load    : False
  → Completed → Print String "Interior loaded"

On Component End Overlap (Box)
  → Other Actor から Cast To Character
  → (成功)Unload Stream Level
       Level Name          : TargetLevelName
       Should Block on Unload : False

Load Stream Levelレイテントノード です。ノードの右下に時計のアイコンが付いていて、実行してもすぐには終わりません。読み込みが終わったときに Completed ピンが発火します。ドアを開けるアニメーションや到着通知は、このピンから繋いでください。

Should Block on LoadFalse にしているのが要点です。True にすると読み込みが終わるまでゲーム全体が停止し、1,200個ぶんの読み込み時間だけ画面が固まります。ロード画面を挟む場面以外では False です。

作った BP_LevelStreamTrigger を、L_Outdoor の建物入口にドラッグして配置します。

ステップ4:測って確かめる

Standalone Game で起動し、コンソールで stat levels を実行します。読み込まれているレベルと、その状態(Loaded / Visible / Unloaded)が一覧で出ます。

建物から離れた場所では、SubLevel_Interior は一覧に現れないか Unloaded です。ここで stat unit を見ます。

Frame:   8.20 ms
Game:    2.10 ms
Draw:    7.80 ms
GPU:     6.40 ms

トリガーに入ると、Print StringInterior loaded が表示され、stat levelsSubLevel_InteriorVisible になります。屋内へ入って測ると、

Frame:  11.60 ms
Game:    2.40 ms
Draw:   11.10 ms
GPU:     8.20 ms

Draw が 7.80ms から 11.10ms へ増えています。 これは失敗ではありません。 屋内の1,200個ぶんが読み込まれ、実際に描かれている状態です。重要なのは、 建物の外にいる間はこの3.3ミリ秒を払っていなかった ことです。建物が10棟あれば、常時全部を読み込む構成との差は10倍になります。

トリガーから出ると SubLevel_InteriorUnloaded へ戻り、Draw も 7.80ms 付近に戻ります。

うまくいかないときの切り分けです。

  • Load Stream Level が何も起こさないLevel Name が実際のアセット名と一致していません。パスではなく アセット名だけSubLevel_Interior)を入れてください
  • レベルパネルに Change Streaming Method が出ない → そのレベルはWorld Partitionが有効です。Basic テンプレートで作り直してください
  • 読み込まれたのに何も見えないMake Visible After LoadFalse です。True にするか、別途 Make Visible を呼びます
  • 入った瞬間に画面が固まるShould Block on LoadTrue になっています
  • トリガーに反応しないBox CollisionCollision Presets を確認してください。OverlapAllDynamic になっている必要があります

ポイントは2つです。

  • トリガーはドアより手前に置く: 読み込みには時間がかかります。ドアの真上にトリガーを置くと、開けた瞬間はまだ空の部屋です。 プレイヤーが数秒歩く距離だけ手前 に置いて、猶予を作ってください
  • アンロードは慎重に: プレイヤーがドアの前で行ったり来たりすると、読み込みと破棄が繰り返されます。End Overlap から直接アンロードせず、Delay を数秒挟むか、アンロード用のトリガーをロード用より大きく作るのが定石です

読み込み中の処理を Completed ピンから組み立てる考え方は、Event Dispatcherによるイベント駆動設計 の「起きたことを通知する」発想と地続きです。読み込んだアクターの初期化を疎結合に書きたいときに効いてきます。

Sponsored

おまけ:先に知っておくと良いこと

Level Instanceを先に覚えておくと得です。 「同じ建物を10棟置きたい」ときに、レベルを1つ作ってアクターのように配置できる仕組みです。World Partitionのレベルでも使えて、1つ編集すれば全部に反映されます。Blueprint Component で部品を作り回すのと同じ発想を、レベル単位でやるものだと考えてください(→ Blueprint Componentで機能を使い回す)。

World PartitionのHLODは自動で生成されます。 遠くのセルが読み込まれていない間、そこには何も表示されません。これでは地平線が空白になるので、World Partitionは HLOD(複数アクターをまとめた簡略メッシュ) を用意して遠景を埋めます。ビルドは Build → Build HLODs から行います。遠くの景色が抜けているときは、まずこれを疑ってください。

アンロードしても即座にメモリが空くとは限りません。 ガベージコレクションが走るまでは保持されたままです。stat levelsUnloaded になっていてもメモリが減らない場合、それはこの遅延です。異常ではありません。

Open Level はストリーミングではありません。 完全に別のマップへ移る操作で、現在のレベルは全部破棄されます。タイトル画面からゲーム本編へ、といった場面で使うものです。シームレスな移動には向きません。ロード画面の挟み方や、破棄されるデータの受け皿は Open Levelとロード画面、消えるデータの受け皿 で扱っています。

エディタでの計測はあてになりません。 ストリーミングの体感はディスクの速度に大きく左右されます。エディタはアセットをキャッシュしているので実際より速く見えます。実機に近い数字が要るなら、パッケージ化ビルドで測ってください(→ stat unitで重さの原因を特定する)。


まとめ

方式使いどころ切り替えの条件
World Partition広い屋外マップ。UE5の既定距離(自動)
Data Layers昼夜、進行度、イベント状態Set Data Layer Runtime State
手動 Level Streaming屋内、ダンジョン、エリア差し替え出来事Load Stream Level
Open Levelタイトルからゲーム本編へ完全なマップ移動

押さえておきたいのは3点です。

  • UE5で広いマップを作るなら、まずWorld Partition。 Open WorldEmpty Open World テンプレートで新規レベルを作れば有効になります
  • World Partitionと従来のサブレベル管理は併用できない。 マップを作り始める前にどちらかを選びます
  • 手動のLevel Streamingは今も現役。 距離で表せない切り替え(ドアをくぐる、イベントが起きる)は、こちらの担当です

そして計測との関係です。ストリーミングが効くのは主に メモリと初期ロード時間 で、stat unitDrawGPU にすぐ現れるとは限りません。効果を確かめるときは stat levels で読み込み状態を見て、その上で stat unit の変化を見る。この2段構えで判断してください。

これで「測る → 描く量を減らす → 読み込む量を減らす」の3本が揃いました。最初に戻って stat unit を打ったとき、いちばん大きかった数字はどれだったでしょうか。次に手を入れるべき場所は、そこから決まります。

さらに学ぶために