UE4插件創建



分類:

  • Blank
  • BlueprintLibrary
  • ContentOnly
  • Editor Toolbar Button
  • Editor Standalone Window
  • Editor Mode
  • Third Party Library

插件中的代碼

生成Visual Studio或Xcode的項目文件時,含有 Source 文件夾(包含 .Build.cs 文件)的插件將被添加到項目文件,以便導航到其源代碼。編譯游戲項目時,UBT將自動編譯此類插件。

插件可含有任意數量的模塊源目錄。多數插件僅有一個模塊(但可創建多個模塊,例如插件包含純編輯器功能時),及游戲期間要運行的其他代碼。

插件源文件的大部分布局與引擎中其他C++模塊相同。

在模塊的 Source 目錄(或其子目錄)內,插件可在標頭文件中聲明新反映的類型(UCLASSUSTRUCT 等)。引擎的構建系統將檢測此類文件,並按需要生成代碼支持新類型。需遵守C++模塊中使用 Uobjects 時的一般規則,例如在模塊的源文件中包含生成的標頭文件和模塊 generated.inl 文件。

UE4支持共生模塊和插件。通過在自身.uproject文件中啟用插件,項目模塊可依賴插件。類似地,通過在自身.uplugin文件中啟用其他插件,插件可表明依賴性。但其中有一項重要限制:插件和模塊將拆分為若干層級,僅能依賴同一級或更高級的其他插件或模塊。例如,項目模塊可依賴引擎模塊,但引擎模塊無法依賴項目模塊。這是因為引擎(及其所有插件和模塊)的級別高於項目,須能在無項目的情況下編譯。以下圖表展示了項目和模塊間的依賴性層級:

PluginAndModuleDependency.png

箭頭表明可能的依賴性。各插件或模塊類型可依賴同級別或更高級別的其他插件或模塊類型。

引擎插件

虛幻引擎4的 Engine 目錄下包含部分內置插件。引擎插件和項目插件類似,但可用於所有項目。此類插件通常由引擎和工具程序員創建,目的在於提供可在多個項目中使用並能在單一位置維護的基線功能。利用此功能,用戶可直接添加或覆蓋引擎功能,而無需修改引擎代碼。

項目中的插件

插件位於項目目錄的 Plugins 子文件夾下,將在引擎或編輯器啟動時被探測和加載。

如插件包含具有 Source 文件夾(和 .Build.cs 文件)的模塊,插件代碼將被自動添加到生成的C++項目文件,以便在開發項目時開發插件。編譯項目時,有可用源的插件都被作為游戲依賴項進行編譯。

項目生成器將忽略無 Source 文件夾的插件,其不會出現在C++項目文件中,但若存在二進制文件,啟動時仍將加載此類插件。

目前,無法將插件配置文件與項目打包。未來版本中可能會支持此功能,但目前需手動將此類文件復制到項目的 Config 文件夾。

插件描述文件

插件描述文件是命名以 .uplugin 結尾的文件。文件名的第一部分固定為插件命名。插件描述文件固定位於插件目錄中,啟動時將被引擎發現。

插件描述文件使用Json(JavaScript對象表示法)文件格式。

描述文件示例

此范例描述文件來自引擎的 UObjectPlugin

{
    "FileVersion" :3,
    "Version" :1,
    "VersionName" :"1.0",
    "FriendlyName" :"UObject Example Plugin",
    "Description" :"An example of a plugin which declares its own UObject type.This can be used as a starting point when creating your own plugin.",
    "Category" :"Examples",
    "CreatedBy" :"Epic Games, Inc.",
    "CreatedByURL" :"http://epicgames.com",
    "DocsURL" :"",
    "MarketplaceURL" :"",
    "SupportURL" :"",
    "EnabledByDefault" : true,
    "CanContainContent" : false,
    "IsBetaVersion" : false,
    "Installed" : false,
    "Modules" :
    [
        {
            "Name" :"UObjectPlugin",
            "Type" :"Developer",
            "LoadingPhase" :"Default"
        }
    ]
}

描述文件格式

描述文件為JSON格式的變量列表,此類列表為 FPluginDescriptor 類型。其中具有一個附加字段"FileVersion",其是文件結構中唯一的必需字段。"FileVersion"提供插件描述文件的版本,通常應設為引擎支持的最高版本(當前為"3")。由於此版本應用於插件描述文件的格式,而非插件本身,因此其可能不會頻繁變化,也不應隨插件后續版本的發行而改變。要與引擎舊版本進行最大化兼容,可使用較舊版本號,但不建議進行此操作。

