【Unreal Engine】UMGが重い・フォーカスが飛ぶ——選択位置の制御とパフォーマンス最適化

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

ゲームパッドで選択位置が迷子になる、UIを開くと重い——UMGの二大トラブルを、Navigation設定とInvalidation Box/Retainer Boxで解決する方法を図解。ポーズメニューを直す実践つき。

UIが形になってくると、次の2つの壁がほぼ同時にやってきます。 ゲームパッドで選択位置が変なところに飛ぶ ことと、 UIを開いた瞬間にフレームレートが落ちる こと。どちらも「作っているときは気づかず、仕上げの段階で表面化する」やっかいな問題です。

この記事では、 フォーカス (いまどのウィジェットが入力を受けているか)を自分で制御する方法と、UIが重くなる原因と対処を解説します。最後は ポーズメニューを実際に直す ところまでやります。

用語の整理: この記事で言う フォーカス は「入力を受け付けている対象」で、 マウスカーソル とは別物です。ゲームパッドの方向キーで動いているのはフォーカスであり、マウスを乗せたときの見た目の変化(Hovered)ともまた違います。

ゲームパッドの選択位置が意図しない場所へ飛んでいるイメージと、重くなったUIのイメージ

この記事でわかること

  • フォーカスが飛ぶのは 自動ナビゲーションの推測が外れる から
  • Set User Focus が効かないときに 確認すべき4点
  • UIが重い原因は 自分で書いた処理描画 のどちらか
  • Invalidation Box =計算のキャッシュ、 Retainer Box =テクスチャに焼く
  • 実践:ポーズメニューの フォーカスと負荷を両方直す

Sponsored

フォーカスが飛ぶ理由

フォーカス とは「いま入力を受け付けているのは、どのウィジェットか」を表す状態です。マウスだけなら意識しなくても動きますが、 ゲームパッドやキーボードで操作した瞬間 に、この概念が前面に出てきます。

ボタンが並んだメニューで、��下を押したときにフォーカスが隣ではなく遠くのボタンへ飛んでしまう様子の図

既定では、UMGは 押した方向に近いウィジェットを自動で探して フォーカスを移します。この推測がよく効くのは、ボタンが素直に縦や横へ並んでいるときだけです。

推測が外れるのは、たとえばこんなときです。

  • ボタンの間に 見えないコンテナ が挟まっていて、位置関係が想定とずれる
  • 非表示(Collapsed / Hidden)のウィジェット が混ざっていて、飛び先の候補が変わる
  • グリッド状に並んでいて、「下」がどれを指すのか一意に決まらない

つまり、フォーカスが飛ぶのはバグではなく 推測に任せた結果 です。だから直し方も明確で、 推測をやめさせて、行き先を自分で指定する ことになります。

Navigationを明示して止める

操作させたいウィジェット(Buttonなど)を選ぶと、詳細パネルの下のほうに Navigation という項目があります。上下左右それぞれに、移動の仕方を指定できます。

Escape・Explicit・Stopの3つのモードで、下キーを押したときの挙動が変わる様子の図
モード動き使いどころ
Escape(既定)自動で近いウィジェットを探す素直に並んだシンプルなメニュー
Explicit移動先を1つ指名する順序を保証したいメニュー全般
Custom移動先を関数で決めるグリッド移動など特殊なルール
Stopその方向へは移動しない一覧の端、閉じたくないダイアログ

複数のボタンが並ぶメニューなら、 全ボタンの上下を Explicit で指名する のが結局いちばん早い解決策です。「一番下のボタンの下は、一番上のボタン」と指定すればループもできます。

もう1つ、忘れると必ずハマるのが 最初にどこへフォーカスを当てるか です。

Set User Focus は、Viewportに載せた後で呼ぶ。 ウィジェットがまだ画面に存在しない状態で呼んでも効きません。Event Construct の中で呼ぶと失敗することがあるので、 Add to Viewport を実行した後 に呼びます。

それでも当たらないときの確認は4点です。 ① 対象が表示されているかCollapsed / Hidden では当たりません)、 Is Focusable が有効か③ Owning Playerが合っているか④ 後続の処理でフォーカスを上書きしていないか

