LyraLog3 UI扩展点分析

UIExtension 系统全流程

UCLASS(MinimalAPI)
class UUIExtensionSubsystem : public UWorldSubsystem
{
    GENERATED_BODY()

private:
    /*
     * UMG上的扩展点说:我这里有个插槽,Tag=X,接受 UUserWidget 子类
     * 通过:
     * ExtensionSubsystem->RegisterExtensionPoint
     * ExtensionSubsystem->RegisterExtensionPointForContext
     */
    typedef TArray<TSharedPtr<FUIExtensionPoint>> FExtensionPointList;
    TMap<FGameplayTag, FExtensionPointList> ExtensionPointMap;

    /*
     * GameFeatureFeature或者技能说: 我有个 Widget 类,要挂到 Tag=X 的插槽上
     * 通过 ExtensionSubsystem->RegisterExtensionAsWidgetForContext
     */
    typedef TArray<TSharedPtr<FUIExtension>> FExtensionList;
    TMap<FGameplayTag, FExtensionList> ExtensionMap;
};

时序

┌──────────────── 插槽侧(Consumer)─────────────────┐
│                                                    │
│  1. GameFeature 激活                               │
│     └─ AddWidgets → PushContentToLayer_ForPlayer   │
│        把 Layout Widget 推到 UI Layer 上            │
│        (Layout 里放了 UUIExtensionPointWidget)      │
│                                                    │
│  2. UUIExtensionPointWidget::RebuildWidget()        │
│     └─ RegisterExtensionPoint(Tag, Callback)       │
│     └─ RegisterExtensionPointForContext             │
│        (Tag, LocalPlayer, Callback)                │
│     └─ RegisterExtensionPointForContext             │
│        (Tag, PlayerState, Callback)                │
│     └─ 写入 ExtensionPointMap[Tag]                  │
│     └─ NotifyExtensionPointOfExtensions()          │
│        ↓ 反查 ExtensionMap 看有没有已注册的 Widget   │
│        ↓ 如果有且 Context 匹配 → 回调 → 创建 Widget │
│                                                    │
└────────────────────────────────────────────────────┘

┌──────────────── Widget侧(Producer)──────────────┐
│                                                    │
│  3. GameFeatureAction_AddWidgets::AddWidgets        │
│     └─ RegisterExtensionAsWidgetForContext          │
│        (SlotID, LocalPlayer, WidgetClass, -1)      │
│     └─ 写入 ExtensionMap[Tag]                       │
│     └─ NotifyExtensionPointsOfExtension(Added)     │
│        ↓ 正查 ExtensionPointMap 看有没有匹配的插槽  │
│        ↓ DoesExtensionPassContract 检查:           │
│          ① Context 匹配(null==null 或 same obj)  │
│          ② Data 是 AllowedDataClasses 的子类        │
│        ↓ 如果通过 → 触发插槽的 Callback             │
│                                                    │
│  4. UUIExtensionPointWidget::OnAddOrRemoveExtension │
│     └─ Action == Added                              │
│     └─ Data 是 UClass (WidgetClass)                 │
│     └─ CreateEntryInternal(WidgetClass)             │
│     └─ Widget 显示出来!                             │
│                                                    │
└────────────────────────────────────────────────────┘

UMG扩展点在调用 RegisterExtensionPoint 和 RegisterExtensionPointForContext的时候,会填一个回调,

回调里面会创建UserWidget和 AddChild

void UUIExtensionPointWidget::OnAddOrRemoveExtension(EUIExtensionAction Action, const FUIExtensionRequest& Request)
{
    if (Action == EUIExtensionAction::Added)
    {
        UObject* Data = Request.Data;

        TSubclassOf<UUserWidget> WidgetClass(Cast<UClass>(Data));
        if (WidgetClass)
        {
            UUserWidget* Widget = CreateEntryInternal(WidgetClass);
            ExtensionMapping.Add(Request.ExtensionHandle, Widget);
        }
        else if (DataClasses.Num() > 0)
        {
            // ... 其他情况
        }
    }
    else
    {
        if (UUserWidget* Extension = ExtensionMapping.FindRef(Request.ExtensionHandle))
        {
            RemoveEntryInternal(Extension);
            ExtensionMapping.Remove(Request.ExtensionHandle);
        }
    }
}

