【Unreal Engine】Game Instanceでデータを持ち越す:レベルを移っても消えない置き場所

作成: 2026-07-20

レベルを移動したらスコアが0になった。UE5でゲーム起動中ずっと生き残る唯一の入れ物がGame Instanceです。作り方とProject Settingsへの登録、Get Game Instance → Castの読み書き、Init/Shutdown、置いていいものと悪いもの、Save GameやSubsystemとの違いまで。

ステージ1でコインを30枚集めて、扉に入る。次のステージが始まった瞬間、画面のカウントが0に戻っています。変数はちゃんとPlayerに持たせたはずなのに、値だけがどこかへ消えています。

消えたのではなく、その Player自体が作り直されている のがこの現象の正体です。UE5でレベルをまたいで生き残る入れ物は1つだけで、それが Game Instance です。この記事では、Game Instance Blueprintの作り方とProject Settingsへの登録、Get Game Instance からの読み書き、InitShutdown の使いどころ、そして置いていいものと悪いものを解説します。

3つのレベルの箱を1本の帯が縦に貫き、その帯の上にスコアが乗っているイメージ

この記事でわかること

  • レベルを移ると 何が作り直され、何が生き残るのか
  • Game Instance Blueprintの作り方と、 忘れると何も起きないProject Settingsの登録
  • 読み書きの定番 Get Game InstanceCast To
  • Init / Shutdown はいつ呼ばれるのか
  • Game Instanceに 置いていいもの・置いてはいけないもの
  • Save GameSubsystem との役割の違い
  • 実践: 3ステージ通しのスコアとクリア記録を持ち越す

Sponsored

生き残るのはGame Instanceだけ

まず、それぞれの入れ物が いつからいつまで生きているのか を時間軸で見ます。

ゲーム起動から終了までの時間軸に、GameInstanceの帯だけが端から端まで伸び、Actor・Widget・GameMode・PlayerStateの帯はレベルごとに切れている図
入れ物生きている期間レベルを移ると
Game Instanceアプリの起動から終了までそのまま残る
GameMode / GameStateレベルの開始から終了まで作り直される
PlayerState同上作り直される
Character・配置したActor同上破棄される
Widget追加してから破棄まで破棄される
Level Blueprintの変数そのレベルの中だけ消える

Game Instanceだけが別格です。 最初のレベルが読み込まれるより前に作られ、アプリを閉じるまで1つのまま存在し続けます。 レベルの外側にいるので、レベルが入れ替わっても影響を受けません。

逆に言うと、Game Instance以外のどこに変数を置いても、レベルをまたいだ時点で初期値へ戻ります。「PlayerStateに入れておけば大丈夫だと思った」という詰まり方が多いのですが、PlayerStateもレベル単位の入れ物です(→ レベル遷移の記事)。

判断はこの一言で決まります。 Playを止めるまで覚えていてほしい値は、Game Instanceに置く。

作って、登録する

手順は3つで、 3つ目を忘れると何も起きません

コンテンツブラウザでBlueprint Classを作り、ALL CLASSESからGameInstanceを選び、Project SettingsのGame Instance Classへ登録する3手順の図

1. Blueprint Classを作る

コンテンツブラウザで右クリック → Blueprint Class 。親クラスの選択ダイアログで ALL CLASSES を開き、検索欄に GameInstance と入力して選びます。よく使うクラスのボタンには並んでいないので、検索から選んでください。名前は BP_GameInstance にします。

2. 持ち越したい変数を追加する

開いて、My Blueprintパネルから変数を足します。初期値は Class Defaults で設定します。Actorと違い、レベルに置かれるものではないので、詳細パネルの Instance Editable は使いません。

3. Project Settingsで登録する

Edit > Project Settings > Project > Maps & Modes を開き、 Game Instance カテゴリの Game Instance ClassBP_GameInstance に設定します。

