【Unreal Engine】UMG入門:Widget BlueprintでHUDを作る

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

UMGでHPバーを画面に出すまでを、Create Widget→Add to Viewportの流れとアンカーの仕組みから図解。Bindingに頼らず、減ったときだけ更新する軽いHUDを組む実践つき。

ゲームのロジックは組めるようになったのに、UIになると手が止まる。UMGでは「Widgetを作ったのに画面に出ない」「解像度を変えたら全部ずれた」でつまずきがちです。どちらも原因が分かれば一瞬で直りますが、分からないと先へ進めません。

この記事では、HPバーを画面に出すまでの流れを Widgetを作る → 画面に載せる の2段階として整理し、アンカーの仕組みと、ダメージを受けたときだけ更新する軽いHPバーの作り方を解説します。

Widget Blueprintという1枚のシートを、ゲーム画面に重ねているイメージ

この記事でわかること

  • UIは 作っただけでは出ないCreate WidgetAdd to Viewport の2段階
  • Designerタブ=見た目、Graphタブ=動き の役割分担
  • 解像度で崩れる原因は アンカー 。「画面のどこを基準にするか」の設定
  • Binding は楽だが呼ばれ続ける。 Event Dispatcher なら変わったときだけ
  • 実践:ダメージを受けたときだけ更新する HPバー

Sponsored

Widgetは「作る」と「載せる」の2段階

最初のつまずきはここです。 Widget Blueprintを作っただけでは、画面には何も出ません。

Widget Blueprintという設計図から、Create Widgetで実体を作り、Add to Viewportで画面に載せる3段階の図

Widget Blueprintは 設計図 です。この設計図から Create Widget実体(インスタンス) を作り、さらに Add to Viewport画面に載せて はじめて見えるようになります。

Event BeginPlay(Player Controller)
  → Create Widget(Class: WBP_PlayerHUD, Owning Player: Self)
  → Set HUDRef(作った実体を変数に保持しておく)
  → Add to Viewport(Target: HUDRef)

作った実体を変数に保存しておく のがコツです。あとで「HPバーを更新したい」「メニューを閉じたい」と思ったとき、参照がないと 後から直接操作しづらくなります

消すときは Remove from Parent です。Add to Viewport の逆で、画面から取り外します。

Add to Viewport が要るのは、単独で画面に出すWidgetだけ です。親Widgetの中にDesignerで並べた子Widgetは、親が画面に載れば一緒に表示されるので、個別に呼ぶ必要はありません。

どこに書くか: HUDのように ゲーム中ずっと出ているUI は、Player Controllerの BeginPlay に書くのが定番です。キャラクターに書くと、死んで再生成されたときにUIも作り直しになります。役割分担の全体像は GameMode/GameState/PlayerStateの使い分け を参照してください。

DesignerタブとGraphタブ

Widget Blueprintを開くと、右上に DesignerGraph の切り替えがあります。役割は明確に分かれています。

左にレイアウトを組むDesignerタブ、右にノードを組むGraphタブを並べた図
タブ何をする場所か主に触るもの
Designer見た目 を組む。並べる・大きさを決める・色を決めるPalette(部品置き場)、Hierarchy(親子関係)、Details
Graph動き を組む。押されたら何をするか、いつ更新するか通常のBlueprintと同じノードグラフ

Designerで置く部品は、大きく2種類あると考えると整理できます。

  • 見せる部品: TextImageProgress BarButton など、実際に画面へ表示されるもの
  • 並べる部品(コンテナ): Canvas PanelHorizontal BoxVertical Box など、中身の配置を決めるもの

初心者が苦労するのは後者です。とくに Canvas Panel の中では自由配置、Horizontal Box / Vertical Box の中では自動整列 という違いを知らないと、「動かしたい位置に動かせない」という現象に出会います。

迷ったときの型: ルートは Canvas Panel 、その中に Horizontal BoxVertical Box を置いて、部品はさらにその中へ。 大枠は自由配置、細かい並びは自動整列 に任せると崩れにくくなります。

アンカー:画面のどこを基準にするか

「自分の画面では完璧だったのに、解像度を変えたら全部ずれた」——UMGで最も多い事故が、このアンカーの理解不足です。

左上アンカー・中央アンカー・全画面ストレッチの3種類で、画面サイズが変わったときの挙動を比べた図

