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就是加上去的