この3つ目が本記事で最も事故の多い箇所です。 登録していないと、エンジンは既定の GameInstance を使い続けます。 自作クラスは作られないので、次の節で書くCastが毎回失敗し、変数の読み書きが丸ごと素通りします。エラーも警告も出ません。

「Game Instanceを作ったのに値が保持されない」と感じたら、コードを疑う前にここを開いてください。

補足: 設定はプロジェクト単位です。変更したらPlayを一度止めて、開き直してから確認してください。実行中のPIEには反映されません。

Sponsored

読み書きはGet Game InstanceからCast

どのBlueprintからでも、2ノードで届きます。Actorでも、Widgetでも、Level Blueprintでも同じです。

Get Game InstanceからCast To BP_GameInstanceを通り、Set Total Scoreへつながるノードグラフ
Get Game Instance
  → Cast To BP_GameInstance
      As BP Game Instance →
        Set Total Score(Value: Total Score + 10)

Get Game Instance が返すのは基底の Game Instance 型なので、そのままでは自分で足した変数が見えません。 Cast To BP_GameInstance を挟むと、変数と関数が並びます。

Castの失敗ピンをつないでおくと、登録忘れにその場で気づけます。

Get Game Instance
  → Cast To BP_GameInstance
      Cast Failed → Print String("Game Instance Class が未設定です")

Castが重いのではないかと気になるかもしれませんが、ここでは心配は要りません。 BP_GameInstance はProject Settingsで指定されている時点で必ず読み込まれている ため、Castによって余計なアセットが引きずられることがないからです。Castが問題になるのはどういう場合かはCast と Blueprint Interfaceの記事にまとめています。

それでも Get Game Instance → Cast の2ノードを毎回書くのが煩わしくなったら、次の2つの手があります。

  • Game Instance側に関数を作る: AddScore(Amount) のような関数をGame Instance Blueprintに用意すると、呼ぶ側は「Cast → 関数を1つ呼ぶ」で済みます。加算のルールが1箇所にまとまるので、後から「2倍イベント中は倍率をかける」といった変更も1箇所で終わります
  • Subsystemへ移す: Subsystemには専用のGetノードが用意されるため、Castが不要になります(後述)

InitとShutdown

Game Instance Blueprintのイベントグラフで右クリックすると、 Event InitEvent Shutdown を追加できます。

起動→Level A→Level B→終了の時間軸上で、Initが起動時に1回、Shutdownが終了時に1回だけ呼ばれ、レベル遷移では呼ばれないことを示した図
イベント呼ばれるタイミング回数
Initゲームの起動時。 最初のレベルが読み込まれる前1回だけ
Shutdownゲームの終了時1回だけ

レベルを移動しても呼ばれません。 ここがActorの Event BeginPlay との決定的な違いです。BeginPlayはレベルが変わるたびに走りますが、Initは起動時の1回きりです。

エディタで確認するときは、 Playを押した時にInit、Playを止めた時にShutdownが走る と考えてください。PIEのセッションごとにGame Instanceが作り直されるためです。

Initに書くと良いのは、次のような「起動時に1回だけやること」です。

  • セーブデータの存在確認と、あればロード
  • 音量やキーコンフィグなど、設定値の読み込み
  • 乱数シードの決定

ただし1つ注意があります。 Initの時点では、レベルもプレイヤーもまだ存在しません。 Get Player CharacterNone を返します。プレイヤーに触る処理は、Initではなくレベル側のBeginPlayに置いてください。

Sponsored

置いていいもの・悪いもの

Game Instanceは便利なので、置き場所に困ったものが全部集まってきます。ここで1つだけ、はっきりした線があります。 参照は置かない。値を置く。

左:ActorやWidgetへの参照はレベルが変わるとNoneになる。右:IDや数値はそのまま残る、という対比図
置いていい(値)置いてはいけない(参照)
スコア、所持金、残機(Integer / Float)プレイヤーCharacterへの参照
クリア済みステージのID(Name の配列)レベルに置いたActorへの参照
所持アイテムのIDと個数(Map)表示中のWidgetへの参照
難易度や選択キャラのEnumComponentへの参照
音量・キーコンフィグの設定値そのレベルにしか無い座標や部屋番号