アンカー は「この部品は、 親の Canvas Panel のどこを基準に 位置を測るか」の指定です。ルートのCanvas Panelは画面いっぱいに広がっているので、結果として「画面のどこを基準にするか」になります。

設定できるのは Canvas Panel の直接の子だけ です。Horizontal Box の中に入れた部品を選んでも Anchors は出てこないので、 アンカーを設定したいのは、Canvas Panelに直接置いたコンテナのほう だと覚えてください。

アンカー基準向いているUI
左上画面の左上からの距離で位置が決まるHP・スタミナなどのステータス表示
右上/右下それぞれの角からの距離ミニマップ、時計、弾数
中央画面中央からのずれで位置が決まるクロスヘア、ダメージ表示
全体に引き伸ばし上下左右すべてに追従(サイズが伸縮する)背景の暗幕、フルスクリーンのメニュー

判断の基準は 「画面が広くなったとき、この部品はどこに居てほしいか」 です。左上に居てほしいなら左上アンカー、いつも真ん中がいいなら中央アンカーを選びます。

中央に置きたいのに少しずれる ときは Alignment を確認してください。アンカーが決めるのは 基準点 で、その基準点にWidgetの どこを合わせるかAlignment の担当です。きっちり中央に置くなら Alignment(0.5, 0.5) にします。

確認方法も覚えておきましょう。Designerタブ上部にある解像度のプルダウン(Screen Size / Preview Size)で 別の端末やアスペクト比に切り替えてみる と、崩れる部品がその場で分かります。実機で確認する前にここで気づけます。

Sponsored

値をUIに反映する2つの方法

HPバーをHPと連動させたい。方法は2つあり、 どちらを選ぶかで負荷が変わります

Bindingは表示中ずっと値を聞きに行き、Event Dispatcherは変わったときだけ知らせに来る、という対比図

方法1:Binding(バインド)

Progress Barの Percent の横にある Bind ボタンから関数を作ると、UMGが Widgetが有効な間ずっと、毎Tickその関数を呼んで 値を取りに行きます。

  • 良いところ: 数クリックで済む。更新漏れが起きない
  • 注意点: UIが出ている間、呼ばれ続けます 。中に Cast や検索処理を入れると、それが延々と走ります
  • 切り替えるときは削除を: 後から Set Percent で更新する方式に変える場合、 Bindingを削除してから にしてください。残したままだと、せっかくSetした値がBindingの返り値で上書きされます

方法2:Event Dispatcher(変わったときだけ知らせる)

HPが減った側から「減ったよ」と放送し、UIはそれを聞いたときだけ更新します。

  • 良いところ: 変化したときしか動かない 。100回に1回しか変わらない値なら、100分の1のコスト
  • 注意点: 放送を仕込む手間がかかる(→ Event Dispatcherの使い方

この2つ以外に、 外部から直接 Set Percent を呼ぶ 方式もあります(Player Controllerなどが更新を担当する形)。ここでは代表的な2つを比べます。

使い分けの目安は、 変化の頻度 です。毎フレーム動くもの(速度メーター、体力が常時変動するゲーム)はBinding、たまにしか変わらないもの(HP、スコア、所持金、残弾)はEvent Dispatcher。迷ったら まずBindingで作って、重くなってから移す のが現実的です。


実践:減ったときだけ更新するHPバーを作る

アクションRPGの体力ゲージ、シューティングのシールド量、サバイバルゲームの空腹度——「数値をバーで見せる」UIはどのジャンルにも登場します。ここでは ダメージを受けた瞬間だけ更新される HPバーを作ります。

HPは常時変動する値ではなく、ダメージを受けた瞬間にだけ動きます。前の節の目安(たまにしか変わらない値はEvent Dispatcher)にそのまま当てはまるので、この記事ではBindingを使わず、最初からEvent Dispatcherで組みます。

動かすとこうなります。画面左上のHPバーは、ダメージを受けるまでは満タンのまま。攻撃が当たった瞬間だけ、バーがスッと減って現在値に合わせて縮みます。

被弾前はHPバーが満タン100、被弾後はバーが75まで減った状態を左右で対比した実例図

再現用の準備: 次を用意します。

場所名前型・設定
BP_PlayerCharacter継承MaxHealthFloat / 100.0
BP_PlayerCurrentHealthFloat / 100.0
BP_PlayerOnHealthChangedEvent Dispatcher 。入力に NewPercent(Float)を1つ追加
WBP_PlayerHUDHealthBarProgress Bar。 Is Variable にチェック
BP_PlayerControllerPlayerController継承HUDRefWBP_PlayerHUD 型の変数

Widget Blueprintは、コンテンツブラウザで右クリック → User InterfaceWidget Blueprint → 親クラスに User Widget を選んで作ります。

最初に必ずやること: BP_PlayerController を作っただけでは使われません。GameMode(BP_GameMode など)を開き、 Player Controller ClassBP_PlayerController に、Default Pawn ClassBP_Player 設定してください。ここを忘れると BeginPlay すら実行されず、「何も出ない」で延々と悩むことになります。

ステップ1:HUDのレイアウトを組む。 WBP_PlayerHUD のDesignerタブで、Canvas Panel の中に Horizontal Box を置き、その中に Text(「HP」)と Progress BarHealthBar)を並べます。