欲了解其他支持字段的相關詳情,參見FPluginDescriptor API參考頁面。

bEnabledByDefault

插件入口

添加按鈕的方法

首先找到設置“顯示UI擴展點”便於我們看到我們添加按鈕的地方

給任意窗口的工具條、菜單條、菜單等添加按鈕

FUICommandList

FUICommandInfo

UI_COMMAND

可以實現:擴展已有菜單欄、添加新菜單欄、擴展已有工具欄,無法實現添加新工具欄

UI_COMMAND基礎認知

其實和UI_COMMAND相關模塊有三個,分別是FUICommandList,FUICommandInfo以及UI_COMMAND宏,他們的關系是FUICommandList包含FUICommandInfo,並且映射FUICommandInfo到委托,UI_COMMAND宏負責正式注冊FUICommandInfo。

UToolMenus——新版本的方法

先定位菜單類型通過UToolMenus::Get()->ExtendMenu獲得一個UToolMenu對象指針,這個Menu可能是菜單條可能是工具條

獲得到Menu后通過 Menu->FindOrAddSection定位段落Section,最后對段落添加Menu來拓展編輯器,添加Menu可以選擇AddMenuEntryWithCommandList(工具條就是AddEntry),也可以添加子菜單SubMenu

工具欄

//----------4.24版本的做法-------------
	//拓展到已有工具條 位置 Setting
	{
	   UToolMenu* ToolbarMenu = UToolMenus::Get()->ExtendMenu("LevelEditor.LevelEditorToolBar");
	   {
	      FToolMenuSection& Section = ToolbarMenu->FindOrAddSection("Settings");
	      {
	         FToolMenuEntry& Entry = Section.
	            AddEntry(FToolMenuEntry::InitToolBarButton(FFirstEditorToolBarButtonPluginCommands::Get().PluginAction));
	         Entry.SetCommandList(PluginCommands);
			//位置設置到段前
	         FToolMenuEntry& Entry1 = Section.AddEntry(
	          FToolMenuEntry::InitToolBarButton(FFirstEditorToolBarButtonPluginCommands::Get().PluginAction,
	               TAttribute<FText>(),
	               TAttribute<FText>(),
	               TAttribute<FSlateIcon>(),
	               NAME_None,
	               TOptional<FName>("YourEntryName")));
	         Entry1.SetCommandList(PluginCommands);
	         Entry1.InsertPosition.Position = EToolMenuInsertType::First;
	      }
	   }
	}
	// //嘗試添加到新工具條 位置 File 失敗
	// {
	//    UToolMenu* ToolbarMenu = UToolMenus::Get()->RegisterMenu("LevelEditor.NewLevelToolBar");
	//    {
	//       FToolMenuSection& Section = ToolbarMenu->FindOrAddSection("MyToolBarSection");
	//       {
	//          FToolMenuEntry& Entry = Section.
	//                    AddEntry(FToolMenuEntry::InitToolBarButton(FFirstEditorToolBarButtonPluginCommands::Get().PluginAction));
	//          Entry.SetCommandList(PluginCommands);
	//
	//       }
	//    }
	// }

菜單欄

//拓展到已有的菜單條
{
   UToolMenu* Menu = UToolMenus::Get()->ExtendMenu("LevelEditor.MainMenu.Window");//擴展菜單
   {
      FToolMenuSection& Section = Menu->FindOrAddSection("WindowLayout");//找到一個段
      FToolMenuEntry& Entry =  Section.AddMenuEntryWithCommandList(FFirstEditorToolBarButtonPluginCommands::Get().PluginAction, PluginCommands);//用命令列表天啊及菜單入口
      Entry.InsertPosition.Position = EToolMenuInsertType::First;//設置插入方式       
   // Section.AddSubMenu("New Sub Menu", FText::FromString("???"), FText::FromString("???"),
   //                     FNewToolMenuChoice(
   //                         FNewMenuDelegate::CreateRaw(
   //                             this, &FOfficial_EditorToolbarButtonModule::FillSubmenu)));
   }
}
//添加一個新的菜單欄
{

   UToolMenus* ToolMenus = UToolMenus::Get();
    UToolMenu* MyMenu = ToolMenus->RegisterMenu("LevelEditor.MainMenu.MySubMenu");//注冊一個新入口
    FToolMenuSection& Section = MyMenu->AddSection("MyMenuSection");
    Section.AddMenuEntryWithCommandList(FFirstEditorToolBarButtonPluginCommands::Get().PluginAction,PluginCommands);

}

