Skip to main content
#UE4 | #gcconf2020
UE4で作成するUIと最適化手法
Epic Games Japan
Software Engineer, Developer Relations
鍬農 健二郎
#UE4 | #gcconf2020
何故UIをやるのか?
#UE4 | #gcconf2020
概要
● 目的
● UMGを使って効率的にUIを構築できる
● UIの最適化方法を適用できる
● 対象
● UMGを利用するアーティスト
● 実装や最適化をするエンジニア、TA
● 適用範囲
UE 4.25
#UE4 | #gcconf2020
本日のお題
設計
仕組み 最適化
#UE4 | #gcconf2020
本日のお題
● 設計
● レイアウト設計 … アート
● クラス設計 … エンジニア
● 仕組み
● ライフサイクル … エンジニア
● 最適化
● オプション … アート、エンジニア
● デバッグ … アート、エンジニア
#UE4 | #gcconf2020
UMGを基礎から学びたい方はコチラがおすすめ
https://www.slideshare.net/EpicGamesJapan/umg-80334310
#UE4 | #gcconf2020
目次
● 設計
● レイアウト設計
● クラス設計
● 仕組み
● 最適化
#UE4 | #gcconf2020
レイアウト設計
#UE4 | #gcconf2020
レイアウト設計は悩む
再利用
見栄え
無駄なく
#UE4 | #gcconf2020
おさらい
● Widget
小さな機能単位の表示要素
● User Widget
Widgetを組み合わせて作成する表示要素
⇒ Main Widget, Sub Widget
#UE4 | #gcconf2020
Widget
Sub Widget
Widget
#UE4 | #gcconf2020
User Widget
レイアウト
Sub WidgetMain Widget
ボタンセット アタックボタン
#UE4 | #gcconf2020
レイアウト設計での課題
● ウィジェットを配置する基準は?
● ウィジェットの使い分け方は?
● ウィジェットを再利用する方法は?
● アニメーションを再利用する方法は?
#UE4 | #gcconf2020
課題1:ウィジェットを配置する基準は?
● ウィジェットを配置する理由を意識する
何故それを使ったか説明できるように配置する
● レイアウトが常に変わることを意識する
レイアウト変更、マルチプラットフォームに対応
#UE4 | #gcconf2020
Widgetの配置例
#UE4 | #gcconf2020
Widgetの配置例
複数の子Widgetをレイヤー管理するため
ポーズ中に背景をぼかすため (ブラー効果)
子Widgetにアンカーを適用するため
子Widgetで埋めるため
#UE4 | #gcconf2020
Widgetの配置例
ポーズメニュー上にオプション画面を被せるため
ボタンで横幅を埋めるため
範囲を必ず指定の大きさにするため
#UE4 | #gcconf2020
Main Widgetの配置例
Named Slot
(上位レイヤーに被せて表示)
Safe Zone
(可変レイアウト表示)
Switcher
(表示物を切り替えて表示)
#UE4 | #gcconf2020
Sub Widgetの配置例
#UE4 | #gcconf2020
Sub Widgetの配置例
全体をスケールするため
複数の子Widgetを管理するため
#UE4 | #gcconf2020
Sub Widgetの配置例
コンテンツを縦に敷き詰めて配置するため
ポップアップの説明欄は固定サイズで表示するため
#UE4 | #gcconf2020
課題2: ウィジェットの使い分け方は?
● 機能を実現できるもの
やりたいことを実現できるものを選択する
● 拡張性が良いもの
コンテンツの追加や削除など変更に強いものを選択する
● コストが低いもの
同じ機能でもWidget別でコストの違いがある
#UE4 | #gcconf2020
Widgetの使い分け
例:複数の子Widgetをレイヤ管理
Canvas Panel vs Overlay
例:スクロール表示と操作
Scroll Box vs List View
#UE4 | #gcconf2020
課題3: ウィジェットを再利用するには?
● Widgetを拡張する
コンテンツに併せてWidgetを使いやすくする
● Widgetを組み合わせる
コンテンツに合った新しいWidgetを作成する
#UE4 | #gcconf2020
Widget (Text Block)
[問題点]
テキストの設定を毎回調整する必要がある
#UE4 | #gcconf2020
Textのフォントやサイズを簡単に設定できるようにした例
#UE4 | #gcconf2020
テキストスタイルを指定して適用する例
#UE4 | #gcconf2020
カスタムテキストスタイルの例
#UE4 | #gcconf2020
Widget (Button)
[問題点]
似たような機能やデザインのボタンが増える
#UE4 | #gcconf2020
例えばこのようなOK/Cancelの選択肢で同じボタンを使い回したい...
#UE4 | #gcconf2020
サブウィジェット上に配置した共有Widgetを選択して書き換え
#UE4 | #gcconf2020
課題4: アニメーションを再利用するには?
● 共通のWidgetを利用してリソースのみ差し替え
共通Widgetのアニメーションを常に利用する
● 親子関係を利用してアニメーションを適用
スケールやカラーなど親Widgetの変更に追従させる
#UE4 | #gcconf2020
アニメーションの再利用
● 共通のWidgetを利用してリソースのみ差し替え
Basic Button
Animation
Image
リソース
差し替え
Basic Button
Animation
Texture A
Common Widget
#UE4 | #gcconf2020
共有するWidgetにアニメーションを仕込んでおく
#UE4 | #gcconf2020
例えば、ボタンの場合はプレス時のアニメーション
#UE4 | #gcconf2020
配置先でリソースを差し替えておくことで、
#UE4 | #gcconf2020
同じアニメーションを再生することが可能になる
#UE4 | #gcconf2020
共有Widgetは上書きするプロパティをDesignerに反映させるためにPreConstructでセット
ボタンテキストの更新
ボタンイメージの更新
#UE4 | #gcconf2020
アニメーションの再利用
● 親子関係を利用してアニメーションを適用
Content Root
Animation
Content Root
Animation
Popup
対象を
子に追加
#UE4 | #gcconf2020
ポップアップを管理するためのマネージャクラス (親Widget)を準備
#UE4 | #gcconf2020
ポップアップ共通のアニメーションを親のWidgetに仕込んでおく
#UE4 | #gcconf2020
アニメーションを適用するWidgetの子に対象を追加
#UE4 | #gcconf2020
子Widgetに対してScale変更やOpacity変更が伝搬
#UE4 | #gcconf2020
その他考察事項
● キャンバスパネルの多用を避ける
● ネガティブパディングの設定を避ける
● 複製するならサブウィジェット化する、など
#UE4 | #gcconf2020
レイアウト設計のまとめ
● わかりやすさ、再利用、コストを意識する
● デザインするUIとの折り合いをつける
#UE4 | #gcconf2020
クラス設計
#UE4 | #gcconf2020
クラス設計も迷う
再利用
複雑さ
分担
#UE4 | #gcconf2020
Widgetのクラス設計についての例
UMGのベストプラクティス
公式ブログで紹介しているので参考になります
#UE4 | #gcconf2020
おさらい
ウィジェットブループリントを新規作成する時にデフォルト
のParent classはUser Widgetである
#UE4 | #gcconf2020
おさらい
User Widgetクラスはレイアウトに配置したり追加したり
削除することができる
#UE4 | #gcconf2020
おさらい
[!] Widget Blueprintの制約
● Widget Blueprintは Designer上の配置情報を継承しない
● 配置情報が継承されないことを前提に設計する必要がある
Parent Child
#UE4 | #gcconf2020
クラス設計での課題
● Widgetの構造をどうするか?
● Widgetの実装をどうするか?
● Widgetの接続をどうするか?
#UE4 | #gcconf2020
課題1: Widgetの構造をどうするか?
● Widgetの役割に応じて適切に配置する
Engineの機能とクラスを理解しておく
● ネイティブクラスを作成する
プロジェクト独自の機能を追加して使いやすくする
#UE4 | #gcconf2020
Widget/UMG関連のEngineクラスの一部
UCanvasPanel
UButton
UUserWidget
UVisual
UObject
UWidget
UPanelWidget
UHorizontalBox UContentWidget
UImage
UPanelSlotUWidget
Widget Blueprint
ここだけは最低限覚えておく
(それ以外はイメージだけでOK)
#UE4 | #gcconf2020
Widget/UMG関連のEngineクラスの一部
UCanvasPanel
UButton
UUserWidget
UVisual
UObject
UWidget
UPanelWidget
UHorizontalBox UContentWidget
UImage
UPanelSlot
ユーザー定義のWidget
#UE4 | #gcconf2020
Widget/UMG関連のEngineクラスの一部
UCanvasPanel
UButton
UUserWidget
UVisual
UObject
UWidget
UPanelWidget
UHorizontalBox UContentWidget
UImage
UPanelSlot
子を格納可能なWidget
#UE4 | #gcconf2020
Widget/UMG関連のEngineクラスの一部
UCanvasPanel
UButton
UUserWidget
UVisual
UObject
UWidget
UPanelWidget
UHorizontalBox UContentWidget
UImage
UPanelSlot
レイヤー管理可能なWidget
水平方向に格納可能なWidget
#UE4 | #gcconf2020
Widget/UMG関連のEngineクラスの一部
UCanvasPanel
UButton
UUserWidget
UVisual
UObject
UWidget
UPanelWidget
UHorizontalBox UContentWidget
UImage
UPanelSlot 1つの子を格納可能なWidget
#UE4 | #gcconf2020
Widget/UMG関連のEngineクラスの一部
UCanvasPanel
UButton
UUserWidget
UVisual
UObject
UWidget
UPanelWidget
UHorizontalBox UContentWidget
UImage
UPanelSlot
独自の機能を持つWidget
#UE4 | #gcconf2020
Widget/UMG関連のEngineクラスの一部
UCanvasPanel
UButton
UUserWidget
UVisual
UObject
UWidget
UPanelWidget
UHorizontalBox UContentWidget
UImage
UPanelSlotUWidget
Widget Blueprint
プロジェクトで作成するWidgetの
クラス構成はどうする?
Native BlueprintEngine
#UE4 | #gcconf2020
ActionRPG サンプルの場合
入力ラベル
入力ボタン
#UE4 | #gcconf2020
ActionRPG のクラス
UUserWidget
UWidgetUWidget
WB_InputButton
Native BlueprintEngine
シンプルな構成なので
Parent Classは全てUserWidget
WB_InputLabel
#UE4 | #gcconf2020
ActionRPG のクラスを拡張
UWidgetUWidget
Native BlueprintEngine
プロジェクト独自の
ネイティブクラスを追加
WB_InputLabel
UCustomWidget
UCustomButton
UUserWidget
WB_InputButton
#UE4 | #gcconf2020
ActionRPG のクラスを拡張
UWidgetUWidget
Native BlueprintEngine
WB_InputLabel
UCustomWidget
UCustomButton
UUserWidget
WB_InputButton
プロジェクト独自の
共通Widget
#UE4 | #gcconf2020
ActionRPG のクラスを拡張
UWidgetUWidget
Native BlueprintEngine
WB_InputLabel
UCustomWidget
UCustomButton
UUserWidget
WB_InputButton
基本的にWidgetは
共通Widgetから作成
#UE4 | #gcconf2020
ActionRPG のクラスを拡張
UWidgetUWidget
Native BlueprintEngine
WB_InputLabel
UCustomWidget
UCustomButton
UUserWidget
WB_InputButton
ボタンWidget
共通の処理
#UE4 | #gcconf2020
ActionRPG のクラスを拡張
UWidgetUWidget
Native BlueprintEngine
WB_InputLabel
UCustomWidget
UCustomButton
UUserWidget
WB_InputButton
追加になっても
変更点のみの追加でOK
WB_ButtonText
#UE4 | #gcconf2020
UWidgetUWidget
プロジェクトで拡張するカスタムテキスト例
UTextBlock
UTextLayoutWidget
エンジンのデフォルトテキスト
Native BlueprintEngine
#UE4 | #gcconf2020
プロジェクトのカスタムテキスト
プロジェクトで拡張するカスタムテキスト例
UWidgetUWidget
UTextBlock
UCustomText
UTextLayoutWidget
Native BlueprintEngine
#UE4 | #gcconf2020
課題2: Widgetの実装をどうするか?
● BlueprintとC++の線引きをしておく
責任の範囲を決めて実装する
● 可能な限り小さなWidgetに分割する
作業の競合、ロードなどに影響
#UE4 | #gcconf2020
Widget実装の分担
Blueprint Class
● デザイナに配置したWidgetの制御
● アニメーションの制御
● C++から提供されたAPI
Native Class (C++)
● データの受け渡し
● 複雑でコストが高い計算や処理
● 他クラスとのやりとり、UI更新の通知
エンジニア
アート
#UE4 | #gcconf2020
Widgetの実装分担 (Blueprint)
作業者A
● Designerの内容
● Widget表示切替
● アニメーション再生
#UE4 | #gcconf2020
Widgetの実装分担 (C++)
作業者B
● トリガーイベント
● コンテンツ情報
● BPで使用するAPI
#UE4 | #gcconf2020
Widgetの実装分担 (C++の実装ルール)
● Abstract
Native Classは常に抽象化する
● NotBlueprintable
他のNative Classの基底クラスになるもの
● Blueprintable
派生してBlueprintで継承可能なもの
#UE4 | #gcconf2020
課題3: Widgetの接続をどうするか?
● Native ClassとSubsystemを活用する
Widget Blueprint から直接別のクラスに参照するすることもできるが、
サブウィジェットはできる限り他のクラスと参照を持たない方が望ましい
#UE4 | #gcconf2020
SubSystemを基礎から学びたい方はコチラがおすすめ
https://www.slideshare.net/EpicGamesJapan/ue4-subsystem
#UE4 | #gcconf2020
ネイティブクラス経由でWidgetの内容を更新する
Health Bar
Bar Widget Custom Player
● Initialize
Health Bar
Bar Widget Custom Player
● Update
Action
OnChangeValue()
OnChangeValue()
Apply Damage
Update
ItemApply Heal
Native BlueprintEngine
#UE4 | #gcconf2020
サブシステム経由でWidgetの内容を更新する
Weather Icon
Custom Widget Weather SubSystem
● Initialize
Weather Icon
Custom Widget Weather SubSystem
● Update
Action
OnChangeStatus()
OnChangeStatus()
Change Weather
Time
Native BlueprintEngine
#UE4 | #gcconf2020
クラス設計のまとめ
● クラスの役割を明確にして分離する
● ネイティブクラスを効果的に利用する
#UE4 | #gcconf2020
目次
● 設計
● 仕組み
ライフサイクル
● 最適化
#UE4 | #gcconf2020
仕組みを理解するのは色々と役に立つ
● Widgetへの機能追加が容易になる
● ボトルネックの勘所が分かるようになる
● 最適化のオプションを理解して適用できる
効率化!
#UE4 | #gcconf2020
例:UIの処理が重いらしい
とりあえずUnreal Insightsのグラフをみても
何が原因なのか分からない
#UE4 | #gcconf2020
Tick
UIの処理の仕組みが分かっていれば
たしかこう動いていたので
SlatePrepass Paint AddElement
#UE4 | #gcconf2020
なんかUIの処理が重いらしい
Misc
Slateはこんなに処理負荷があった
⇒ 〇〇の最適化を入れよう
SlatePrepass Paint Add
Element
#UE4 | #gcconf2020
Widget
おさらい
Widgetは内部的に2つのパーツで構成される
● UWidget :Slateに対する制御など他のObjectと対話
● SWidget :レイアウトやインタラクションなどユーザーと対話
UWidget
(制御部)
Engine UserSWidget
(表示部)
#UE4 | #gcconf2020
シンプルなWidgetの開始と終了
#UE4 | #gcconf2020
シンプルなWidgetの開始と終了
Create
OnScreen
Remove
#UE4 | #gcconf2020
Widget Lifecycle
Create OnScreen Tick Remove
生成 追加 更新 除去
5つのステップ
Destroy
破棄
#UE4 | #gcconf2020
フローとオブジェクトの変化
Create OnScreen Tick Remove Destroy
UWidget
フロー
オブジェクト
UWidget
SWidget
UWidget
SWidget
UWidget
#UE4 | #gcconf2020
Widget Lifecycle
Create OnScreen Tick Remove Destroy
生成
#UE4 | #gcconf2020
Create Widget
1度だけ呼ばれる生成処理開始
UWidgetと
関連するUObjectの生成
Create
CreateWidget NewObject Initialize
#UE4 | #gcconf2020
Create Widget (負荷ポイント)
Widgetと関連
オブジェクトのロード
Create
CreateWidget NewObject Initialize
Widgetと関連
オブジェクトの生成
参照オブジェクトの過多で
ロード時のヒッチ等に注意
#UE4 | #gcconf2020
Widget Lifecycle
Create OnScreen Tick Remove Destroy
追加
#UE4 | #gcconf2020
OnScreen Widget
OnScreen
AddToViewport TakeWidget RebuildWidget
Synchronize
Properties
Construct
Viewportに登録 SWidgetの生成開始
構築完了
(Begin Play)
UWidgetとSWidget
の同期
PreConstruct
構築完了
(Construction Script)
SWidgetの生成
#UE4 | #gcconf2020
OnScreen Widget (負荷ポイント)
OnScreen
AddToViewport TakeWidget RebuildWidget
Synchronize
Properties
Construct
Widget Blueprintの
初期化処理が集中
PreConstruct
スレート
インスタンス生成
対象が多いとヒッチ発生
初期化の負荷分散も検討
#UE4 | #gcconf2020
Widget Lifecycle
Create OnScreen Tick Remove Destroy
更新
#UE4 | #gcconf2020
Tick Widget
Tick
SlatePrepass Paint AddElement
子Widgetを含む
スケールの適用
子Widgetを含む
Render Batchの作成
#UE4 | #gcconf2020
Tick Widget (負荷ポイント)
Tick
SlatePrepass Paint AddElement
全てのWidgetを
走査してスケール適用
全てのWidgetを
走査して描画情報作成
Widgetが多いと負荷になりがち
キャッシュを使って負荷制御を
#UE4 | #gcconf2020
Tick Widget
レイアウト決め 下書き作成 設計書作成
SlatePrepass Paint AddElement
…… x2.0
…… x1.0
…… x2.0
…… x1.0
…… x1.2
Id:1 Id:2
Id:3Id:4
#UE4 | #gcconf2020
スレッドを跨いで描画情報を通知
Game Threadは描画に必要な情報を作成するだけ
 ⇒ Render ThreadがRHIコマンドを作成してGPUに転送
Game Thread
Render Thread
レイアウト
決め
下書き
作成
設計書
作成
RHI Cmd
作成