2つ設定します。 Horizontal Box を選んでアンカーを左上 にし、画面左上へ配置。そして HealthBarSlotSizeFill に(既定の Auto だとバーがほとんど見えない幅になります)。

ステップ2:HUDを画面に出す。 BP_PlayerControllerBeginPlay で作って載せます。

BeginPlayからCreate Widget、Set HUDRef、Add to Viewportへつなぐノードグラフ
Event BeginPlay(BP_PlayerController)
  → Create Widget(Class: WBP_PlayerHUD, Owning Player: Self)
  → Set HUDRef
  → Add to Viewport(Target: HUDRef)

ここで一度Playしてください。 左上に「HP」と、満タンのバーが出ていれば 前半は成功です。何も出ないならGameModeの設定か Add to Viewport を、バーが細すぎるなら Size = Fill を確認します。

ステップ3:HPが減ったら放送する。 BP_Player に体力を減らす関数を作り、変化した瞬間に OnHealthChanged を鳴らします。

関数名は ReduceHealth にしてください。ApplyDamage はエンジン標準ノードと同名で、ノード検索のときに紛らわしくなります。ここを敵の攻撃と連動させて、Apply DamageEvent Any Damage 経由でHPを減らす形へ発展させるなら、ダメージ処理とApply Damage へ進みます。

ReduceHealthでCurrentHealthを減らし、割合を計算してOnHealthChangedをCallするノードグラフ
Function: ReduceHealth(入力: DamageAmount / Float)
  → Set CurrentHealth
      = Clamp(Value: Subtract(A: CurrentHealth, B: DamageAmount), Min: 0.0, Max: MaxHealth)
  → OnHealthChanged を Call(NewPercent: Divide(A: CurrentHealth, B: MaxHealth))

Percent0.0〜1.0の割合 なので、CurrentHealth ÷ MaxHealth を渡します。実数値のまま渡すと、HP100でバーが振り切れたまま動きません。

テスト用の呼び出し口も作っておきます。 BP_Player のイベントグラフで、適当なキー入力から ReduceHealth(DamageAmount: 25.0) を呼ぶだけで十分です。敵を作る前に動作確認できます。

ステップ4:UI側で受け取る。 WBP_PlayerHUD に、放送を聞くカスタムイベントを作ります。

OnHealthChangedにBindしたカスタムイベントからSet Percentへつなぐノードグラフ
WBP_PlayerHUD の Event Construct
  → Get Owning Player Pawn
  → Cast To BP_Player
      成功 → Bind Event to OnHealthChanged(Target: Cast の As BP Player)
                Event ピン ← Custom Event: UpdateHealthBar(入力: NewPercent / Float)
          → Set Percent(Target: HealthBar,
                         In Percent: Divide(A: As BP Player の CurrentHealth, B: MaxHealth))  ← ★初期値の同期

UpdateHealthBar
  → Set Percent(Target: HealthBar, In Percent: NewPercent)

★の行が重要です。 Event Dispatcherは、Bindする前に起きた変化を後から教えてくれません 。Bindした直後に現在値を自分で読んで反映しておかないと、「途中参加したUIが満タンのまま」という状態になります。

Bind Event to OnHealthChangedTarget に、Castの結果(As BP Player)をつなぐのを忘れないでください。Eventピンから Add Custom Event for Dispatcher を選ぶと、引数の型が合ったイベントが自動で作られます。

