日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

深入Unreal蓝图开发:自定义蓝图节点(中)

發布時間:2023/12/20 编程问答 41 豆豆
生活随笔 收集整理的這篇文章主要介紹了 深入Unreal蓝图开发:自定义蓝图节点(中) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

通過本系列文章上篇的介紹,我們已經可以創建一個“沒什么用”的藍圖節點了。要想讓它有用,關鍵還是上篇中說的典型應用場景:動態添加Pin,這篇博客就來解決這個問題。

目標

和上篇一樣,我還將通過一個盡量簡單的節點,來說明"可動態添加Pin的藍圖節點"的實現過程,讓大家盡量聚焦在“藍圖自定義節點”這個主題上。

設想這樣一個節點:Say Something,把輸入的N個字符串連接起來,然后打印輸出。也就是說,這個節點的輸入Pin是可以動態添加的。我們將在上篇的那個工程基礎上實現這個自定義節點。最終實現的效果如下圖所示:

下面我們還是來仔細的過一遍實現步驟吧!

創建Blueprint Graph節點類型

首先,我們還是需要創建一個class UK2Node的派生類,這個過程在上篇中已經詳細說過了,照單炒菜,很容易就創建了下圖這樣一個空的自定義節點,這里就不贅述了。不清楚的話,可以返回去在照著上篇做就好了。

創建自定義的節點Widget

