C++で作成したウィジェットが描画されない

UUserWidgetクラスをC++で継承して、画像を配置するウィジェットを作成しました。
しかし、それをAddToViewPortで追加しても、画面上に何もウィジェットが表示されません。
後々実装する機能の関係上、ブループリントではなくC++で実装する必要があり、非常に困っています。
もし、原因がわかる方がいらっしゃったら、ご教示いただけませんでしょうか。

大まかな流れ
画像を作成
UImage* TestWidget = NewObject(this, TestWidget);

画像を配置
UCanvasPanelSlot* TestWidgetSlot = RootCanvas->AddChildToCanvas(TestWidget);

位置を調整
TestWidgetSlot->SetPosition(FVector2D(100, 100));
TestWidgetSlot->SetSize(FVector2D(200, 50));

レベルブループリントでウィジェットを描画
BeginPlay→CreateWidget→AddToViewPort

試した事
・Visibilityの指定
setVisibility(ESlateVisibility::Visible);
RootCanvas->SetVisibility(ESlateVisibility::SelfHitTestInvisible);
ImageWidget->SetVisibility(ESlateVisibility::Visible);

・ボタンなど他のもので試す
画像と同じく表示されませんでした。

エンジン、PCの再起動、リビルド
解決しませんでした。

作業環境
Windows10
UnrealEngine5.4
VisualStudio

エラー、ログ
エラーは発生していません。
ログを確認しても、警告などは見当たりません。

具体的な記述内容

コンストラクタでの記述
: Super(ObjectInitializer)
RootCanvas = CreateDefaultSubobject(TEXT(“RootCanvas”));

NativeConstructでの記述
Super::NativeConstruct();
RootCanvas->SetVisibility(ESlateVisibility::SelfHitTestInvisible);
WidgetTree->RootWidget = RootCanvas;
UImage* TestWidget = NewObject(this, TestWidget);
WidgetImage->SetVisibility(ESlateVisibility::Visible);
UCanvasPanelSlot* TestWidgetSlot = RootCanvas->AddChildToCanvas(TestWidget);
TestWidgetSlot->SetPosition(FVector2D(200, 200));
TestWidgetSlot->SetSize(FVector2D(200, 200));

InitializeからNativeConstructまでの間にSlateのポインタが参照されてしまうので、NativeConstructのタイミングでWidgetTreeのRootWidgetを書き換えるのでは遅い、という理解です

WidgetReflectorで参照したところ、NativeConstructで記述した場合は、RootWidgetが見つからなかった場合のためのSSpacerが挿入されていました ( UUserWidget::RebuildWidget を参照のこと)

UUserWidget::Initialize() をオーバーライドし、そこでRootWidget(CanvasPanel)の追加をしてみてください
その他の要素の設定はNativeConstructでOKです


bool UMyUserWidget::Initialize()
{
    bool Result = Super::Initialize();

    if (!HasAnyFlags(RF_ClassDefaultObject))
    {
        RootCanvas = WidgetTree->ConstructWidget<UCanvasPanel>();
        WidgetTree->RootWidget = RootCanvas;
    }

    return Result;
}

void UMyUserWiget::NativeConstruct()
{
    // ...
}

参考:

1 Like

ありがとうございます。
大変申し訳ないのですが、私自身UE5を始めたばかりな上、プログラミングに関しても、少しJavaScriptを触った程度の人間なので、まだまだ知識不足なところがありまして…色々と調べて自分なりに理解はできたのですが、それが合ってるのか自信がありません。
もし、明らかに間違っているような部分があれば、教えていただけませんか?

1.Slateというのは、UMGの基盤的なフレームワーク

2.初期化には、デフォルトのコンストラクタやNativeCostruct以外にも複数のプロセスがある
(具体的にはデフォルトコンストラクタ→PostInitProperties→Initialize→NativePreConstruct→NativeConstruct?)

3.UMGの階層構造とは別に、必須なものを定義したSlateの階層構造があり、RootWidgetはSlate側の階層構造に属している。

4.Slateの階層構造は、Initializeの段階で確定しており、後から変更するのはあまり良くない

5.NativeCostructで記述した
WidgetTree->RootWidget = RootCanvas
という処理は、あくまでRootWidgetに対するポインタを書き換えただけで、そのものを書き換えている訳では無い。

6.そのため、InitializeでSlateの階層構造が確定するよりも前に、RootWidgetに設定する必要がある。

7.画像やボタンなどのウィジェットはUMG側の階層構造に属しているため、NativeCostructの段階では確定しておらず、後から追加するのは問題ない。

WidgetをC++「のみ」で構築することはおすすめしません
UnrealEngineに不慣れであれば特に、です

Widget(UMG)は、デザイナーで作ることを前提とした機能です

古い(UE4時代の)記事ですが、C++でWidgetを扱う場合のベストプラクティスが公開されています
https://www.unrealengine.com/ja/tech-blog/umg-best-practices

C++からWidget要素にアクセスする必要がある場合は、BindWidgetを使うのが良いでしょう
特定の前準備は必要ですが、初期化時に紐づけてアクセス可能になるようにしてくれます

4.Slateの階層構造は、Initializeの段階で確定しており、後から変更するのはあまり良くない

良くないというか、本来C++のみでRootから動的に構築されることは運用外かと
末端を変更することは問題ありません
ですが、C++の利用はロジック部分だけにとどめ、見た目に関わる部分はWidgetBlueprintで作成するべきでしょう
後々実装する機能がどのようなものであれ、C++のみですべて実装されなければならない、という状況にはならないはずです

3.UMGの階層構造とは別に、必須なものを定義したSlateの階層構造があり、RootWidgetはSlate側の階層構造に属している。

5.NativeCostructで記述した
WidgetTree->RootWidget = RootCanvas
という処理は、あくまでRootWidgetに対するポインタを書き換えただけで、そのものを書き換えている訳では無い。

UWidgetを書き換えただけで、SWidgetを書き換えているわけではない、というところがポイントです
正確にはWidgetTreeに対する変更ですが…

Widgetは、ロジックを保持するためのUWidget層と対応するように、実際に画面に描画するためのSlate実装であるSWidget層を持っています

Slateに対する変更監視はSlateを見ればよいため、初期化時にSWidgetのポインタを取得する、という実装になっているのでしょう
UUserWidget::RebuildWidgetが再度呼ばれなければこのポインタが書き換わることがないため、WidgetTreeだけを書き換えても意味がない、ということです

1 Like

ウィジェットの位置情報などを持った構造体をActerなどから受け取って、それを元に動的にウィジェットの配置を変更するという事がやりたかったのですが、
おっしゃる通り、C++にこだわりすぎていました。
添付していただいた記事を参考に、C++とWidgetBlueprintを適宜使い分けてUIの制作をしていこうと思います。
大変勉強になりました。本当にありがとうございます。