Playして、テスト用キーを押してみてください。 1回目で75%、2回目で50%、4回で空 とバーが段階的に減れば成功です。このとき 減った瞬間以外は UpdateHealthBar が1回も呼ばれていません 。そこがこの作り方の狙いです。

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

  • バーが動かないBind EventTarget が空か、Event Construct に繋がっていません
  • Cast To BP_Player が失敗する → GameModeの Default Pawn Class が違うか、まだPossessされていません。Player Controller側でWidgetを作る順番を BeginPlay の後半にずらすと安定します
  • バーが最初から空/満タンのまま → ★の初期値同期が抜けています

ポイントは2つです。

  • UIは「表示」だけを担当させる: HPを減らす計算は BP_Player 、表示は WBP_PlayerHUD 。UIの中でゲームロジックを動かさないと決めておくと、UIを作り直しても仕様が壊れません
  • Bindした直後に、必ず現在値を1回読む: Dispatcherは過去の変化を教えてくれません。「途中から見はじめた人に、いまの状況を伝える」処理が要ります

動いたら、あえて1回壊してみると理解が定着します。HealthBarPercentBind を追加し、常に 1.0 を返すだけの関数を作ってPlayしてください。ReduceHealth を何度呼んでもバーは満タンに戻ります。Set Percent した値を、Bindingが毎フレーム上書きしているからです。前の節で触れた「切り替えるときはBindingを削除」が、なぜ必須なのかがこれで分かります。確認したらBindは削除して戻します。

ゲージがなめらかに減るアニメーションを付けたいなら、Set Percent へ渡す値を FInterp To で目標値に寄せていくか、UMGの Animation 機能を使います。UIが増えて重く感じたら、UMGのパフォーマンス最適化 へ進んでください。

Sponsored

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

ゲーム世界の映像をUIに出せる。 Image のBrushにはテクスチャを指定できますが、そこには カメラが撮っている映像(Render Target) も入れられます。ミニマップやキャラクター選択のプレビューはこれで作ります(→ Render Targetでミニマップを作る)。

メニューを開いたらマウスカーソルが出ない、は設定漏れです。 Player Controllerの Set Show Mouse Cursor(true) と、Set Input Mode UI Only(またはGame and UI)の2つが必要です。閉じるときは 両方を戻しますSet Input Mode Game Only だけではカーソルが出たままになるので、Set Show Mouse Cursor(false) も忘れずに実行してください。

Event Construct は1回きりとは限りません。 Widgetを画面から外して再度追加すると、そのたびに呼ばれます。「一度だけ初期化したい」処理には Event On Initialized を使うほうが安全です。

同じ部品を何度も作るなら、Widgetを部品化できます。 インベントリのアイテム枠のように「同じ見た目を並べる」ものは、専用のWidget Blueprint(例: WBP_ItemSlot)を作り、それを親のWidgetの中に配置します。Blueprintのコンポーネント化と同じ発想で、修正が1箇所で済みます。

Text Blockの文字が切れるときは、幅から確認してください。 原因は Auto Wrap Text が無効(UMGのTextは既定で折り返しません)、親Slotの幅が足りない、Clipping の設定、フォントサイズが大きすぎる——のどれかです。長い文章なら Auto Wrap Text 、1行に収めたいなら親の幅か Size Box で調整します。

C++で書くとどうなるか。 UIそのものはWidget Blueprintで作り、C++側は 値を渡す・イベントを受ける に徹するのが定番です。UUserWidget を継承したクラスを作り、BlueprintImplementableEvent で「更新して」と呼び出す形にすると、見た目の調整はデザイナー側で完結します。

// Player Controller の中で書く場合(this が Owning Player になる)
if (UUserWidget* HUD = CreateWidget<UUserWidget>(this, HUDClass))
{
    HUD->AddToViewport();
}

やっていることはBlueprintの Create WidgetAdd to Viewport と同じです。第1引数が Owning Player にあたるので、Player Controllerの中なら this を渡します(GetWorld() を渡すと所有関係が変わってしまいます)。


まとめ

UMGでつまずくポイントは、だいたい次の3つに集約されます。

症状原因対処
画面に出ない作っただけで載せていないCreate WidgetAdd to Viewport
解像度で崩れるアンカー未設定「画面が広くなったらどこに居てほしいか」で決める
重いBindingに処理を詰め込んだ変化が少ない値は Event Dispatcher

そして設計の原則が1つあります。 UIは表示に専念させる ことです。値を持つのも計算するのも、ゲームロジック側の仕事になります。この線引きさえ守れば、UIを作り直してもゲームは壊れません。

いま作っているHUD、その中でゲームの値を計算していませんか?