FExtender——經典方法

分為幾個步驟

1、加載模塊

2、創建Extender對象

3、給Extender對象添加擴展屬性(在哪里擴展,綁定什么委托)、

4、將Extender對象綁定到對應模塊

void FMyEditorToolbarButtonModule::RegisterMenus()
{
    {//藍圖編輯器ToolBar
		FBlueprintEditorModule& BlueprintEditorModule = FModuleManager::LoadModuleChecked<FBlueprintEditorModule>("Kismet");
		BlueprintEditorModule.OnRegisterTabsForEditor().AddRaw(this, &FMyEditorToolbarButtonModule::OnBPToolBarRegister);
	}
    
	//Source\Engine\Source\Editor
	IAnimationBlueprintEditorModule& AnimationBlueprintEditorModule = FModuleManager::LoadModuleChecked<IAnimationBlueprintEditorModule>("AnimationBlueprintEditor");
	{//動畫藍圖拓展已有菜單欄
		TSharedPtr<FExtender> MenuExtender = MakeShareable(new FExtender);
		MenuExtender->AddMenuExtension("HelpApplication", EExtensionHook::After, PluginCommands, FMenuExtensionDelegate::CreateRaw(this, &FMyEditorToolbarButtonModule::AddMenuExtension));
		AnimationBlueprintEditorModule.GetMenuExtensibilityManager()->AddExtender(MenuExtender);
	}

	{//動畫藍圖拓展添加新菜單欄
		TSharedPtr<FExtender> MenuExtender = MakeShareable(new FExtender);
		MenuExtender->AddMenuBarExtension("Help", EExtensionHook::After, PluginCommands, FMenuBarExtensionDelegate::CreateRaw(this, &FMyEditorToolbarButtonModule::AddMenuBarExtension));
		AnimationBlueprintEditorModule.GetMenuExtensibilityManager()->AddExtender(MenuExtender);
	}

	{//動畫藍圖拓展添ToolBar
		TSharedPtr<FExtender> MenuExtender = MakeShareable(new FExtender);
		MenuExtender->AddToolBarExtension("Settings", EExtensionHook::After, PluginCommands, FToolBarExtensionDelegate::CreateRaw(this, &FMyEditorToolbarButtonModule::AddToolBarExtension));
		AnimationBlueprintEditorModule.GetToolBarExtensibilityManager()->AddExtender(MenuExtender);
	}

    /*
	If you try to do
	FBlueprintEditorModule& BlueprintEditorModule = FModuleManager::LoadModuleChecked<FBlueprintEditorModule>(“Kismet”),
	the code will compile, but the engine will crash when starting up.
	One solution I found was to change LoadingPhase in .uplugin file to PostEngineInit.
	*/
	{
		FBlueprintEditorModule& BlueprintEditorModule = FModuleManager::LoadModuleChecked<FBlueprintEditorModule>(TEXT("Kismet"));
		TSharedPtr<FExtender> MenuExtender = MakeShareable(new FExtender);
		MenuExtender->AddMenuExtension("HelpApplication", EExtensionHook::After, PluginCommands,
			FMenuExtensionDelegate::CreateRaw(this, &FMyEditorToolbarButtonModule::AddMenuExtension));
		BlueprintEditorModule.GetMenuExtensibilityManager()->AddExtender(MenuExtender);
	}
}
void FMyEditorToolbarButtonModule::AddMenuExtension(class FMenuBuilder& Builder)
{
	Builder.BeginSection(TEXT("MyButton"));
	Builder.AddMenuEntry(FMyEditorToolbarButtonCommands::Get().PluginAction);
	Builder.EndSection();
}

void FMyEditorToolbarButtonModule::AddMenuBarExtension(class FMenuBarBuilder& Builder)
{
	Builder.AddMenuEntry(FMyEditorToolbarButtonCommands::Get().PluginAction);
}
void FMyEditorToolbarButtonModule::AddToolBarExtension(class FToolBarBuilder& Builder)
{
	Builder.BeginSection(TEXT("MyButton"));
	Builder.AddToolBarButton(FMyEditorToolbarButtonCommands::Get().PluginAction);
	Builder.EndSection();
}