我們要動態增加Pin的話,需要在節點上顯示一個"加號按鈕",點擊之后增加一個“input pin”。這就不能使用默認的Blueprint Graph Node Widget了,需要對其進行擴展。這個擴展的思路和前面一樣,也是找到特定的基類,重載其虛函數即可,這個基類就是class SGraphNodeK2Base。我們要重載的兩個核心的函數是:

  • CreateInputSideAddButton(),創建我們需要的添加輸入Pin的按鈕;
  • OnAddPin(),響應這個按鈕的操作;
  • 來看一下最簡化的代碼吧:
    SGraphNodeSaySomething.h

    class SGraphNodeSaySomething : public SGraphNodeK2Base { public:SLATE_BEGIN_ARGS(SGraphNodeSaySomething){}SLATE_END_ARGS()void Construct(const FArguments& InArgs, UBPNode_SaySomething* InNode); protected:virtual void CreateInputSideAddButton(TSharedPtr<SVerticalBox> InputBox) override;virtual FReply OnAddPin() override; };

    SGraphNodeSaySomething.cpp

    void SGraphNodeSaySomething::Construct(const FArguments& InArgs, UBPNode_SaySomething* InNode) {this->GraphNode = InNode;this->SetCursor( EMouseCursor::CardinalCross );this->UpdateGraphNode(); }void SGraphNodeSaySomething::CreateInputSideAddButton(TSharedPtr<SVerticalBox> InputBox) {FText Tmp = FText::FromString(TEXT("Add word"));TSharedRef<SWidget> AddPinButton = AddPinButtonContent(Tmp, Tmp);FMargin AddPinPadding = Settings->GetInputPinPadding();AddPinPadding.Top += 6.0f;InputBox->AddSlot().AutoHeight().VAlign(VAlign_Center).Padding(AddPinPadding)[AddPinButton]; }FReply SGraphNodeSaySomething::OnAddPin() { }

    如果你接觸過Unreal Slate的話,上面這個Slate Widget的代碼很容易看懂啦,如果你沒有玩過Slate。。。。Slate是虛幻自己的一套 Immediate Mode UI framework,建議先過一下官方文檔。

    最后,因為這個基類:SGraphNodeK2Base,屬于GraphEditor模塊,所以要修改MyBlueprintNodeEditor.Build.cs,把它添加到PrivateDependencyModuleNames:

    PrivateDependencyModuleNames.AddRange(new string[] {"UnrealEd","GraphEditor","BlueprintGraph","KismetCompiler","MyBlueprintNode"});

    擴展藍圖編輯器的節點Widget

    OK,上面我們已經創建了兩個類,分別是:

  • class UBPNode_SaySomething : public UK2Node
  • class SGraphNodeSaySomething : public SGraphNodeK2Base
  • 下面我們就需要讓藍圖編輯器知道:創建UBPNode_SaySomething對象的時候,需要使用SGraphNodeSaySomething這個Widget。

    添加自定義Node Widget的兩種方式(參見引擎源碼class FNodeFactory):

  • 重載UEdGraphNode::CreateVisualWidget()函數,例如:
  • TSharedPtr<SGraphNode> UNiagaraNode::CreateVisualWidget() {return SNew(SNiagaraGraphNode, this); }
  • 使用 class FEdGraphUtilities 注冊 class FGraphPanelNodeFactory對象,例如:
  • void FBehaviorTreeEditorModule::StartupModule() {GraphPanelNodeFactory_BehaviorTree = MakeShareable( new FGraphPanelNodeFactory_BehaviorTree() );FEdGraphUtilities::RegisterVisualNodeFactory(GraphPanelNodeFactory_BehaviorTree); }

    在這里,我們使用第一種方式,也就是在class UBPNode_SaySomething中重載父類的虛函數CreateVisualWidget()。

    TSharedPtr<SGraphNode> UBPNode_SaySomething::CreateVisualWidget() {return SNew(SGraphNodeSaySomething, this); }

    完成上述代碼之后,運行藍圖編輯器,添加Say Something節點,就可以看到這個Widget了:

    動態增加輸入參數變量

    當用戶點擊“Add Word +”按鈕時,SGraphNodeSaySomething::OnAddPin()會被調用,下面是它的實現代碼:

    FReply SGraphNodeSaySomething::OnAddPin() {UBPNode_SaySomething* BPNode = CastChecked<UBPNode_SaySomething>(GraphNode);const FScopedTransaction Transaction(NSLOCTEXT("Kismet", "AddArgumentPin", "Add Argument Pin"));BPNode->Modify();BPNode->AddPinToNode();FBlueprintEditorUtils::MarkBlueprintAsModified(BPNode->GetBlueprint());UpdateGraphNode();GraphNode->GetGraph()->NotifyGraphChanged();return FReply::Handled(); }

    上面這段代碼主要是響應用戶的UI操作,添加Pin的核心操作,還是放在UBPNode_SaySomething::AddPinToNode()這個函數里面去實現的:

    void UBPNode_SaySomething::AddPinToNode() {TMap<FString, FStringFormatArg> FormatArgs= {{TEXT("Count"), ArgPinNames.Num()}};FName NewPinName(*FString::Format(TEXT("Word {Count}"), FormatArgs));ArgPinNames.Add(NewPinName);CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_String, NewPinName); }

    現在我們就可以在藍圖編輯器里面操作添加輸入Pin了 :

    動態刪除Pin

    如果用戶想要刪除某個輸入變量Pin,他需要在那個Pin上點擊鼠標右鍵,呼出Context Menu,選擇“刪除”菜單項將其移除。下面我們就看看這個操作是如何實現的。

    我們可以通過重載void UEdGraphNode::GetContextMenuActions(const FGraphNodeContextMenuBuilder& Context) const來定制Context Menu。

    void UBPNode_SaySomething::GetContextMenuActions(const FGraphNodeContextMenuBuilder & Context) const {Super::GetContextMenuActions(Context);if (Context.bIsDebugging)return;Context.MenuBuilder->BeginSection("UBPNode_SaySomething", FText::FromString(TEXT("Say Something")));if (Context.Pin != nullptr){if (Context.Pin->Direction == EGPD_Input && Context.Pin->ParentPin == nullptr){Context.MenuBuilder->AddMenuEntry(FText::FromString(TEXT("Remove Word")),FText::FromString(TEXT("Remove Word from input")),FSlateIcon(),FUIAction(FExecuteAction::CreateUObject(this, &UBPNode_SaySomething::RemoveInputPin, const_cast<UEdGraphPin*>(Context.Pin))));}}// end of ifContext.MenuBuilder->EndSection(); }

    這個函數的實現很直白啦,就是操作MenuBuilder,添加菜單項,并綁定UIAction到成員函數UBPNode_SaySomething::RemoveInputPin,接下來就是實現這個函數了。

    void UBPNode_SaySomething::RemoveInputPin(UEdGraphPin * Pin) {FScopedTransaction Transaction(FText::FromString("SaySomething_RemoveInputPin"));Modify();ArgPinNames.Remove(Pin->GetFName());RemovePin(Pin);FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(GetBlueprint()); }

    也很簡單,就是直接調用父類的RemovePin(),并同步處理一下自己內部的狀態變量就好了。

    實現這個藍圖節點的編譯

    通過前面的步驟,藍圖編輯器的擴展就全部完成了,接下來就是最后一步了,通過擴展藍圖編譯過程來實現這個節點的實際功能。

    我們延續上篇的思路來實現這個節點的功能,也就是重載UK2Node::ExpandNode()函數。

    核心的問題是如何把當前的所有的輸入的Pin組合起來? 答案很簡單,把所有輸入的Pin做成一個TArray<>,這樣就可以傳入到一個UFunction來調用。

    首先我們在 class UMyBlueprintFunctionLibrary 中添加一個函數:

    UCLASS() class MYBLUEPRINTNODE_API UMyBlueprintFunctionLibrary : public UBlueprintFunctionLibrary {GENERATED_BODY()public:UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true"))static void SaySomething_Internal(const TArray<FString>& InWords); };

    然后,仍然與上篇相同,使用一個 class UK2Node_CallFunction 節點實例對象來調用這個UFunction,不同的是,我們需要使用一個 class UK2Node_MakeArray 節點的實例來把收集所有的動態生成的輸入Pin。下面是實現的代碼:

    void UBPNode_SaySomething::ExpandNode(FKismetCompilerContext & CompilerContext, UEdGraph * SourceGraph) {Super::ExpandNode(CompilerContext, SourceGraph);UEdGraphPin* ExecPin = GetExecPin();UEdGraphPin* ThenPin = GetThenPin();if (ExecPin && ThenPin) {// create a CallFunction nodeFName MyFunctionName = GET_FUNCTION_NAME_CHECKED(UMyBlueprintFunctionLibrary, SaySomething_Internal);UK2Node_CallFunction* CallFuncNode = CompilerContext.SpawnIntermediateNode<UK2Node_CallFunction>(this, SourceGraph);CallFuncNode->FunctionReference.SetExternalMember(MyFunctionName, UBPNode_SaySomething::StaticClass());CallFuncNode->AllocateDefaultPins();// move exec pinsCompilerContext.MovePinLinksToIntermediate(*ExecPin, *(CallFuncNode->GetExecPin()));CompilerContext.MovePinLinksToIntermediate(*ThenPin, *(CallFuncNode->GetThenPin()));// create a "Make Array" node to compile all argsUK2Node_MakeArray* MakeArrayNode = CompilerContext.SpawnIntermediateNode<UK2Node_MakeArray>(this, SourceGraph);MakeArrayNode->AllocateDefaultPins();// Connect Make Array output to function argUEdGraphPin* ArrayOut = MakeArrayNode->GetOutputPin();UEdGraphPin* FuncArgPin = CallFuncNode->FindPinChecked(TEXT("InWords"));ArrayOut->MakeLinkTo(FuncArgPin);// This will set the "Make Array" node's type, only works if one pin is connected.MakeArrayNode->PinConnectionListChanged(ArrayOut);// connect all arg pin to Make Array inputfor (int32 i = 0; i < ArgPinNames.Num(); i++) {// Make Array node has one input by defaultif (i > 0)MakeArrayNode->AddInputPin();// find the input pin on the "Make Array" node by index.const FString PinName = FString::Printf(TEXT("[%d]"), i);UEdGraphPin* ArrayInputPin = MakeArrayNode->FindPinChecked(PinName);// move input word to array UEdGraphPin* MyInputPin = FindPinChecked(ArgPinNames[i], EGPD_Input);CompilerContext.MovePinLinksToIntermediate(*MyInputPin, *ArrayInputPin);}// end of for}// break any links to the expanded nodeBreakAllNodeLinks(); }

    核心步驟來講解一下:

  • 創建了一個class UK2Node_CallFunction的實例,然后把自身節點的兩端的Exec Pin重定向到這個Node的兩端;
  • 使用“函數參數名稱”找到UK2Node_CallFunction節點的輸入Pin,把它連接到一個新建的UK2Node_MakeArray的節點實例上;
  • 把自己所有的輸入變量Pin重定向到UK2Node_MakeArray的輸入上(需要為它動態添加新的Pin);
  • 結束語

    今天涉及到的class稍微有點多,我整理了一個UML靜態結構圖,看看這幾個classes直接的關系以及它們所在的模塊。完整源代碼仍然是在我的GitHub:https://github.com/neil3d/UnrealCookBook/tree/master/MyBlueprintNode

    至此,通過派生class UK2Node和class SGraphNodeK2Base來擴展Blueprint Graph Editor,我們可以自己定義藍圖節點,以及編輯器中的Node Widget,可以添加按鈕,以及其他任何你想要做的東西。通過這個定制化的Node Widget,可以實現編輯時對Blueprint Graph Node的交互控制。至此,我們已經掌握了最強大的藍圖節點的擴展方法。動態添加Pin這個問題說明白之后,下篇將寫什么呢?先賣個關子,且待下回分解吧~

    總結

    以上是生活随笔為你收集整理的深入Unreal蓝图开发:自定义蓝图节点(中)的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。