理由は単純で、 参照先が次のレベルには存在しないから です。レベル遷移で破棄されたActorを指していた変数は None になり、そこから関数を呼ぶと Accessed None のエラーになります。

やっかいなのは、 同じレベル内でテストしている間は動いてしまう ことです。遷移して初めて壊れるので、原因に気づくのが遅れます。持ち越したいのが「あの敵」なら、Actor参照ではなく その敵を再現できるID を置いてください。

もう1つ、置きすぎにも線を引いておきます。Game Instanceは1つしかないので、変数を足せば足すほど、無関係な機能が同じクラスに同居します。 「レベルをまたぐか」で判断して、またがないものは入れない のが、後で読める状態を保つコツです。

Save Gameとの違い

よく混同されますが、担当している範囲がまったく違います。

Game InstanceSave Game
どこにあるメモリディスク上のファイル(.sav
生きている期間アプリを閉じるまで削除するまで
レベル遷移残る残る
アプリの再起動消える残る
読み書きの手間変数を直接触るだけ箱に詰めて書き出し、読んでCast

Game Instanceは「今回のプレイ中の記憶」、Save Gameは「次回に渡す記録」 です。競合するものではなく、組み合わせて使います。

よくある形は次のとおりです。ゲーム中の加算はGame Instance上で行い、区切りの良いところでSave Gameへ書き出す。起動時のInitで読み戻してGame Instanceへ入れる。こうすると、ゲーム中の処理は毎回ファイルを触らずに済みます。

Sponsored

Subsystemとの使い分け

GameInstance Subsystem は、Game Instanceと まったく同じ寿命 を持つ入れ物です。起動時に作られ、レベル遷移で壊れず、終了時に消えます。

違うのは、 置き方の粒度 だけです。

Game InstanceGameInstance Subsystem
何であるか持ち越すための1つの置き場所機能ごとに分ける仕組み
プロジェクトに1つ機能の数だけ作れる
作成Blueprintだけで作れるC++が必要
取得Get Game Instance → Cast専用のGetノード(Castなし)
生存期間起動から終了まで同じ

判断はこの順です。

  1. まずGame Instanceで始める 。Blueprintだけで完結し、変数を1つ足せば動きます
  2. スコア・音量・セーブ管理・実績のように 無関係な機能が同居し始めたら 、機能ごとにSubsystemへ切り出す

つまり、この記事で扱っているのが 置き場所 、Subsystemの記事で扱っているのが その置き場所を機能ごとに分ける仕組み です。先にGame Instanceで動く形を作ってから、必要になった段階でSubsystemへ移す順番で構いません。移すときも、Game Instance側に関数としてまとめてあれば、中身をそのまま持っていけます。

実践:3ステージ通しのスコアを持ち越す

ステージクリア型アクションの累計スコア、ローグライクの1周の稼ぎ、パズルゲームの連戦記録。 「複数のレベルを通した合計」 は、ジャンルを問わず必要になります。ここでは3つのステージを通してスコアを足し込み、どのステージをクリアしたかも記録します。

動かすとこうなる

3つのステージのゴールに順に触れると、スコアが 120 → 200 → 350 と積み上がり、リザルトに合計とクリア数が出ます。

Stage01で120、Stage02で+80して200、Stage03で+150して350になり、リザルトでTOTAL:350 CLEARED:3と表示される流れ図

再現条件

Third Person テンプレート で新規プロジェクトを作り、次を用意します。

レベル4つFile > New Level > Basic で作成し、Content/Maps へ保存。それぞれ Player Start を置き、World Settingsの GameMode OverrideBP_ThirdPersonGameMode にします)

レベル名置くもの
L_Stage01BP_StageGoal を1つ
L_Stage02BP_StageGoal を1つ
L_Stage03BP_StageGoal を1つ
L_Resultなし

BP_GameInstance(親クラス: GameInstance)の変数

