虚幻引擎编辑器开发基础(一)
虛幻引擎編輯器開發(fā)基礎(chǔ)(一)
文章目錄
- 虛幻引擎編輯器開發(fā)基礎(chǔ)(一)
- 一、前言
- 二、插件與模塊
- 2.1 插件(Plguin)
- 2.1.1 插件的作用
- 2.1.2 插件的類型
- 2.1.3 插件結(jié)構(gòu)
- 2.2 模塊(Module)
- 2.2.1 build.cs文件
- 2.2.2 創(chuàng)建模塊
- 2.2.3 模塊的加載與卸載
- 三、Slate
- 3.1 獨(dú)立窗口插件淺析
- 3.2 什么是Slate
- 3.3 Slate淺析
- 3.3.1 聲明式語法
- 3.3.2 創(chuàng)建自定義控件
- 3.3.3 布局
- 四、資源
- 4.1 路徑
- 4.2 資源
- 4.3 資源的創(chuàng)建與獲取
- 4.3.1 資源的創(chuàng)建
- 4.3.2 資源的獲取
- 4.4 AssetRegistry分析
- 4.4.1 如何搜尋asset文件
- 4.4.2 如何從FAssetData獲取Uobject
- 4.5 對象的創(chuàng)建與銷毀
- 五、縮略圖
- 參考文章
一、前言
虛幻引擎提供了非常強(qiáng)大的編輯器(如藍(lán)圖編輯器、材質(zhì)編輯器、動(dòng)畫編輯器)。
然而根據(jù)項(xiàng)目的不同以及相應(yīng)的需求也需要擴(kuò)展或者自定義一些編輯器的相關(guān)功能,對引擎做各種工具上的擴(kuò)展,來滿足高效、快速的開發(fā)需要。
虛幻的編輯器開發(fā)包含了零零散散的各種內(nèi)容,包括比如插件和模塊、編輯器窗口的擴(kuò)展、資源文件的自定義等。
這篇文章,將會(huì)整理一下筆者了解的編輯器開發(fā)相關(guān)的基礎(chǔ)內(nèi)容(一)。
由于筆者只自己實(shí)踐過其中的一部分。一些內(nèi)容可能會(huì)留空,標(biāo)注出可能的參考文章,后續(xù)工作涉及到或做到將補(bǔ)充和修改。
二、插件與模塊
在正式進(jìn)入相關(guān)的開發(fā)介紹之前,讓我們來了解一下虛幻的插件與模塊功能。
插件與模塊既是虛幻組織代碼的一種方式,可以我們對編輯器開發(fā)的重要幫手。
虛幻中的Module、Plugin是兩個(gè)不同的概念。
一個(gè)Plugin可以由多個(gè)Module組成,Module只能由代碼組成,而Plugin可以由代碼和資源組成;
Plugin可以編譯之后打包跨工程使用,保持代碼的獨(dú)立性。
而Module在工程里的耦合則較高,是代碼級別的直接引用。
2.1 插件(Plguin)
2.1.1 插件的作用
虛幻的插件作用非常廣:
- 可添加運(yùn)行時(shí)gameplay功能;
- 修改內(nèi)置引擎功能(或添加新功能)、新建文件類型、以及使用新菜單/工具欄命令和子模式擴(kuò)展編輯器的功能;
- 使用插件擴(kuò)展許多現(xiàn)有UE4子系統(tǒng);
2.1.2 插件的類型
虛幻的插件插件的類型分為兩類:
- 引擎插件;
- 項(xiàng)目插件;
注:二者除了放置目錄存在區(qū)別,基本沒有其他差別。
插件存放的文件目錄:
- 插件可擁有自己的Content文件夾,其中包含特定于該插件的資源文件;
- CanContainContent設(shè)置為true
- 引擎將掃描基礎(chǔ)Plugins文件夾下的所有子文件夾,查找要加載的插件;
- 引擎插件: /[UE引擎根目錄]/Engine/Plugins/[插件命名]/
- 游戲插件: /[項(xiàng)目根目錄]/Engine/Plugins/[插件命名]/
有時(shí),在啟動(dòng)UE項(xiàng)目時(shí),會(huì)遇到插件編譯不過的問題。
臨時(shí)解決方法:可以編輯 XXX.uproject 去除某些插件。
查看現(xiàn)有的插件方法:
- Edit -> Plugins;可選擇開啟或禁用相應(yīng)插件
2.1.3 插件結(jié)構(gòu)
虛幻提供了好幾種插件的模板,如Blank(空白)、Content(只包含資源)、藍(lán)圖庫等。
通過上述的操作可以快速的創(chuàng)建一個(gè)新的插件。
下面讓我們一起看一下插件的目錄(文件)結(jié)構(gòu)。舉例如下:
可以看到一個(gè)帶源碼的插件有
-
插件描述文件(.uplugin)
-
模塊配置文件(.Build.cs)
-
源碼目錄(Source)
-
…
插件里還可以包含著色器代碼文件、資源文件等。
插件描述文件(.uplugin)
- 虛幻啟動(dòng)時(shí),會(huì)在Plugin目錄里面搜索所有的.uplugin文件,來查找所有的插件;
- 每個(gè).uplugin文件表示一個(gè)插件,其格式為.json;
- 該文件的作用:提供描述插件相關(guān)基本信息;
Modules字段
每個(gè)模塊,需要配置 名字Name、類型Type、加載階段LoadingPhase;
-
Name 是插件模塊的唯一命名;
-
Type 設(shè)置模塊的類型, 如:Runtime、Developer、Editor等;
- Runtime,在任何情況下都會(huì)加載;
- Editor,只在編輯器啟動(dòng)時(shí)加載;
-
LoadingPhase 指明在什么階段加載模塊,默認(rèn)為Default;
- PreDefault,讓模塊在一般模塊前加載
- Default,默認(rèn)
- PostConfigInit,此模塊在虛幻關(guān)鍵模塊加載后加載
源碼目錄(Source)
- 存儲(chǔ)插件的源碼;
- 在Source下,每個(gè)目錄代表一個(gè)模塊;
- 每個(gè)模塊包含Public和Private目錄,以及模塊配置文件(.Build.cs);
模塊配置文件(.Build.cs)
后續(xù)再介紹。
2.2 模塊(Module)
為什么虛幻引入模塊機(jī)制?
- 編譯模式太多,配置復(fù)雜;
由前面介紹可知,一個(gè)模塊文件夾應(yīng)該包含這些內(nèi)容:
- Public文件夾;
- Private文件夾;
- *.builc.cs文件
UE4的代碼是由模塊來組織的,.build.cs代表一個(gè)模塊。
2.2.1 build.cs文件
模塊配置文件是用來告知**UBT(Unreal Build Tool)**如何配置編譯和構(gòu)造環(huán)境。
using UnrealBuildTool; public class pluginDev : ModuleRules {public pluginDev(TargetInfo Target){PublicDependencyModuleNames.AddRange(new string[]{"Core","CoreUObject","Engine","InputCore"});PrivateDependencyModuleNames.AddRange(new string[]{//...});} }其中,
// 添加#inlcude的頭文件路徑 PublicIncludePaths (List<String>) // 公開給其他模塊的文件路徑 / 但是不需要"導(dǎo)入"或鏈接 PrivateIncludePaths (List<String>) // 通向此模塊內(nèi)部包含文件的所有路徑的列表,不向其他模塊公開// 控制依賴 PublicIncludePathModuleNames(List<String>) // 我們模塊的公共標(biāo)頭需要對這些標(biāo)頭文件進(jìn)行訪問,但是不需要"導(dǎo)入"或鏈接 PublicDependencyModuleNames (List<String>) // 公共源文件所需要的模塊.(需要導(dǎo)入或鏈接?)PrivateIncludePathModuleNames (List<String>) // 我們模塊的私有代碼文件需要對這些標(biāo)頭文件進(jìn)行訪問,但是不需要"導(dǎo)入"或鏈接。 PrivateDependencyModuleNames(List<String>) // 私有代碼依賴這些模塊.(需要導(dǎo)入或鏈接?)DynamicallyLoadedModuleNames (List<String>) // 此模塊在運(yùn)行時(shí)可能需要的附加模塊2.2.2 創(chuàng)建模塊
創(chuàng)建一個(gè)新的模塊分為如下幾步:
- 創(chuàng)建模塊文件夾結(jié)構(gòu);
- 創(chuàng)建模塊構(gòu)建文件 .build.cs;
- 創(chuàng)建模塊的頭文件和實(shí)現(xiàn)文件;
- 創(chuàng)建模塊的C++聲明和定義;
模塊源代碼文件示例:
并實(shí)現(xiàn)StartUpModule和ShutdownModule函數(shù),功能為: 自定義模塊的加載和卸載時(shí)行為。
.h文件
#pragma once #include "ModuleManager.h" class FPluginDevModule : public IModuleInterface {/** IModuleInterface implementation */virtual void StartupModule() override;virtual void ShutdownModule() override; }.CPP文件
#include "XXX.h"void FPluginDevModule::StartupModule() {// ... 模塊加載執(zhí)行內(nèi)容 } void FPluginDevModule::ShutdownModule() {// ... 模塊卸載執(zhí)行內(nèi)容 } //!!! // 表明FPluginDevModule是實(shí)現(xiàn)pluginDev模塊的類 IMPLEMENT_MODULE(FPluginDevModule,pluginDev)由上述代碼可知,虛幻的模塊類繼承自IModuleInterface。
其包含7個(gè)虛函數(shù):
- StartupModule
- PreUnloadCallback
- PostLoadCallback
- ShutdownModule
- SupportsDynamicReloading
- SupportAutomaticShutdown
- IsGameMode
StartupModule是模塊的入口函數(shù)。
2.2.3 模塊的加載與卸載
在源碼層面,一個(gè)包含 *.build.cs 的文件就是一個(gè)模塊。
每個(gè)模塊編譯鏈接后后,會(huì)生成比如一個(gè)靜態(tài)庫lib或動(dòng)態(tài)庫dll。
虛幻引擎初始化模塊加載順序,由2個(gè)部分決定:
總體的順序:
模塊的加載注冊
- 模塊需要提供給外部一個(gè)操作的接口,就是一個(gè)IModuleInterface指針;
- 這里并不是說調(diào)用模塊內(nèi)的任何函數(shù)(或類)都需要通過這個(gè)指針
- 實(shí)際上,只需要#include了相應(yīng)頭文件就可以調(diào)用對應(yīng)的功能,如New一個(gè)類,調(diào)一個(gè)全局函數(shù);
- 這個(gè)IModuleInterface指針的意義: 操作作為整體的模塊本身,如模塊的加載/初始化/卸載。訪問模塊內(nèi)的一些全局變量。
- IModuleInterface 在ModuleInterface.h
- 獲取這個(gè)指針的方法,只有一個(gè):就是通過 FModuleManager 上的 LoadModule/GetModule。
FModuleManager去哪里加載這些模塊呢?
即調(diào)用FModuleManager::LoadModule,其中又對動(dòng)態(tài)和靜態(tài)區(qū)別處理:
- 動(dòng)態(tài)鏈接庫,根據(jù)名字直接加載對應(yīng)的DLL即可。
- 作為合法UE模塊的dll,必定要導(dǎo)出一些約定的函數(shù)來返回自身IModuleInterface指針;
- 靜態(tài)鏈接庫,去一個(gè)叫StaticallyLinkedModuleInitializers的Map里找。這就要求所有模塊已把自己注冊到這個(gè)Map里。
為滿足以上約定,每個(gè)模塊在實(shí)現(xiàn)的過程中,需要插入一些宏代碼,例如上述示例中的IMPLEMENT_MODULE。
IMPLEMENT_MODULE代碼如下:
靜態(tài)鏈接時(shí):
- FStaticallyLinkedModuleRegistrant,是一個(gè)注冊輔助類,利用全局變量構(gòu)造函數(shù)自動(dòng)調(diào)用的特性,實(shí)現(xiàn)自動(dòng)注冊。
FStaticallyLinkedModuleRegistrant:
template< class ModuleClass > class FStaticallyLinkedModuleRegistrant { public:FStaticallyLinkedModuleRegistrant( const ANSICHAR* InModuleName ){// Create a delegate to our InitializeModule methodFModuleManager::FInitializeStaticallyLinkedModule InitializerDelegate = FModuleManager::FInitializeStaticallyLinkedModule::CreateRaw(this, &FStaticallyLinkedModuleRegistrant<ModuleClass>::InitializeModule );// Register this moduleFModuleManager::Get().RegisterStaticallyLinkedModule(FName( InModuleName ), // Module nameInitializerDelegate ); // Initializer delegate}IModuleInterface* InitializeModule( ){return new ModuleClass();} };動(dòng)態(tài)鏈接時(shí):
- 聲明了一個(gè)dllexport函數(shù),功能就是創(chuàng)建并返回相應(yīng)模塊;
- ModuleImplClass:具體實(shí)現(xiàn)的類;
- ModuleName:模塊的名稱(字符串);
插件重編譯問題:有時(shí)候修改了代碼卻發(fā)現(xiàn)沒有進(jìn)行編譯。
如何解決:把插件目錄下的兩個(gè)文件夾刪除掉:Binaries(包含DLL)、Intermediate(Obj文件等)。
三、Slate
在了解了虛幻的插件和模塊的概念之后,讓我們來通過一個(gè)虛幻的例程,簡單地了解一下編輯器開發(fā)的基礎(chǔ),Slate。即如何用C++編程創(chuàng)建UI。
在游戲開發(fā)過程中的UI大多直接UMG進(jìn)行開發(fā)實(shí)現(xiàn)。
而在編輯器開發(fā)過程中,則可能需要使用Slate來實(shí)現(xiàn)一些UI功能。
亦可以用Slate組合一些控件,再封裝成為UMG。
3.1 獨(dú)立窗口插件淺析
首先,創(chuàng)建Standalone Window插件。
- 在創(chuàng)建新的插件時(shí),選擇Editor Standalone Window進(jìn)行創(chuàng)建;
然后,編譯項(xiàng)目之后打開,可以在工具欄上看到按鈕:
點(diǎn)擊之后有:
- 點(diǎn)擊工具欄按鈕彈出一個(gè)窗口(里面有一個(gè)Tab);
- 提示的內(nèi)容為:可以添加代碼到 FSimpleWindowModule::OnSpawnPluginTab in SimpleWindow.cpp來重寫窗口的內(nèi)容;
源碼閱讀與分析
- StandaloneWindow 插件的主入口;
- StandaloneWindowStyle 定義了UI的風(fēng)格;
- StandaloneWindowCommands 聲明要注冊的命令;
StandaloneWindowCommands類
- 聲明命令和注冊;
StartupModule主要做了幾件事情:
- 初始化風(fēng)格;
- 注冊UI命令和其回調(diào);
- 添加創(chuàng)建的擴(kuò)展;
- 注冊標(biāo)簽頁的創(chuàng)建方法;
其中Extender的回調(diào)如下:
- 工具欄和菜單欄調(diào)用不同的函數(shù),但注冊的同一個(gè)命令。
這個(gè)命令對應(yīng)的回調(diào)即:
其中PluginButtonClicked回調(diào)的具體實(shí)現(xiàn):
- 根據(jù)名字去激活一個(gè)Tab標(biāo)簽UI,即StartupModule函數(shù)中最后注冊的。
這個(gè)標(biāo)簽頁的具體生成方法,即OnSpawnPuginTab函數(shù)實(shí)現(xiàn)的。最后返回一個(gè)Slate聲明創(chuàng)建的UI。
對于以上的源碼以及其實(shí)現(xiàn)效果,有三點(diǎn)值得研究,分為是:
其中:
- 第一點(diǎn)(菜單欄和工具欄的擴(kuò)展)為后續(xù)文章的內(nèi)容之一,到時(shí)再進(jìn)行介紹。
- 第二點(diǎn)(Slate創(chuàng)建的簡單UI)為本節(jié)后續(xù)的內(nèi)容;
- 第三點(diǎn),大量地在虛幻使用,在此我們進(jìn)行介紹。
UI_COMMAND系列
這里說的UI_COMMAND系列,筆者指的是:
// UICommandInfo.h/cpp FUICommandInfo 類// UICommandList.h/cpp FUICommandList 類// Commands.h/cpp UI_COMMAND 宏三者的關(guān)系如下:
- UI_COMMAND宏,負(fù)責(zé)正式注冊FUICommandInfo;
- FUICommandList,用于包含F(xiàn)UICommandInfo,并且映射FUICommandInfo到委托;
UI_COMMAND宏
// -> MakeUICommand_InternalUseOnly // -> -> FUICommandInfo::MakeCommandInfo 在該函數(shù)中New了一個(gè)FUICommandInfo OutCommand = MakeShareable(new FUICommandInfo(InContext->GetContextName())); FInputBindingManager::Get().CreateInputCommand(InContext,OutCommand.ToSharedRef());將FUICommandInfo注冊到了FInputBindingManager的單例中。
FUICommandList映射委托調(diào)用
一系列函數(shù)FUICommandList::MapAction(),會(huì)將FUICommandList和FUIAction注冊到Map中。
FUICommandInfo會(huì)被UI使用,當(dāng)UI條件觸發(fā),則會(huì)調(diào)用綁定的委托。
3.2 什么是Slate
現(xiàn)在我們來了解一下3.1中我們提出的第二點(diǎn)研究內(nèi)容,即Slate。那么什么是Slate呢?
Slate概述 中提到:
- Slate 是完全自定義、與平臺(tái)無關(guān)的用戶界面框架,旨在讓工具和應(yīng)用程序(比如虛幻編輯器)的用戶界面或游戲中用戶界面的構(gòu)建過程變得有趣、高效。
- 它將聲明性語法與輕松設(shè)計(jì)、布局和風(fēng)格組件的功能相結(jié)合,允許在UI上輕松實(shí)現(xiàn)創(chuàng)建和迭代。
筆者是這樣理解的:
- 用聲明式的語法,用C++來定義UI,包含了UI的布局、風(fēng)格、以及對UI響應(yīng)的處理。
就3.1中的例子,通過C++代碼直接定義一個(gè)包含了一個(gè)文本框的標(biāo)簽頁,并指定了文本內(nèi)容。
- 可以看出這樣的方式,使得程序員可以訪問構(gòu)建UI,而無需添加間接層。
3.3 Slate淺析
Slate控件分為三種:
- 無插槽(Slot)的控件,SLeafWidget,例如:SImage、STextBlock。
- 有一個(gè)Slot的控件,SCompoundWidget,例如:SButton。
- 有多個(gè)Slot的控件,SPanel(布局),例如:SVerticalBox。
其中:
-
在Slate中,容器不存儲(chǔ)控件,容器中的Slot(插槽)存儲(chǔ)控件。
-
插槽可以存儲(chǔ)三種的控件任意一種。
3.3.1 聲明式語法
為了實(shí)現(xiàn)聲明式語法,UE提供了一組完整的宏來簡化聲明和創(chuàng)建新控件的過程。
接下來,將對Slate中的宏定義進(jìn)行一些簡單的解析,以SButton(SButton.h)為例。
看一下SButton類是如何聲明的。
class SLATE_API SButton: public SBorder { // ... 省略部分源碼 public:// SLATE_BEGIN_ARGS( SButton ): _Content(), _ButtonStyle( &FCoreStyle::Get().GetWidgetStyle< FButtonStyle >( "Button" ) ), _TextStyle( &FCoreStyle::Get().GetWidgetStyle< FTextBlockStyle >("NormalText") ), _HAlign( HAlign_Fill ), _VAlign( VAlign_Fill ), _ContentPadding(FMargin(4.0, 2.0)), _Text(), _ClickMethod( EButtonClickMethod::DownAndUp ), _TouchMethod( EButtonTouchMethod::DownAndUp ), _PressMethod( EButtonPressMethod::DownAndUp ), _DesiredSizeScale( FVector2D(1,1) ), _ContentScale( FVector2D(1,1) ), _ButtonColorAndOpacity(FLinearColor::White), _ForegroundColor( FCoreStyle::Get().GetSlateColor( "InvertedForeground" ) ), _IsFocusable( true ){}/** Slot for this button's content (optional) */SLATE_DEFAULT_SLOT( FArguments, Content )/** The visual style of the button */SLATE_STYLE_ARGUMENT( FButtonStyle, ButtonStyle )/** The text style of the button */SLATE_STYLE_ARGUMENT( FTextBlockStyle, TextStyle )// ... 省略部分源碼/** Called when the button is clicked */SLATE_EVENT( FOnClicked, OnClicked )/** Called when the button is pressed */SLATE_EVENT( FSimpleDelegate, OnPressed )/** Called when the button is released */SLATE_EVENT( FSimpleDelegate, OnReleased )SLATE_EVENT( FSimpleDelegate, OnHovered )SLATE_EVENT( FSimpleDelegate, OnUnhovered )SLATE_END_ARGS()// ... 省略部分源碼 };SLATE_BEGIN_ARGS 和 SLATE_END_ARS
#define SLATE_BEGIN_ARGS( WidgetType ) \public: \struct FArguments : public TSlateBaseNamedArgs<WidgetType> \{ \typedef FArguments WidgetArgsType; \FORCENOINLINE FArguments()#define SLATE_END_ARGS() \};在二者的包圍中,創(chuàng)建了一個(gè)FArguments參數(shù)類。
FArguments的成員變量或函數(shù)通過以下幾個(gè)宏定義來輔助實(shí)現(xiàn)。
- SLATE_ARGUMENT
- SLATE_ATTRIBUTE
- SLATE_EVENT
- SLATE_DEFAULT_SLOT
SLATE_ARGUMENT 宏
- 參數(shù)只能是值。
SLATE_ATTRIBUTE 宏
- 屬性可以是值也可以是函數(shù)。
SLATE_EVENT 宏
- 事件,其實(shí)就是回調(diào)。
SLATE_DEFAULT_SLOT 宏
-
根據(jù)名稱創(chuàng)建了Widget,可以用來存儲(chǔ)Widget。
-
重載了[]操作符,可以有一個(gè)Widget作為輸入。
SButton是SCompoundWidget,只有一個(gè)Slot插槽。
讓我們再來看一下多個(gè)插槽的情況,看一下布局,以SVerticalBox舉例。
可以看到類中的宏定義如下:
SLATE_BEGIN_ARGS( SVerticalBox ) {_Visibility = EVisibility::SelfHitTestInvisible; }SLATE_SUPPORTS_SLOT(SVerticalBox::FSlot)SLATE_END_ARGS()SLATE_SUPPORTS_SLOT 宏
- 定義了一個(gè)Slot數(shù)組;
- 在該類中為SVerticalBox::FSlot,并重載了+操作符,添加Slot到數(shù)組中。
其中,SVerticalBox::FSlot在類里面中實(shí)現(xiàn)如下。
/** A Vertical Box Panel. See SBoxPanel for more info. */ class SLATECORE_API SVerticalBox : public SBoxPanel { public:// SVerticalBox::FSlot的定義class FSlot : public SBoxPanel::FSlot{public:FSlot(): SBoxPanel::FSlot(){}// 省略了部分源碼}// 省略了部分源碼 };3.3.2 創(chuàng)建自定義控件
根據(jù)上述的宏,我們很容易可以對自定義Slate的控件。
例如我們可以自定義 CompoundWidget。
- 必須要有的 SLATE_BEGIN_ARGS 和 SLATE_END_ARGS 以及構(gòu)造器Construct函數(shù)。
- 由于是SCompoundWidget的子類,則可以通過ChildSlot構(gòu)造控件內(nèi)容。
下面給出一個(gè)自定義控件的例子(僅僅是將兩個(gè)按鈕組裝在一起)。
.h文件如下:
class SWidgetDemoA : public SCompoundWidget {SLATE_BEGIN_ARGS(SWidgetDemoA) {}SLATE_ATTRIBUTE(FString, InText)SLATE_END_ARGS()public:SWidgetDemoA();/*** Construct this widget* @param InArgs The declaration data for this widget*/void Construct(const FArguments& InArgs);private:void OnClicked();};.cpp文件如下:
SWidgetDemoA::SWidgetDemoA() { }void SWidgetDemoA::Construct(const FArguments& InArgs) {// InArgs._InText 類型為 TAttribute<FString>// 需要用Get函數(shù)FString Text = InArgs._InText.Get();this->ChildSlot[SNew(SVerticalBox)+ SVerticalBox::Slot()[SNew(SButton).Text(FText::FromString(Text))]+SVerticalBox::Slot()[SNew(SHorizontalBox)+SHorizontalBox::Slot()[// 測試事件SNew(SButton).OnClicked(this,&SWidgetDemoA::OnClicked)]]]; }void SWidgetDemoA::OnClicked() {UE_LOG(DmoeA, Warning, TEXT("SWidgetDemoA::OnClicked")); }更多可以參考UE源碼文件,或參考Slate控件示例 。
3.3.3 布局
這部分主要摘錄自 虛幻4渲染編程(工具篇)【第九卷:SlateUI布局】
Slate中的SWidget按照插槽Slot數(shù)量劃分以下三種。
在Slate中,容器不存儲(chǔ)控件,容器中的Slot(插槽)存儲(chǔ)控件。
插槽(Slot)可以存儲(chǔ)三種的控件任意一種。
每個(gè)插槽(Slot)到底占據(jù)UI的多少,取決于很多因素。
首先,來看Slot中的SizeParm參數(shù)。這是決定UI控件大小的的第一步。
Engine\Source\Runtime\SlateCore\Public\Widgets\SBoxPanel.h
/** * How much space this slot should occupy along panel's direction. * When SizeRule is SizeRule_Auto, the widget's DesiredSize will be used as the space required. * When SizeRule is SizeRule_Stretch, the available space will be distributed proportionately between * peer Widgets depending on the Value property. Available space is space remaining after all the * peers' SizeRule_Auto requirements have been satisfied. */ FSizeParam SizeParam;翻譯翻譯:
- 如果SizeRule是Auto,那么Slot大小會(huì)使用DesiredSize。
- 如果SizeRule是Stretch,那么Slot就會(huì)盡可能充滿UI空間。
Auto情況:
- 可以看出按鈕的大小是有一定的寬度是一定的。
Stretch情況:
- 當(dāng)SizeParam不指定的情況下就是默認(rèn)為Stretch。
下面將第一個(gè)Slot設(shè)置為Auto,第二個(gè)為Stretch??梢钥闯龇锨懊嫠f的情況。
Slot還有HAlignment和VAlignment兩個(gè)參數(shù),用來描述Slot的位置。
/** Horizontal positioning of child within the allocated slot */ TEnumAsByte<EHorizontalAlignment> HAlignment;/** Vertical positioning of child within the allocated slot */ TEnumAsByte<EVerticalAlignment> VAlignment;目前只考慮水平方向。
當(dāng)我們使用**(Auto,Fill)和(Auto,HAlign_Left)**組合時(shí):
假設(shè)目前有三個(gè)Slot并且向這三個(gè)slot中各添加了一個(gè)按鈕UI控件,其中一個(gè)是(Stretch,HAlign_Fill) 兩個(gè)是(Auto,HAlign_Left)
這個(gè)結(jié)果的計(jì)算如下:
如果將第一個(gè)插槽的填充方式改為Left,那么發(fā)現(xiàn)填充不滿。
- 因?yàn)?strong>Slot的大小大于控件本身的大小,留下很多可以被填充的Slot空間。
- 這里1的slot大小為窗口-2和3的slot大小,但是控件只有和2或3那么大。
如果將第一個(gè)插槽的填充方式改為Center:
如果我將三個(gè)控件全部選擇為Auto:
三個(gè)Slot的大小被明確的計(jì)算出來。
如果三個(gè)Slot里的控件的DesireSize之和小于窗口的大小,那么就會(huì)有Slot空間剩余。
這時(shí)再把控件填充進(jìn)來。因?yàn)槭沁x用的Auto,所以不管控件是什么填充模式,Slot空間的大小始終剛好等于內(nèi)部控件的大小。
當(dāng)Slot的大小總和大于窗口的時(shí)候會(huì)是什么情況呢?
通過SBox自定義DesireSize來定義SHorizontalBox的Slot大小!
如下示例所示,因?yàn)槭茿uto,所以Slot的大小是不會(huì)改變的,那么多余的部分直接會(huì)被Cull掉。
前面一直如果是Auto的情況下,Slot大小會(huì)使用DesiredSize。
那么DesiredSize是如何計(jì)算的呢?
看一下這個(gè)過程:
/*** A Panel's desired size in the space required to arrange of its children on the screen while respecting all of* the children's desired sizes and any layout-related options specified by the user. See StackPanel for an example.** @return The desired size.*/ FVector2D SBoxPanel::ComputeDesiredSize( float ) const {return (Orientation == Orient_Horizontal)? ComputeDesiredSizeForBox<Orient_Horizontal>(this->Children): ComputeDesiredSizeForBox<Orient_Vertical>(this->Children); }SBoxPanel的ComputeDesiredSize:
- 通過注釋可用看出:這個(gè)Desired size是所有子控件的desired size之和加上指定的Margin。
其中調(diào)用的GetDesiredSize為:
FVector2D SWidget::GetDesiredSize() const {return DesiredSize.Get(FVector2D::ZeroVector); }這個(gè)值是在CacheDesiredSize時(shí)計(jì)算的存儲(chǔ)起來的:
void SWidget::CacheDesiredSize(float InLayoutScaleMultiplier) { #if SLATE_VERBOSE_NAMED_EVENTSSCOPED_NAMED_EVENT(SWidget_CacheDesiredSize, FColor::Red); #endif// Cache this widget's desired size.SetDesiredSize(ComputeDesiredSize(InLayoutScaleMultiplier)); }看一下SBox的ComputeDesiredSize函數(shù):
- 可以看出這個(gè)函數(shù)通過Override的寬度和高度自定義大小,反正不管怎么定義計(jì)算它就是代表內(nèi)部控件的大小。
布局小結(jié):
四、資源
了解了Slate,這僅是對編輯器擴(kuò)展的表面皮毛,即UI僅是用于展示、接收和轉(zhuǎn)發(fā)用戶輸入的一個(gè)中間媒介。(當(dāng)然能把UI做得好看好用更好!)
編輯器功能的核心還是在于對資源、用戶輸入的數(shù)據(jù)的處理算法(例如,對資源進(jìn)行創(chuàng)建、計(jì)算、修改等)。
因而,對資源、資產(chǎn)數(shù)據(jù)的了解是處理數(shù)據(jù)的第一步。
4.1 路徑
UE將目錄分為:引擎(Engine)目錄 和 項(xiàng)目(Game)目錄。
并采用沙盒路徑,即虛擬路徑來標(biāo)識(shí)資源的路徑。
其中有:
- /Game 是一個(gè)虛擬的路徑,實(shí)際表示的是項(xiàng)目的 FPaths::ProjectContentDir() 。
- /Engine 也是一個(gè)虛擬路徑,實(shí)際路徑是引擎的 FPaths::EngineContentDir() 。
更多虛擬路徑可以查看源碼PackageName.cpp 中的類 FLongPackagePathsSingleton 的定義。
struct FLongPackagePathsSingleton {FString ConfigRootPath;FString EngineRootPath;FString GameRootPath;FString ScriptRootPath;FString ExtraRootPath;FString MemoryRootPath;FString TempRootPath;TArray<FString> MountPointRootPaths;FString EngineContentPath;FString ContentPathShort;FString EngineShadersPath;FString EngineShadersPathShort;FString GameContentPath;FString GameConfigPath;FString GameScriptPath;FString GameExtraPath;FString GameSavedPath;FString GameContentPathRebased;FString GameConfigPathRebased;FString GameScriptPathRebased;FString GameExtraPathRebased;FString GameSavedPathRebased;//@TODO: Can probably consolidate these into a single array, if it weren't for EngineContentPathShortTArray<FPathPair> ContentRootToPath;TArray<FPathPair> ContentPathToRoot;// ... 省略部分源碼 };路徑相關(guān)的API
常用的路徑接口都放在FPaths這個(gè)類。
/*** Path helpers for retrieving game dir, engine dir, etc.*/ class CORE_API FPaths {// ... 省略部分源碼 };4.2 資源
資源(Asset) 是用于 虛幻引擎 項(xiàng)目的內(nèi)容項(xiàng),可將其看作序列化到文件中的 UObject。
資源對應(yīng)的數(shù)據(jù)結(jié)構(gòu)類為:FAssetData。
其包含了以下幾種重要屬性:
- ObjectPath
- PackageName
- PackagePath
- AssetName
- AssetClass
下面,通過一個(gè)實(shí)際的例子來看下這些路徑都是什么樣的。
在項(xiàng)目的Content目錄創(chuàng)建了一個(gè)藍(lán)圖Acotr。
則有:
- ObjectPath : /Game/NewBlueprint.NewBlueprint
- PackageName : /Game/NewBlueprint
- PackagePath : /Game
- AssetName : NewBlueprint
可以看出其路徑采用了前面虛擬路徑。
那么,有了路徑如何獲得對應(yīng)的UObject數(shù)據(jù)呢?
- 可以通過GetAsset獲得內(nèi)存中的UObject對象!
4.3 資源的創(chuàng)建與獲取
4.3.1 資源的創(chuàng)建
在編寫工具的時(shí)候需要?jiǎng)?chuàng)建資源、初始化資源、管理資源等需求。比如:
-
合并DrawCall時(shí),需要?jiǎng)?chuàng)建貼圖;
-
管理材質(zhì)時(shí),需要?jiǎng)?chuàng)建并指定材質(zhì)球、設(shè)置材質(zhì)等。
首先,弄清楚虛幻的資源結(jié)構(gòu)以及和編輯器的關(guān)系。
虛幻中有大量的類型,諸如UMaterial、UMaterialInstance、UTexture、UStaticMesh等。
這些資源從外部導(dǎo)入虛幻的時(shí)候做了一次數(shù)據(jù)抽取,如貼圖資源模型資源等。
這些數(shù)據(jù)被放在一個(gè)UObject里,然后這個(gè)UObject放在一個(gè)Package里。
拿貼圖的數(shù)據(jù)導(dǎo)入舉例,有代碼UTextureFactory::ImportTexture:
- 引擎會(huì)抽取數(shù)據(jù),然后創(chuàng)建對應(yīng)UTexture,使用創(chuàng)建的UTexture填充其Source。
所以Unreal的導(dǎo)入資產(chǎn),創(chuàng)建有一個(gè)固定格式:
- New一個(gè)Package;
- New一個(gè)資源對應(yīng)的UObject;
- 在此時(shí)指認(rèn)Package和UObject;
- 向這個(gè)UObject填充數(shù)據(jù);
- MarkDirty,注冊,通知資源瀏覽器這里創(chuàng)建了一個(gè)新的資源,然后保存。
下面給出幾個(gè)通過C++創(chuàng)建資源資產(chǎn)的示例。
示例1: 創(chuàng)建一個(gè)模型資源,Create Mesh Asset With C++
大概的過程描述如下:
注意:
- 這里創(chuàng)建的是個(gè)空的MeshAsset;
- 如果想給MeshAsset添加模型頂點(diǎn)信息,可以通過設(shè)置NewStaticMesh->SourceModels來給MeshAsset增加資源信息;
示例2: 創(chuàng)建一個(gè)材質(zhì)實(shí)例,Create Material Instance With C++
// Create Empty Material Instance Asset FString MaterialInsBaseName(TEXT("MI_")); MaterialInsBaseName += TEXT("Test"); FString AssetPath = TEXT("/Game/") + FString(TEXT("Test")) + TEXT("/") + MaterialInsBaseName;UPackage* NewMatInsPack = CreatePackage(nullptr, *AssetPath);// New a Factory UMaterialInstanceConstantFactoryNew* Factory = NewObject<UMaterialInstanceConstantFactoryNew>(); // AssetTools Use Factory to New Asset FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked< FAssetToolsModule >("AssetTools"); UMaterialInstanceConstant* ReplaceMI = Cast<UMaterialInstanceConstant>(AssetToolsModule.Get().CreateAsset(MaterialInsBaseName, FPackageName::GetLongPackagePath(AssetPath), UMaterialInstanceConstant::StaticClass(), Factory));ReplaceMI->MarkPackageDirty(); ReplaceMI->PreEditChange(nullptr); ReplaceMI->PostEditChange();//Save the assets TArray<UPackage*> PackagesToSave; PackagesToSave.Add(NewMatInsPack); FEditorFileUtils::PromptForCheckoutAndSave(PackagesToSave, false, /*bPromptToSave=*/ false);備注:
- 除了采用NewUObject之外,還可以用AssetTool提供的AssetFactory來創(chuàng)建資源,原理是一樣的;
- 但AssetFactory比我們NewObject要多做一些保險(xiǎn)的事情;
示例3: 創(chuàng)建一個(gè)紋理資源,Create Texture With C++
// Create Texture2D Asset FString AssetName = TEXT("TestTexture"); FString AssetPath = TEXT("/Game/") + FString(TEXT("Test")) + TEXT("/") + AssetName;UPackage* NewAssetPack = CreatePackage(nullptr, *AssetPath);UTexture2D* NewTexture = NewObject<UTexture2D>(NewAssetPack, FName(*AssetName), RF_Public | RF_Standalone);// 可以在代碼中設(shè)置貼圖的屬性,如格式尺寸等. {int32 width = 512;int32 height = 512;NewTexture->PlatformData = new FTexturePlatformData;NewTexture->PlatformData->SizeX = width;NewTexture->PlatformData->SizeY = height;NewTexture->PlatformData->PixelFormat = PF_R8G8B8A8;FTexture2DMipMap* Mip = new(NewTexture->PlatformData->Mips) FTexture2DMipMap();Mip->SizeX = width;Mip->SizeY = height;Mip->BulkData.Lock(LOCK_READ_WRITE);uint8* TextureData = (uint8 *)Mip->BulkData.Realloc(512 * 512 * sizeof(uint8) * 4);uint8 PixleSize = sizeof(uint8) * 4;const uint32 TextureDataSize = width * height * PixleSize;TArray<FColor>ColorData;ColorData.AddDefaulted(width * height * 4);for (int32 i = 0; i < height; i++){for (int32 j = 0; j < width; j++){ColorData[i * width + j].R = (float)i / (float)height * 255;ColorData[i * width + j].G = (float)j / (float)width * 255;ColorData[i * width + j].B = (float)j / (float)width * 255;ColorData[i * width + j].A = 255;}}FMemory::Memcpy(TextureData, ColorData.GetData(), TextureDataSize);Mip->BulkData.Unlock();NewTexture->UpdateResource(); }FAssetRegistryModule::AssetCreated(NewTexture); NewAssetPack->MarkPackageDirty();//Save the assets TArray<UPackage*> PackagesToSave; PackagesToSave.Add(NewAssetPack); FEditorFileUtils::PromptForCheckoutAndSave(PackagesToSave, false, /*bPromptToSave=*/ false);4.3.2 資源的獲取
在實(shí)踐中,有時(shí)候需要根據(jù)路徑從而獲取資源,再進(jìn)行一系列處理。
Unreal的資源分為美術(shù)資源(即導(dǎo)入的)和藍(lán)圖資源(即在UE中創(chuàng)建的)。
資源獲取分別采用不同函數(shù)獲取到C++中使用。
藍(lán)圖資源加載:
- 使用LoadClass函數(shù)加載藍(lán)圖類。
美術(shù)資源加載:
- 使用LoadObject函數(shù)加載美術(shù)資源。例如加載一個(gè)StaticMesh。
除之之外,在開發(fā)過程中,還經(jīng)常需要處理的一個(gè)問題就是:
- 需要獲得某種特定格式資產(chǎn)的所有路徑,如Mesh、Material等。
UE篩選某類資源(FAssetData)的資源有兩種方法:
- UObjectLibrary;
- FAssetRegistryModule + FARFilter;
UObjectLibrary方法:
- 只需利用UObjectLibrary和FAssetData兩個(gè)關(guān)鍵結(jié)構(gòu)即可;
FAssetRegistryModule和FARFilter方法
- 設(shè)置路徑和類名;
兩種方法的區(qū)別:
- UObjectLibrary加載了相應(yīng)的所有資源進(jìn)入內(nèi)存!
- 而FAssetRegistryModule和FARFilter僅僅是獲取資源的路徑。相應(yīng)的資源并沒有被加載為UObject!
4.4 AssetRegistry分析
這部分摘錄自 UE4 AssetRegistry分析
Asset Registry是Editor的子系統(tǒng),負(fù)責(zé)在Editor加載時(shí)收集未加載的Asset信息。
這些信息儲(chǔ)存在內(nèi)存中,因此Editor可以創(chuàng)建資源列表,而不需要真正加載這些資源。
Content Browser是這個(gè)系統(tǒng)的主要消費(fèi)者,但是Editor的其他部分,以及Editor外的模塊也能訪問它們。
官方文檔-Assets/Registry
簡而言之,AssetRegisty可以搜尋uasset文件,并用FAssetData抽象表示,并在有需要時(shí)根據(jù)FAssetData中的路徑線索加載UObject。
Asset Registry類圖:
4.4.1 如何搜尋asset文件
使用AssetRegistry,我們可以方便的從磁盤上讀取Asset文件并構(gòu)建FAssetData描述。
同步方式
通過上層接口進(jìn)行調(diào)用,通常只能通過同步的方式搜尋,通常使用以下接口:
ScanFilesSynchronous(const TArray<FString>& InFilePaths, bool bForceRescan = false)搜尋InFilePaths路徑下的所有asset,不需要返回值,因?yàn)樗褜ず蟮慕Y(jié)果會(huì)保存在FAssetRegistryState中,之后可以使用過濾器進(jìn)行查詢。
具體方式為新建一個(gè)FAssetDataGatherer,把模式設(shè)置為同步,然后FAssetDataGatherer就會(huì)直接阻塞的調(diào)用run()方法,為我們搜尋asset了。
類似的,也可以使用ScanFilesSynchronous方法進(jìn)行同步搜索。
SearchAllAssets(bool bSynchronousSearch)一種更簡單的方式:同步搜尋所有Assets。
SearchAllAssets方法可以以同步和異步方式運(yùn)行,該方法的同步模式通常用于CommandLet中,因?yàn)榇藭r(shí)對時(shí)間消耗并不是很敏感。
此方法同步模式最終執(zhí)行方式其實(shí)是和ScanFilesSynchronous相同的,只是為我們自動(dòng)得到了所有要搜尋的路徑。
異步方法
異步方式通常由編輯器內(nèi)部調(diào)用,尚不清楚如何通過上層進(jìn)行調(diào)用。
SearchAllAssets(bool bSynchronousSearch)之前說了,SearchAllAssets可以以異步模式執(zhí)行,其實(shí)現(xiàn)方法也比較直觀,為新建了一個(gè)FAssetDataGatherer對象,然后在UAssetRegistryImpl::Tick中獲取。
直接調(diào)用異步SearchAllAssets的地方目前只有一處,就是UAssetRegistryImpl的構(gòu)造函數(shù)中,當(dāng)在編輯器中,且不通過CommandLet時(shí),就會(huì)調(diào)用,其作用是初始化ContentBrowser。
我們剛打開編輯器時(shí)的初始化加載過程就是在做異步資源發(fā)現(xiàn)操作。
讓我們看一下Tick是如何執(zhí)行的,調(diào)用過程為:
- EngineTick()
- UEditorEngine::Tick 編輯器EngineTick
- FAssetRegistryModule::TickAssetRegistry
- UAssetRegistryImpl::Tick
在UAssetRegistryImpl::Tick中,會(huì)不斷通過FAssetDataGatherer類型的成員變量BackgroundAssetSearch獲取新搜尋到的FAssetData,并重置BackgroundAssetSearch的搜尋結(jié)果。
之后AssetRegistry就會(huì)更新自己和State的數(shù)據(jù),并發(fā)送一些廣播,通知搜尋到 了新的assetdata。
通過調(diào)用過程可以看到,在引擎的循環(huán)中,如果是編輯器,那FAssetRegistry的Tick始終在執(zhí)行,以此達(dá)到保持編輯器中assetdata始終于磁盤同步的目的。
AddPathToSearch(const FString& Path)這個(gè)方法是私有方法,并不能被上層調(diào)用,作用為向異步掃描過程中加入待掃描路徑,編輯器自身的一些功能模塊會(huì)調(diào)用到這。
4.4.2 如何從FAssetData獲取Uobject
如前面提到的:采用FAssetData::GetAsset()即可獲得UObject。
可以再通過Cast<>轉(zhuǎn)換成對應(yīng)的資源類型。
4.5 對象的創(chuàng)建與銷毀
既然介紹了一些資源的創(chuàng)建獲取。
下面再整理一些常用的C++層進(jìn)行對象的創(chuàng)建與銷毀方法:
純C++類的創(chuàng)建與銷毀
創(chuàng)建:
- 通過new來創(chuàng)建對象,推薦用智能指針管理。
銷毀:
- 智能指針計(jì)數(shù)器會(huì)自動(dòng)消亡。
UObject及其子類創(chuàng)建與銷毀
創(chuàng)建:
UObjectClass* MyClass = NewObject<UObjectClass>();銷毀:
- 虛幻的垃圾回收機(jī)制自動(dòng)回收。
AActor及其子類的創(chuàng)建與銷毀
創(chuàng)建:
- 使用SpawnActor(有七個(gè)重載函數(shù))。
銷毀:
- 虛幻的垃圾回收機(jī)制自動(dòng)回收;
Component的創(chuàng)建
構(gòu)造器中創(chuàng)建組件:
- 可以使用 CreateDefaultSubobject來創(chuàng)建組件,且此函數(shù)只能在構(gòu)建函數(shù)中使用。
其它函數(shù)中創(chuàng)建組件:
- 組件繼承自UObject類,使用NewObject來創(chuàng)建,但創(chuàng)建完成后需要進(jìn)行手動(dòng)注冊才能生效。
五、縮略圖
了解了路徑和資源之后,我們再來看看縮略圖的渲染。(雖然筆者認(rèn)為這個(gè)可能基本不會(huì)去改它)
ThumbnailRenderer.h
定義了一個(gè)渲染指定Object縮略圖的抽象基類。
/**** This is an abstract base class that is used to define the interface that* UnrealEd will use when rendering a given object's thumbnail. The editor* only calls the virtual rendering function.*/#pragma once #include "CoreMinimal.h" #include "UObject/ObjectMacros.h" #include "UObject/Object.h" #include "ThumbnailRenderer.generated.h"class FCanvas; class FRenderTarget;UCLASS(abstract, MinimalAPI) class UThumbnailRenderer : public UObject {GENERATED_UCLASS_BODY()public:/*** Returns true if the renderer is capable of producing a thumbnail for the specified asset.** @param Object the asset to attempt to render*/virtual bool CanVisualizeAsset(UObject* Object) { return true; }/*** Calculates the size the thumbnail would be at the specified zoom level** @param Object the object the thumbnail is of* @param Zoom the current multiplier of size* @param OutWidth the var that gets the width of the thumbnail* @param OutHeight the var that gets the height*/virtual void GetThumbnailSize(UObject* Object, float Zoom, uint32& OutWidth, uint32& OutHeight) const PURE_VIRTUAL(UThumbnailRenderer::GetThumbnailSize,);/*** Draws a thumbnail for the object that was specified.** @param Object the object to draw the thumbnail for* @param X the X coordinate to start drawing at* @param Y the Y coordinate to start drawing at* @param Width the width of the thumbnail to draw* @param Height the height of the thumbnail to draw* @param Viewport the viewport being drawn in* @param Canvas the render interface to draw with*/UE_DEPRECATED(4.25, "Please override the other prototype of the Draw function.")virtual void Draw(UObject* Object, int32 X, int32 Y, uint32 Width, uint32 Height, FRenderTarget* Viewport, FCanvas* Canvas) { Draw(Object, X, Y, Width, Height, Viewport, Canvas, false); }/*** Draws a thumbnail for the object that was specified.** @param Object the object to draw the thumbnail for* @param X the X coordinate to start drawing at* @param Y the Y coordinate to start drawing at* @param Width the width of the thumbnail to draw* @param Height the height of the thumbnail to draw* @param Viewport the viewport being drawn in* @param Canvas the render interface to draw with* @param bAdditionalViewFamily whether this draw should write over the render target (true) or clear it before (false)*/virtual void Draw(UObject* Object, int32 X, int32 Y, uint32 Width, uint32 Height, FRenderTarget* Viewport, FCanvas* Canvas, bool bAdditionalViewFamily) PURE_VIRTUAL(UThumbnailRenderer::Draw, );/*** Checks to see if the specified asset supports realtime thumbnails, which will cause them to always be rerendered to reflect any changes* made to the asset. If this is false, thumbnails should render once and then not update again.* For most renderers, this should remain as true.** @param Object The asset to draw the thumbnail for** @return True if the thumbnail needs to always be redrawn, false if it can be just drawn once and then reused.*/virtual bool AllowsRealtimeThumbnails(UObject* Object) const { return true; }protected:/** Renders the thumbnail's view family. */UNREALED_API static void RenderViewFamily(FCanvas* Canvas, class FSceneViewFamily* ViewFamily); };它有大量的子類:
- UCurveLinearColorThumbnailRenderer
- USoundWaveThumbnailRenderer
- UTextureThumbnailRenderer
- URuntimeVirtualTextureThumbnailRenderer
- UPaperSpriteThumbnailRenderer
- UPaperTileSetThumbnailRenderer
- UGeometryCacheThumbnailRenderer
- UGeometryCollectionThumbnailRenderer
- UDestructibleMeshThumbnailRenderer
- UFoliageType_ISMThumbnailRenderer
- UAnimBlueprintThumbnailRenderer
- UAnimSequenceThumbnailRenderer
- UBlendSpaceThumbnailRenderer
- UBlueprintThumbnailRenderer
- UClassThumbnailRenderer
- ULevelThumbnailRenderer
- UMaterialFunctionThumbnailRenderer
- UMaterialInstanceThumbnailRenderer
- UPhysicsAssetThumbnailRenderer
- USkeletalMeshThumbnailRenderer
- USlateBrushThumbnailRenderer
- UStaticMeshThumbnailRenderer
- UVolumeTextureThumbnailRenderer
- UWorldThumbnailRenderer
詳情可以參考 AssetThumbnail資源縮略圖。
注:
- 筆者在加某個(gè)編輯器(例如Mesh編輯器)加功能的過程中,曾遇到過崩潰,是縮略圖或者Preview窗口的渲染引起的。
- 這里是一個(gè)需要考慮的問題,當(dāng)在做對編輯器源碼進(jìn)行修改的時(shí)候。
參考文章
- UE Plugins官方介紹
- UE4 PluginDescriptor字段描述
- UE4模塊官方文檔
- UE4模塊文章
- Slate概述
- UE文檔 Slate UI框架
- 【UE4 Renderer】<02> Slate系統(tǒng)
- Slate渲染流程
- UE資源路徑相關(guān)整理分析
- UE4 4.20 UE4獲取所有特定資源(FAssetData)的資源路徑
- 虛幻4渲染編程(工具篇)【第八卷:Asset creation】
- 虛幻4渲染編程(工具篇)【第九卷:SlateUI布局】
- UTexture2D的讀取與寫入數(shù)據(jù)并保存成為Asset
- UE4 AssetRegistry分析
- 對象創(chuàng)建與資源獲取
- 資源管理之UAssetManager用法
- AssetThumbnail資源縮略圖
總結(jié)
以上是生活随笔為你收集整理的虚幻引擎编辑器开发基础(一)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 什么是项目管理?范围、时间、成本、质量
- 下一篇: 关于 @EnableConfigurat