UnrealC++でSlateのクラスをキャストする


はじめに

最近作っているプラグインでFSlateApplicationから取得したウィンドウからBlueprintやMaterialのグラフエディタのインスタンスを取得するために、Widgetの子を再帰的に辿って取得する処理が必要になり、そのためにSlateのクラス(SWidgetを継承したクラス)をキャストする必要がありました。

SWidgetUObjectを継承していないため、標準のCast関数テンプレートは使えません。

キャストする関数(マクロ?)

GraphPrinterCore.h

GraphPrinterCore.h
namespace CastSlateWidgetPrivate
{
    template<class To, class From>
    TSharedPtr<To> CastSlateWidget(TSharedPtr<From> FromPtr, const FName& ToClassName)
    {
        static_assert(TIsDerivedFrom<From, SWidget>::IsDerived, "This implementation wasn't tested for a filter that isn't a child of SWidget.");
        static_assert(TIsDerivedFrom<To, SWidget>::IsDerived, "This implementation wasn't tested for a filter that isn't a child of SWidget.");

        if (FromPtr.IsValid())
        {
            if (FromPtr->GetType() == ToClassName)
            {
                return StaticCastSharedPtr<To>(FromPtr);
            }
        }

        return nullptr;
    }
}
#define CAST_SLATE_WIDGET(ToClass, FromPtr) CastSlateWidgetPrivate::CastSlateWidget<ToClass>(FromPtr, #ToClass)

SWidgetUObjectではないですが、自身のクラスの名前を保持しているためキャスト元のSWidget::GetTypeから取得した名前とキャスト先のクラス名を文字列化させたものを比較して、同一ならStaticCastSharedPtrでキャストします。

StaticCastSharedPtrはUObject以外(例えば構造体など)もTSharedPtrでラップすればキャストできる関数です。しかし、名前がDynamicCastSharedPtrでない所から分かる通り、もしキャストに失敗してもnullptrを返してくれません。そのため、クラス名の文字列での判定をしています。

Engine\Source\Runtime\SlateCore\Public\Widgets\DeclarativeSyntaxSupport.h
#define SNew( WidgetType, ... ) \
    MakeTDecl<WidgetType>( #WidgetType, __FILE__, __LINE__, RequiredArgs::MakeRequiredArgs(__VA_ARGS__) ) <<= TYPENAME_OUTSIDE_TEMPLATE WidgetType::FArguments()


#define SAssignNew( ExposeAs, WidgetType, ... ) \
    MakeTDecl<WidgetType>( #WidgetType, __FILE__, __LINE__, RequiredArgs::MakeRequiredArgs(__VA_ARGS__) ) . Expose( ExposeAs ) <<= TYPENAME_OUTSIDE_TEMPLATE WidgetType::FArguments()

余談ですが、軽く調べた感じだとSWidget::GetTypeから取得できる文字列はSlateを書く際に使うSNewのマクロ内で引数のクラス名を文字列化して変数に入れているようでした。
そのため、UObject系列のクラス名が接頭辞を抜いた形(例えばUObjectなら「Object」)になっているのに対して、SWidget::GetTypeから取得できる名前は接頭辞付きなため、上記コードのようにマクロでキャスト先のクラス名をそのまま文字列化させています。

GraphPrinterCore.cpp

GraphPrinterCore.cpp
TSharedPtr<SGraphEditor> FGraphPrinterCore::FindGraphEditor(TSharedPtr<SWidget> SearchTarget)
{
    TArray<TSharedPtr<SWidget>> ChildWidgets;
    CollectAllChildWidgets(SearchTarget, ChildWidgets);

    for (const auto& ChildWidget : ChildWidgets)
    {
        TSharedPtr<SGraphEditor> GraphEditor = CAST_SLATE_WIDGET(SGraphEditor, ChildWidget);
        if (GraphEditor.IsValid())
        {
            return GraphEditor;
        }
    }

    return nullptr;
}

使用する際はこのようになります。
こちらははじめにでお話した子ウィジェットからグラフエディタを取得する処理です。

おわりに

今回はウィンドウから子ウィジェットを走査する形は単純に重そうなので他の処理にするのも良いですが、この方法だとこちらで紹介されているWidget Reflectorで確認できるWidgetに必ず辿り着ける点が魅力かと思われます。取得方法は後で考えるからとにかくこのWidgetに対して何かしたいなどの試作作業でも役に立つかもしれません。
そもそもSlateのクラスをキャストしようとすること自体あまりないかと思いますが、もしやろうとしている方の参考になれば幸いです。

明日は、@shiratori1221さんの「GameplayAbilityとアニメーションモンタージュを使ったコンボについて」です!