ゲームのロジックは組めるようになったのに、UIになると手が止まる。UMGでは「Widgetを作ったのに画面に出ない」「解像度を変えたら全部ずれた」でつまずきがちです。どちらも原因が分かれば一瞬で直りますが、分からないと先へ進めません。
この記事では、HPバーを画面に出すまでの流れを Widgetを作る → 画面に載せる の2段階として整理し、アンカーの仕組みと、ダメージを受けたときだけ更新する軽いHPバーの作り方を解説します。
この記事でわかること
- UIは 作っただけでは出ない。
Create Widget+Add to Viewportの2段階- Designerタブ=見た目、Graphタブ=動き の役割分担
- 解像度で崩れる原因は アンカー 。「画面のどこを基準にするか」の設定
- Binding は楽だが呼ばれ続ける。 Event Dispatcher なら変わったときだけ
- 実践:ダメージを受けたときだけ更新する HPバー
Widgetは「作る」と「載せる」の2段階
最初のつまずきはここです。 Widget Blueprintを作っただけでは、画面には何も出ません。

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を開くと、右上に Designer と Graph の切り替えがあります。役割は明確に分かれています。

| タブ | 何をする場所か | 主に触るもの |
|---|---|---|
| Designer | 見た目 を組む。並べる・大きさを決める・色を決める | Palette(部品置き場)、Hierarchy(親子関係)、Details |
| Graph | 動き を組む。押されたら何をするか、いつ更新するか | 通常のBlueprintと同じノードグラフ |
Designerで置く部品は、大きく2種類あると考えると整理できます。
- 見せる部品:
Text、Image、Progress Bar、Buttonなど、実際に画面へ表示されるもの - 並べる部品(コンテナ):
Canvas Panel、Horizontal Box、Vertical Boxなど、中身の配置を決めるもの
初心者が苦労するのは後者です。とくに Canvas Panel の中では自由配置、Horizontal Box / Vertical Box の中では自動整列 という違いを知らないと、「動かしたい位置に動かせない」という現象に出会います。
迷ったときの型: ルートは
Canvas Panel、その中にHorizontal BoxやVertical Boxを置いて、部品はさらにその中へ。 大枠は自由配置、細かい並びは自動整列 に任せると崩れにくくなります。
アンカー:画面のどこを基準にするか
「自分の画面では完璧だったのに、解像度を変えたら全部ずれた」——UMGで最も多い事故が、このアンカーの理解不足です。

アンカー は「この部品は、 親の 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)で 別の端末やアスペクト比に切り替えてみる と、崩れる部品がその場で分かります。実機で確認する前にここで気づけます。
値をUIに反映する2つの方法
HPバーをHPと連動させたい。方法は2つあり、 どちらを選ぶかで負荷が変わります 。

方法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バーは、ダメージを受けるまでは満タンのまま。攻撃が当たった瞬間だけ、バーがスッと減って現在値に合わせて縮みます。

再現用の準備: 次を用意します。
| 場所 | 名前 | 型・設定 |
|---|---|---|
BP_Player(Character継承) | MaxHealth | Float / 100.0 |
BP_Player | CurrentHealth | Float / 100.0 |
BP_Player | OnHealthChanged | Event Dispatcher 。入力に NewPercent(Float)を1つ追加 |
WBP_PlayerHUD | HealthBar | Progress Bar。 Is Variable にチェック |
BP_PlayerController(PlayerController継承) | HUDRef | WBP_PlayerHUD 型の変数 |
Widget Blueprintは、コンテンツブラウザで右クリック → User Interface → Widget Blueprint → 親クラスに User Widget を選んで作ります。
最初に必ずやること: BP_PlayerController を作っただけでは使われません。GameMode(BP_GameMode など)を開き、 Player Controller Class を BP_PlayerController に、Default Pawn Class を BP_Player に 設定してください。ここを忘れると BeginPlay すら実行されず、「何も出ない」で延々と悩むことになります。
ステップ1:HUDのレイアウトを組む。 WBP_PlayerHUD のDesignerタブで、Canvas Panel の中に Horizontal Box を置き、その中に Text(「HP」)と Progress Bar(HealthBar)を並べます。
2つ設定します。 Horizontal Box を選んでアンカーを左上 にし、画面左上へ配置。そして HealthBar の Slot で Size を Fill に(既定の Auto だとバーがほとんど見えない幅になります)。
ステップ2:HUDを画面に出す。 BP_PlayerController の BeginPlay で作って載せます。

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 Damage → Event Any Damage 経由でHPを減らす形へ発展させるなら、ダメージ処理とApply Damage へ進みます。

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))
Percent は 0.0〜1.0の割合 なので、CurrentHealth ÷ MaxHealth を渡します。実数値のまま渡すと、HP100でバーが振り切れたまま動きません。
テスト用の呼び出し口も作っておきます。 BP_Player のイベントグラフで、適当なキー入力から ReduceHealth(DamageAmount: 25.0) を呼ぶだけで十分です。敵を作る前に動作確認できます。
ステップ4:UI側で受け取る。 WBP_PlayerHUD に、放送を聞くカスタムイベントを作ります。

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 OnHealthChanged の Target に、Castの結果(As BP Player)をつなぐのを忘れないでください。Eventピンから Add Custom Event for Dispatcher を選ぶと、引数の型が合ったイベントが自動で作られます。
Playして、テスト用キーを押してみてください。 1回目で75%、2回目で50%、4回で空 とバーが段階的に減れば成功です。このとき 減った瞬間以外は UpdateHealthBar が1回も呼ばれていません 。そこがこの作り方の狙いです。
うまくいかないときの切り分けです。
- バーが動かない →
Bind EventのTargetが空か、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回壊してみると理解が定着します。HealthBar の Percent に Bind を追加し、常に 1.0 を返すだけの関数を作ってPlayしてください。ReduceHealth を何度呼んでもバーは満タンに戻ります。Set Percent した値を、Bindingが毎フレーム上書きしているからです。前の節で触れた「切り替えるときはBindingを削除」が、なぜ必須なのかがこれで分かります。確認したらBindは削除して戻します。
ゲージがなめらかに減るアニメーションを付けたいなら、Set Percent へ渡す値を FInterp To で目標値に寄せていくか、UMGの Animation 機能を使います。UIが増えて重く感じたら、UMGのパフォーマンス最適化 へ進んでください。
おまけ:先に知っておくと良いこと
ゲーム世界の映像を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 Widget + Add to Viewport と同じです。第1引数が Owning Player にあたるので、Player Controllerの中なら this を渡します(GetWorld() を渡すと所有関係が変わってしまいます)。
まとめ
UMGでつまずくポイントは、だいたい次の3つに集約されます。
| 症状 | 原因 | 対処 |
|---|---|---|
| 画面に出ない | 作っただけで載せていない | Create Widget + Add to Viewport |
| 解像度で崩れる | アンカー未設定 | 「画面が広くなったらどこに居てほしいか」で決める |
| 重い | Bindingに処理を詰め込んだ | 変化が少ない値は Event Dispatcher へ |
そして設計の原則が1つあります。 UIは表示に専念させる ことです。値を持つのも計算するのも、ゲームロジック側の仕事になります。この線引きさえ守れば、UIを作り直してもゲームは壊れません。
いま作っているHUD、その中でゲームの値を計算していませんか?