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

posted @ 2021-07-20 19:18  飞翔的子明  阅读(3094)  评论(0编辑  收藏  举报