分類:
- Blank
- BlueprintLibrary
- ContentOnly
- Editor Toolbar Button
- Editor Standalone Window
- Editor Mode
- Third Party Library
插件中的代碼
生成Visual Studio或Xcode的項目文件時,含有 Source
文件夾(包含 .Build.cs
文件)的插件將被添加到項目文件,以便導航到其源代碼。編譯游戲項目時,UBT將自動編譯此類插件。
插件可含有任意數量的模塊源目錄。多數插件僅有一個模塊(但可創建多個模塊,例如插件包含純編輯器功能時),及游戲期間要運行的其他代碼。
插件源文件的大部分布局與引擎中其他C++模塊相同。
在模塊的 Source
目錄(或其子目錄)內,插件可在標頭文件中聲明新反映的類型(UCLASS
、USTRUCT
等)。引擎的構建系統將檢測此類文件,並按需要生成代碼支持新類型。需遵守C++模塊中使用 Uobjects
時的一般規則,例如在模塊的源文件中包含生成的標頭文件和模塊 generated.inl
文件。
UE4支持共生模塊和插件。通過在自身.uproject文件中啟用插件,項目模塊可依賴插件。類似地,通過在自身.uplugin文件中啟用其他插件,插件可表明依賴性。但其中有一項重要限制:插件和模塊將拆分為若干層級,僅能依賴同一級或更高級的其他插件或模塊。例如,項目模塊可依賴引擎模塊,但引擎模塊無法依賴項目模塊。這是因為引擎(及其所有插件和模塊)的級別高於項目,須能在無項目的情況下編譯。以下圖表展示了項目和模塊間的依賴性層級:
箭頭表明可能的依賴性。各插件或模塊類型可依賴同級別或更高級別的其他插件或模塊類型。
引擎插件
虛幻引擎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/