変数名初期値
TotalScoreInteger0
ClearedStagesName( Array(空)

作成後、 Edit > Project Settings > Project > Maps & Modes > Game Instance ClassBP_GameInstance に設定します。

BP_StageGoal(親クラス: Actor)

要素設定
Box CollisionBox Extent 100 / 100 / 100 、Collision Preset OverlapAllDynamic
変数 StageID(Name / Instance Editable既定値 Stage01
変数 StageScore(Integer / Instance Editable既定値 0
変数 NextLevel(Level(Object Reference)/ Instance Editable未設定
変数 IsTransitioning(Boolean)false

Instance Editable にするのが要点です。1つの BP_StageGoal を3つのレベルに置いて、 配置した個体ごとに 詳細パネルから値を変えます。

配置先StageIDStageScoreNextLevel
L_Stage01Stage01120L_Stage02
L_Stage02Stage0280L_Stage03
L_Stage03Stage03150L_Result

Game Instance側:ClearStage関数

BP_GameInstance の Functions に ClearStage を新規作成し、入力ピンを2つ足します。

入力ピン
StageIDName
ScoreInteger
ClearStage関数のノードグラフ。Add UniqueでClearedStagesへ追加し、TotalScoreにScoreを加算して、結果をPrint Stringで出す
関数: ClearStage(入力: StageID / Name, Score / Integer)

  → Add Unique(Target: ClearedStages, New Item: StageID)
  → Set Total Score(Value: Add(A: Total Score, B: Score))
  → Print String(Append: "TOTAL: " + Total Score を文字列化)

Add ではなく Add Unique を使っています。同じステージをやり直したときに、クリア記録が二重に積まれないようにするためです。 Add Unique は、すでに同じ値が入っていれば何もせずインデックスだけを返します。

スコアの加算はここに集約しておきます。 TotalScore を書き換える場所を1つに固定しておくと 、後で「どこかで勝手に0にされている」を探し回らずに済みます。

ゴール側

BP_StageGoal のBox Collisionで On Component Begin Overlap を取ります。

BP_StageGoal
On Component Begin Overlap(Box)
  → Cast To BP_ThirdPersonCharacter(Object: Other Actor)
  → Branch(Condition: IsTransitioning)
      True  → (何もしない)
      False ↓
  → Set IsTransitioning(true)
  → Get Game Instance
  → Cast To BP_GameInstance
      As BP Game Instance →
        ClearStage(StageID: StageID, Score: StageScore)
  → Open Level (by Object Reference)(Level: NextLevel)

ClearStageOpen Level より前に呼ぶ ところが重要です。Open Level の後ろにつないだノードは、今のレベルが消える直前に走ることになり、順序に依存した処理が壊れます(→ レベル遷移の記事)。

IsTransitioning のBranchは二重反応の防止です。オーバーラップは1フレームに複数回入ることがあり、これが無いと加算が2回走ります。

リザルト側

L_Result のLevel Blueprintで読み出します。

Event BeginPlay
  → Get Game Instance
  → Cast To BP_GameInstance
      As BP Game Instance →
        Print String(Append: "TOTAL: " + Total Score, Duration: 10.0)
        → Length(Target: Cleared Stages)
        → Print String(Append: "CLEARED: " + 結果, Duration: 10.0)

確認する

L_Stage01 を開いてPlayし、ゴールに触れて3ステージを通してください。

  • ゴールに触れるたび、画面に TOTAL: 120TOTAL: 200TOTAL: 350 と出る
  • L_ResultTOTAL: 350CLEARED: 3 が10秒間表示される
  • Output Logで Init のログを出しておくと、 Playの開始時に1回だけ 出る。レベルを移っても出ない

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

  • リザルトが常に0、途中のPrint Stringも出ない → Project SettingsのGame Instance Classが未設定。既定のGameInstanceが使われていて、Castが失敗している
  • 各ステージの表示は正しいのに、リザルトだけ0 → リザルト側でGame Instanceではなく、そのレベルのLevel Blueprint変数を読んでいる
  • TOTAL: 240 のように加算が倍になるIsTransitioning のBranchが無く、オーバーラップが2回走っている
  • CLEARED が3より多いAdd Unique ではなく Add を使っている。同じステージを再クリアした分が積まれる
  • ゴールに触れても反応しない → Box Collisionの Collision Preset が OverlapAllDynamic になっていない
  • 次のレベルへ行かない → 配置した個体の NextLevel が未設定(Instance Editable がオフだと、そもそも設定欄が出ない)

ここで一度、 Project Settings の Game Instance Class を空に戻してPlayしてみてください。 ゴールに触れても何も表示されず、加算も一切起きません。Castが静かに失敗するだけで、エラーは出ません。この挙動を一度見ておくと、「作ったのに効かない」に出くわしたとき、真っ先に正しい場所を開けるようになります。

ポイントは2つです。

  • 書き換える場所を関数1つに閉じる: TotalScore を外から直接 Set せず、ClearStage を通してのみ変更しています。どこからでも触れる置き場所ほど、入口を狭くしておく価値があります。倍率イベントや実績判定を足すときも、この関数の中だけで済みます
  • 持ち越すのは値であって、参照ではない: 記録しているのは NameInteger だけです。「クリアしたゴールActor」を配列に入れたくなりますが、次のレベルでは全部 None になります。IDで持って、必要ならData Assetから実体を引き直してください

この合計を次回の起動にも残すならセーブ/ロードの記事へ、リザルト画面をPrint Stringから本物のUIにするならUMGの記事へ続きます。

Sponsored

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

  • Game Instanceには見た目が無い: Actorではないので、レベルに配置することも、位置を持つこともできません。Componentも追加できない前提で設計してください。「持ち回りたい機能」がComponentの形をしているなら、それはComponentとしてプレイヤー側に挿し、値だけをGame Instanceへ預けるほうが素直です
  • Tickは使わない方向で考える: Game Instanceで毎フレームの処理を回すと、レベルの状態と関係なく走り続けます。時間の計測が必要なら、値の更新はイベント側から呼ぶ形にしてください(→ Tickを減らす設計
  • リトライ時の初期化を忘れない: 2周目のために TotalScore を0へ戻す処理が要ります。置き場所はタイトル画面側です。リザルト側で戻すと、リザルトが自分の表示する値を消してしまいます
  • パッケージ版とエディタで挙動が違って見えることがある: エディタではPlayを止めるたびにGame Instanceが作り直されますが、パッケージ版ではアプリを閉じるまで残り続けます。「エディタでは毎回リセットされるのに、製品版では前回の値が残っている」という差はここから来ます(→ パッケージングの記事
  • デバッグは値を出して確認する: Game Instanceの変数はレベル上のどのActorにも属さないため、World Outlinerから選んで詳細パネルで覗くという確認ができません。要所に Print String を置くか、Output Logへ出す習慣をつけると追いやすくなります(→ Print Stringとログの記事

まとめ

  • レベルを移ると Actor・Widget・GameMode・PlayerStateは作り直される 。生き残るのは Game Instance だけ
  • 作る手順は3つ。3つ目の Project Settings > Maps & Modes > Game Instance Class への登録 を忘れると、何も起きずエラーも出ない
  • 読み書きは Get Game InstanceCast To 。Castの失敗ピンに Print String を挿しておくと登録忘れに気づける
  • Init は起動時に1回だけ 。レベル遷移では呼ばれず、その時点ではプレイヤーもレベルも存在しない
  • 置くのは 。Actor参照やWidget参照は、次のレベルで None になる
  • Game Instanceは今回のプレイ中の記憶、Save Gameは次回へ渡す記録 。機能ごとに分けたくなったらSubsystemへ

いま作っているゲームで、タイトルに戻るまで覚えていてほしい数値はいくつありますか。その一覧が、そのまま BP_GameInstance の変数リストになります。