void FMyEditorToolbarButtonModule::OnBPToolBarRegister(FWorkflowAllowedTabSet& TabSet, FName Name, TSharedPtr<FBlueprintEditor> BP)
{
	TSharedPtr<FExtender> ToolBarExtender = MakeShareable(new FExtender);
	ToolBarExtender->AddToolBarExtension("Settings", EExtensionHook::After, PluginCommands, FToolBarExtensionDelegate::CreateRaw(this, &FMyEditorToolbarButtonModule::AddToolBarExtension));
	BP->AddToolbarExtender(ToolBarExtender);
}

一個模塊有擴展器容器,可以容納很多Extender

擴展的位置取決於Extender想Add什么Extension,可以是ToolBar、MenuBar、Menu

想改那個編輯器就取出那個編輯的ExtensibilityManager然后進行添加

能夠進行擴展的編輯器都繼承了IHasMenuExtensibility、IHasToolBarExtensibility的接口!!

class IAnimationBlueprintEditorModule : public IModuleInterface, public IHasMenuExtensibility, public IHasToolBarExtensibility
{
    
}

特殊的:藍圖的編輯器不能擴展ToolBar方法特殊,如果插件要動到藍圖編輯器,我們需要將uplugin中的LoadingPhase設置到PostEngineInit在引擎初始化之后啟動

class FBlueprintEditorModule : public IModuleInterface,
	public IHasMenuExtensibility
{
...
}

插件面板

編輯方法

直接使用Slate做

用Slate硬寫,大體上生成的UI樹長這樣,重要的是要綁定委托,MVC框架下,VC代碼寫一起

FGlobalTabmanager::Get()->RegisterNomadTabSpawner(MyTab1Name,
			FOnSpawnTab::CreateLambda([](const FSpawnTabArgs& SpawnTabArgs) {
			TSharedRef<SSlider> MySlider = SNew(SSlider).Value(0.2f);
			return SNew(SDockTab)
				.TabRole(ETabRole::NomadTab)
				[
					// Put your tab content here!
					SNew(SVerticalBox)
					+ SVerticalBox::Slot()
					[
						MySlider
					]
					+ SVerticalBox::Slot()
					.HAlign(HAlign_Center)
					.VAlign(VAlign_Center)
					[
						SNew(STextBlock)
						.Text_Lambda(
							[MySlider]() {
								int32 Result = (int32)(MySlider->GetValue() * 100);
								return FText::FromString(FString::FromInt(Result));
							}
						)
					]
				];
			}))
			.SetDisplayName(LOCTEXT("MyTab1TabTitle", "MyTab1"))
			.SetMenuType(ETabSpawnerMenuType::Disabled);
Slate語法

SNew創建對象

SAssignNew創建對象並賦值

使用設計器

局限性很大 UMG類繼承Slate類,很多Slate類都沒有對應的U類

1、首先設計器必須是UMG類的內容

2、然后他好像只能在關卡編輯器下起作用

自定義一個SWidget類

繼承SUserWidget類,在引擎SUserWidget.cpp文件中有對應的教程

/**
 * Use SUserWidget as a base class to build aggregate widgets that are not meant
 * to serve as low-level building blocks. Examples include: a main menu, a user card,
 * an info dialog for a selected object, a splash screen.
 *
 * See SUserWidgetExample
 *
 * SMyWidget.h
 * -----------
 * class SMyWidget : public SUserWidget
 * { 
 *   public:
 *   SLATE_USER_ARGS( SMyWidget )
 *  {}
 *   SLATE_END_ARGS()
 *
 *   // MUST Provide this function for SNew to call!
 *   virtual void Construct( const FArguments& InArgs ) = 0;
 *
 *   virtual void DoSomething() = 0;
 * };
 *
 * SMyWidget.cpp
 * -------------
 * namespace Implementation 
 * {
 *   class SMyWidget : public ::SMyWidget
 *   {
 *     public:
 *     virtual void Construct( const FArguments& InArgs ) override
 *     {
 *        SUserWidget::Construct( SUserWidget::FArguments()
 *        [
 *           SNew(STextBlock)
 *           .Text( NSLOCTEXT("x", "x", "My Widget's Content") )
 *        ]
 *     }
 *     
 *     private:
 *     // Private implementation details can occur here
 *     // without ever leaking out into the .h file!
 *   }
 * }
 *
 * TSharedRef<SMyWidget> SMyWidget::New()
 * {
 *   return MakeShareable( new SMyWidget() );
 * }
 */