Event BeginPlay(Player Controller)
  → Create Widget(Class: WBP_PauseMenu)
  → Set MenuRef
  → Add to Viewport(Target: MenuRef)
  → Set User Focus(Target: MenuRef の StartButton, Player Controller: Self)  ← 載せた後

UIが重くなる仕組み

UMGの負荷は、大きく2種類に分かれます。ここを混同すると、間違ったところを最適化して時間を溶かします。

Blueprintのロジックが走るコストと、画面に絵を描くコストの2種類を分けて示した図
  1. ロジックのコスト: Event Tick、Binding関数、その中の Cast や検索処理——つまり 自分が書いた処理
  2. 描画のコスト: ウィジェットの数だけ発生する、レイアウト計算と描画。すなわち UMG側の仕事

順番として、 まず1番から疑う のが効率的です。自分で書いた処理は原因を特定しやすく、直した効果もはっきり出ます。

  • Event Tick に処理を書いている: UIのTickはゲームが動いている限り走り続けます。表示していない間も動くことがあるので、そもそも使わないのが安全です
  • Bindingに重い処理を入れている: Bindingは表示中ずっと呼ばれます。中で Get All Actors Of Class のような検索処理をしていたら、それが延々と走ります

対処はどちらも同じで、 値が変わったときだけ更新する 形に変えることです(→ Event Dispatcherで疎結合な設計を作る)。

2番の描画コストが効いてくるのは、 ウィジェットが大量にある とき(インベントリのスロット、長いリスト)や、 大きな半透明領域・複雑なマテリアル を使っているときです。数の目安は解像度や中身で変わるので、「何個から」ではなく 測って判断 します。

Sponsored

Invalidation BoxとRetainer Box

名前が似ていて混同されがちですが、 キャッシュしているものが違います

Invalidation Boxは計算結果を覚えておく仕組み、Retainer Boxは絵をテクスチャに焼く仕組みという対比図
Invalidation BoxRetainer Box
何をキャッシュするか描画に必要な 計算結果描いた結果の テクスチャ
効くコストレイアウト計算・描画準備描画そのもの(ドローコール)
メモリ小さい大きい(テクスチャを持つ)
できること更新の省略更新の省略 + マテリアルを掛けられる
向いているもの中身が変わらない複雑なUI半透明が重なる重いUI、ぼかしなどの効果

Invalidation Box は「中身が変わっていないなら、計算し直さなくていいよね」という仕組みです。子ウィジェットのプロパティが変われば自動でキャッシュが捨てられ、次の描画で作り直されます。

  • 効くもの: 表示しっぱなしで、たまにしか変わらない複雑な階層(ステータス欄、装備一覧)
  • 効かないもの: テキスト1個のような単純なウィジェット。 毎フレーム変わるもの (キャッシュを捨て続けるので、むしろ損)

Retainer Box は「一度描いた絵をテクスチャに焼いて、それを貼る」仕組みです。強力ですがメモリを食うので、 Invalidation Boxで足りないとき か、 UIにマテリアル効果を掛けたいとき に限定します。Phase CountPhase を設定すると更新頻度を落とせます(例: Phase Count = 3 でおおよそ3フレームに1回)。ただし子からInvalidationの要求が来れば、そのタイミングでも更新されます。

2つは上位互換ではありません。 Invalidation Boxは CPU側の再計算と描画準備を省く 道具、Retainer Boxは 描画結果をテクスチャ化して更新頻度を落とし、マテリアルも掛けられる 道具です。目的が違うので、用途が一致するほうを選びます。

ただし着手の順番はあります。 ① Tick/Bindingを直す → ② 不要なウィジェットを減らす → ③ 用途に応じてBoxを使う 。①②を飛ばしてBoxから入ると、効果の小さい対処に時間を使うことになります。


実践:ポーズメニューを直す

アクションのポーズ画面、RPGのステータス画面、サバイバルゲームのクラフトメニュー——「開いている間、ゲームが止まっているのに重い」UIはどのジャンルにもあります。ここでは典型的なポーズメニューを、 フォーカスと負荷の両方 直します。