UUIExtensionSubsystem::RegisterExtensionPointForContext的时候,注册扩展点

FUIExtensionPointHandle UUIExtensionSubsystem::RegisterExtensionPointForContext(const FGameplayTag& ExtensionPointTag, UObject* ContextObject, EUIExtensionPointMatch ExtensionPointTagMatchType, const TArray<UClass*>& AllowedDataClasses, FExtendExtensionPointDelegate ExtensionCallback)
{
    // ... check

    FExtensionPointList& List = ExtensionPointMap.FindOrAdd(ExtensionPointTag);
    TSharedPtr<FUIExtensionPoint>& Entry = List.Add_GetRef(MakeShared<FUIExtensionPoint>());

    // ...

    Entry->Callback = MoveTemp(ExtensionCallback);
    NotifyExtensionPointOfExtensions(Entry);

    return ...;
}

void UUIExtensionSubsystem::NotifyExtensionPointOfExtensions(TSharedPtr<FUIExtensionPoint>& ExtensionPoint)
{
    for (FGameplayTag Tag = ExtensionPoint->ExtensionPointTag; Tag.IsValid(); Tag = Tag.RequestDirectParent())
    {
        // 回去找,是否有 有要加到TAG上的Widget
        if (const FExtensionList* ListPtr = ExtensionMap.Find(Tag))
        {
            FExtensionList ExtensionArray(*ListPtr);
            for (const TSharedPtr<FUIExtension>& Extension : ExtensionArray)
            {
                // 类型判断,通常Widget必须是 UserWidget的子类
                // 上下文的判断 null == null 或者 LocalPlayerController对上
                if (ExtensionPoint->DoesExtensionPassContract(Extension.Get()))
                {
                    // 这里会创建回调,触发创建Widget
                    FUIExtensionRequest Request = CreateExtensionRequest(Extension);
                    ExtensionPoint->Callback.ExecuteIfBound(EUIExtensionAction::Added, Request);
                }
            }
        }
        // ...
    }
}

这里涉及到,我有一个扩展点TAG,我有一个Widget要加到扩展点TAG。谁先谁后,是什么情况。

反过来 UGameFeatureAction_AddWidgets 的时候

RegisterExtensionAsData

NotifyExtensionPointsOfExtension

我有一个类,要加在扩展点TAG上,这时候会去找扩展点是不是在,如果在也会触发到刚刚的回调。

void UUIExtensionSubsystem::NotifyExtensionPointsOfExtension(EUIExtensionAction Action, TSharedPtr<FUIExtension>& Extension)
{
    bool bOnInitialTag = true;
    for (FGameplayTag Tag = Extension->ExtensionPointTag; Tag.IsValid(); Tag = Tag.RequestDirectParent())
    {
        if (const FExtensionPointList* ListPtr = ExtensionPointMap.Find(Tag))
        {
            // Copy in case there are removals while handling callbacks
            FExtensionPointList ExtensionPointArray(*ListPtr);

            for (const TSharedPtr<FUIExtensionPoint>& ExtensionPoint : ExtensionPointArray)
            {
                if (bOnInitialTag || (ExtensionPoint->ExtensionPointTagMatchType == EUIExtensionPointMatch::PartialMatch))
                {
                    if (ExtensionPoint->DoesExtensionPassContract(Extension.Get()))
                    {
                        FUIExtensionRequest Request = CreateExtensionRequest(Extension);
                        ExtensionPoint->Callback.ExecuteIfBound(Action, Request);
                    }
                }
            }
        }

        bOnInitialTag = false;
    }
}

综上所述:

UEqZeroGameplayAbility_WithWidget

所以这个技能give的时候 RegisterExtensionAsWidgetForContext

NotifyExtensionPointsOfExtension

如果扩展点有,那UI就是加上去的

上一篇
下一篇