class SUserWidget : public SCompoundWidget
{
 ...
}

官方也給了一個Example的類

自定義資源

創建一個自己的資源類型,一般需要兩個模塊,包括資源描述、資源編輯器(右鍵菜單要出現、特殊的編輯器等等)

plugin中分模塊

依賴的模塊

Editor依賴的較多,UnrealEd模塊管理資源創建面板,AssetTools管理資源操作 、PropertyEditor管理屬性顯示,以及上面提到的資源模塊

PrivateDependencyModuleNames.AddRange(
   new string[]
   {
      "CoreUObject",
      "Engine",
      "Slate",
      "SlateCore",
      "UnrealEd",
      "NewAsset",
      "AssetTools",
      "PropertyEditor",
      // ... add private dependencies that you statically link with here ... 
   }
);

資源模塊一般是私有的,Editor模塊的build的include路徑應該增加一下

toolkit 在引擎內部 等於 editor

引擎默認會給一個資源編輯界面

創建新資源

需要繼承一個UFactory類,不需要綁定

/**
 * 從4.25.1版開始,若想新資源類型出現在內容瀏覽器的右鍵創建菜單中,必須為它注冊IAssetTypeActions對象,具體見以下修改
 * https://github.com/EpicGames/UnrealEngine/commit/e8a2922b50b7659edabb9b0779ed9bfc7e593009
 */
UCLASS()
class UNewAssetFactoryNew : public UFactory
{
   GENERATED_UCLASS_BODY()
public:
   virtual UObject* FactoryCreateNew(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override;
   virtual bool ShouldShowInNewMenu() const override;
};
新版本

新版本的操作不太一樣,需要額外實現一個資源操作類

#include "AssetTypeActions_Base.h"

class FNewAssetAction :public FAssetTypeActions_Base
{
public:
   virtual uint32 GetCategories() override;
   virtual FText GetName() const override;
   virtual UClass* GetSupportedClass() const override;
   virtual FColor GetTypeColor() const override;
   virtual void OpenAssetEditor(const TArray<UObject*>& InObjects, TSharedPtr<IToolkitHost> EditWithinLevelEditor = TSharedPtr<IToolkitHost>()) override;
   virtual bool HasActions(const TArray< UObject* >& InObjects) const override;
   virtual void GetActions(const TArray< UObject* >& InObjects, FMenuBuilder& MenuBuilder) override;
};

再在進入模塊時進行注冊

		{
			IAssetTools& AssetTools = FModuleManager::LoadModuleChecked<FAssetToolsModule>("AssetTools").Get();
			Action = MakeShareable(new FNewAssetAction());
			AssetTools.RegisterAssetTypeActions(Action.ToSharedRef());
		}

關閉的時候也要處理一下

{
			FAssetToolsModule* Module = FModuleManager::GetModulePtr<FAssetToolsModule>("AssetTools");
			if (Module) {
				IAssetTools& AssetTools = Module->Get();
				if (Action.IsValid()) {
					AssetTools.UnregisterAssetTypeActions(Action.ToSharedRef());
				}
			}
		}

導入資源

工廠繼承FReimportHandler類

構造函數需要繼承文件的后綴,編輯器看到某個后綴的文件就將其使用該工廠進行導入

導入函數FactoryCreateFile的實現

UObject* UNewAssetFactory::FactoryCreateFile(UClass* InClass, UObject* InParent,
	FName InName, EObjectFlags Flags, const FString& Filename, const TCHAR* Parms,
	FFeedbackContext* Warn, bool& bOutOperationCanceled)
{
	UNewAsset* NewAsset = nullptr;
	TArray<uint8> Bytes;

	if (FFileHelper::LoadFileToArray(Bytes, *Filename) && Bytes.Num() >= sizeof(int32))
	{
		NewAsset = NewObject<UNewAsset>(InParent, InClass, InName, Flags);
		for (uint32 i = 0; i < sizeof(int32); ++i) {
			NewAsset->IntValue |= Bytes[i] << (i * 8);
		}
	}

	bOutOperationCanceled = false;

	return NewAsset;
}

自定義資源編輯器

在NewAssetAction類中繼續重寫一個OpenAssetEditor,不重寫他會用默認的給我們

然后在這個Open函數里

void FNewAssetAction::OpenAssetEditor(const TArray<UObject*>& InObjects, TSharedPtr<IToolkitHost> EditWithinLevelEditor)
{
    EToolkitMode::Type Mode = EditWithinLevelEditor.IsValid() ? EToolkitMode::WorldCentric : EToolkitMode::Standalone;//套路

	for (auto Obj = InObjects.CreateConstIterator(); Obj; ++Obj)
	{
		auto NewAsset = Cast<UNewAsset>(*Obj);

		if (NewAsset)
		{
			TSharedRef<FNewAssetToolkit> EditorToolkit = MakeShareable(new FNewAssetToolkit());
			EditorToolkit->Initialize(NewAsset, Mode, EditWithinLevelEditor);
		}
	}
}

這里涉及到一個新類,ToolKit,其實就是和Editor一樣的意思

把資源傳到我們的自定義一個編輯器里

toolkit
#include "Toolkits/AssetEditorToolkit.h"
class FNewAssetToolkit : public FAssetEditorToolkit
{
public:
   virtual void RegisterTabSpawners(const TSharedRef<FTabManager>& InTabManager) override;
   virtual void UnregisterTabSpawners(const TSharedRef<FTabManager>& InTabManager) override;
   void Initialize(class UNewAsset* InNewAsset, const EToolkitMode::Type InMode, const TSharedPtr<IToolkitHost>& InToolkitHost);
   virtual FText GetBaseToolkitName() const override;
   virtual FName GetToolkitFName() const override;
   virtual FLinearColor GetWorldCentricTabColorScale() const override;
   virtual FString GetWorldCentricTabPrefix() const override;

private:
   class UNewAsset* NewAsset;
   TSharedPtr<STextBlock> TextBlock;