開閉のフローそのものや Trigger When Paused の詳細は ポーズメニューの実装 が本編です。この記事ではポーズ画面を題材に、フォーカスと負荷の直し方に集中します。

再現用の準備: まず、Enhanced Inputでポーズのキーを用意します(→ Enhanced Input入門)。

種類名前設定
Input ActionIA_PauseDigital (Bool)。 Trigger When Paused にチェック(後述)
Input Mapping Context既存のものに追加IA_PauseEsc などを割り当て
Widget BlueprintWBP_PauseMenu下の構成
BP_PlayerController の変数MenuRefWBP_PauseMenu 型のObject Reference

WBP_PauseMenu の中身は、 わざと自動ナビゲーションが外れる配置 にします。

要素配置
ResumeButton / OptionButtonHorizontal Box に横並び(画面上段)
QuitButtonその下に単独で配置(画面下段・少し右寄り)
ItemCountText所持アイテム数を表示( ここに重いBindingを仕込みます

ItemCountTextTextBindingを作り、その中で Get All Actors Of Class(BP_Item) して個数を数える 。これが「デバッグ用に書いて、そのまま忘れた」典型です。

症状の確認: Playしてポーズを開き、ゲームパッドの方向キーを押してみてください。 どこにもフォーカスが当たっていない はずです(Set User Focus を呼んでいないため)。当たったとしても、上段2つと下段1つがずれて配置されているので 下キーで意図しない方へ飛びます 。そして stat unit のGame時間が、メニューを開いている間だけ上がります。

ポーズメニューでフォーカ��スが定まらず、同時にGame時間が上がっている状態の図

ステップ1:開いて、フォーカスを当てる。 Player Controller側で、載せた後にフォーカスを当てます。

Create WidgetからAdd to Viewport、その後にSet User Focusを呼ぶノードグラフ
Enhanced Input Action IA_Pause
  Started
    → Branch(Condition: Is Valid(MenuRef))
        False(=開く)
          → Create Widget(Class: WBP_PauseMenu, Owning Player: Self)
          → Set MenuRef
          → Add to Viewport(Target: MenuRef)
          → Set Input Mode Game and UI(In Widget to Focus: MenuRef)
          → Set Show Mouse Cursor(true)
          → Set Game Paused(true)
          → Set User Focus(Target: MenuRef の ResumeButton)      ← 載せた後
        True(=閉じる)
          → Remove from Parent(Target: MenuRef)
          → Set MenuRef(None)
          → Set Input Mode Game Only
          → Set Show Mouse Cursor(false)
          → Set Game Paused(false)

2つ、忘れると詰まる点があります。

  • IA_PauseTrigger When Paused を有効に する。これがないと、ポーズ中に同じキーを押しても反応せず、 メニューを閉じられなくなります
  • 入力モードは Game and UI にする。UI Only にすると、Player Controllerのゲーム入力(=ポーズ解除)が届かなくなります

ステップ2:移動先を指名する。 各ButtonのNavigationを Explicit にして、行き先を指定します。上段が横並び、下段が単独という 自動では推測しづらい配置 なので、ここを明示すると挙動が安定します。

ボタンLeftRightDownUp
ResumeButtonOptionButtonOptionButtonQuitButtonQuitButton
OptionButtonResumeButtonResumeButtonQuitButtonQuitButton
QuitButtonResumeButton(ループ)ResumeButton

ステップ3:Bindingを外して、イベント更新にする。 ItemCountText のBindingを 削除 し、代わりに メニューを開いた瞬間に1回だけ 数えて表示します。

ポーズメニューでは、これがいちばん確実な方法です。 Set Game Paused(true) の後はWorld Timerも止まる ため、「Timerで定期更新」は成立しません。そもそもポーズ中は値が変わらないので、 開いたときに1回 で十分です。

WBP_PauseMenu の Event Construct
  → Get All Actors Of Class(Actor Class: BP_Item)
  → Length
  → Set Text(Target: ItemCountText, In Text: 個数を文字列化)

同じ Get All Actors Of Class でも、 毎フレーム走るか、開いたとき1回かで負荷はまったく違います 。処理を軽くしたのではなく、 呼ぶ回数を減らした のがポイントです。

ステップ4:必要なら描画側も。 ボタン3つ程度ならここで十分です。装備一覧やインベントリのように 変わらない複雑な塊 があるなら、その部分だけ Invalidation Box で囲みます。 メニュー全体ではなく、動かない部分だけ です。

Playして確認してください。 開いた瞬間に「再開」にフォーカスが当たり、方向キーで3つのボタンを意図どおり行き来でき、もう一度Escで閉じられる 。そして stat unit のGame時間が、開く前とほぼ変わらない。ここまで確認できれば成功です。

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

  • どこにもフォーカスが当たらないSet User FocusAdd to Viewport より前にあります。または対象が非表示・Is Focusable オフ
  • メニューを閉じられないIA_PauseTrigger When Paused か、入力モードが UI Only になっています
  • 方向キーが効かない → 入力モードの確認に加え、フォーカスが別のウィジェットに移っていないかも見てください
  • まだ重い → 残っている Event Tick かBindingを探します。Boxを足すのはその後です

ポイントは2つです。

  • フォーカスは「載せてから当てる」: Create Widget の時点ではまだ画面に存在しません。順番を守ったうえで、表示状態・Is Focusable・Owning Playerも確認対象に入れておくと切り分けが速くなります
  • 軽くする前に、呼ぶ回数を数える: 同じ処理でも毎フレームか1回かで桁が変わります。処理の中身を最適化するより、 いつ呼ばれているか を見直すほうが効果が大きいことがほとんどです

UI全体の設計から見直したくなったら UMG入門 へ、負荷の測り方を詳しく知りたいなら statコマンド へ進んでください。

Sponsored

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

マウスとゲームパッドが混在するときは、状態の違いを意識してください。 マウスを乗せたときに変わるのは Hovered で、方向キーで動かしている フォーカスとは別の状態 です。ただしクリックするとフォーカスはそちらへ移ります。「パッドで選んでいたのに、マウスを触ったら選択が変わった」を防ぐには、 入力デバイスの切り替わりを検知してフォーカスを当て直す のが定番です。

フォーカスが「見えない」問題も忘れずに。 UMGの ButtonStyle にあるのは Normal / Hovered / Pressed / Disabled で、 Focused という項目はありません 。フォーカス位置を見せたいなら、標準のフォーカス枠(Focus Brush)を使うか、Is Hovered ではなく フォーカス状態を取得して自前で見た目を切り替える 必要があります。正しく動いていても見た目に出ないと、プレイヤーには伝わりません。

Widget Reflector を使うと構造と更新が見えます。 エディタで Tools → Debug → Widget Reflector を開き、Pick Painted Widgets でUIをクリックすると、そのウィジェットの階層をたどれます。 どこが再描画されているか を調べたいときは、あわせて Invalidation DebuggingPaint Debugging を有効にしてください。「何が重いのか分からない」ときの最短ルートです。

そもそもウィジェットを減らせないか考えてみてください。 装飾のためだけに重ねたImageやBorder、空のコンテナは、それぞれがコストです。UIが重いと感じたら、最適化テクニックの前に Hierarchyを開いて不要な階層を消す ほうが、効果が大きいことがよくあります。


まとめ

UMGの二大トラブルは、それぞれ原因がはっきりしています。

症状原因対処
フォーカスが変な所へ飛ぶ自動ナビゲーションの推測Navigationを Explicit で指名
最初にフォーカスが当たらないSet User Focus が早すぎるAdd to Viewport で呼ぶ
方向キーが効かない入力モードがGame OnlyのままSet Input Mode Game and UI(ポーズ解除キーを残す。UI Only はタイトル画面向き)
開くと重いTick / Binding値が変わったときだけ更新する
数百ウィジェットで重い描画コストInvalidation Box → 最後にRetainer Box

推測に任せず、明示する。毎フレームではなく、変わったときだけ動かす。 UMGの問題は、この2つの原則でほとんど片付きます。

いま作っているUI、その中に「デバッグ用に付けたまま忘れたBinding」はありませんか?