   TSharedPtr<class IDetailsView> DetailsView;
};

資源編輯后需要NewAsset->MarkPackageDirty();來讓編輯器知道他需要保存

自定義細節面板

沒有做操作就會給一個默認的細節面板

寫一個自己的類的Detail對應類,繼承IDetailCustomization接口,然后這個類的特殊顯示屬性繼承IPropertyTypeCustomization接口

#include "CoreMinimal.h"
#include "IDetailCustomization.h"
#include "IPropertyTypeCustomization.h"

class FNewAssetDetailCustomization : public IDetailCustomization
{
public:
   virtual void CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) override;
};


在Module啟動位置,給FPropertyEditorModule注冊屬性編輯器類和detail類的關系

RegisterCustomPropertyTypeLayout
RegisterCustomClassLayout
FPropertyEditorModule& PropertyEditorModule = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");
PropertyEditorModule.RegisterCustomPropertyTypeLayout("StructMember", FOnGetPropertyTypeCustomizationInstance::CreateLambda(
   [] {return MakeShareable(new FNewAssetMemberCustomization); }
));
PropertyEditorModule.RegisterCustomPropertyTypeLayout("ClassMember", FOnGetPropertyTypeCustomizationInstance::CreateLambda(
   [] {return MakeShareable(new FNewAssetMemberCustomization); }
));
PropertyEditorModule.RegisterCustomClassLayout("NewAsset", FOnGetDetailCustomizationInstance::CreateLambda(
   [] {return MakeShareable(new FNewAssetDetailCustomization); }
));
PropertyEditorModule.NotifyCustomizationModuleChanged();

類的屬性特定顯示

class FNewAssetMemberCustomization : public IPropertyTypeCustomization
{
public:
   virtual void CustomizeHeader(
      TSharedRef<IPropertyHandle> InPropertyHandle,
      FDetailWidgetRow& HeaderRow,
      IPropertyTypeCustomizationUtils& CustomizationUtils) override;

   virtual void CustomizeChildren(
      TSharedRef<IPropertyHandle> InPropertyHandle,
      IDetailChildrenBuilder& ChildBuilder,
      IPropertyTypeCustomizationUtils& CustomizationUtils) override;

private:
   TSharedPtr<class STextBlock> TextBlock_MemberValue;
};

貌似是Header里負責填寫改變屬性row的樣式,而children函數負責處理property數據

待補充

參考

https://zhuanlan.zhihu.com/p/338067229

https://www.bilibili.com/video/BV1tv41117qg

https://docs.unrealengine.com/4.26/zh-CN/ProductionPipelines/Plugins/

https://www.unrealengine.com/zh-CN/onlinelearning-courses/best-practices-for-creating-and-using-plugins?sessionInvalidated=true


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM