惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Google DeepMind News
Google DeepMind News
爱范儿
爱范儿
J
Java Code Geeks
L
LangChain Blog
V
V2EX
大猫的无限游戏
大猫的无限游戏
S
SegmentFault 最新的问题
博客园 - Franky
Microsoft Azure Blog
Microsoft Azure Blog
Jina AI
Jina AI
Blog — PlanetScale
Blog — PlanetScale
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
The Cloudflare Blog
博客园 - 司徒正美
B
Blog
G
Google Developers Blog
Stack Overflow Blog
Stack Overflow Blog
罗磊的独立博客
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Apple Machine Learning Research
Apple Machine Learning Research
Engineering at Meta
Engineering at Meta
MyScale Blog
MyScale Blog
有赞技术团队
有赞技术团队
Hugging Face - Blog
Hugging Face - Blog

智伤帝的个人博客

Maya 拍屏方案汇总 C++ 道法器术 C++ 基础入门 FBX 二进制数据解析 Maya RBF 算法应用 Maya 顶点色单通道笔刷 Maya CurveBrush 笔刷开发 Maya C++ pyd 模块开发 Unreal C++ VScode 配置 Maya C++ mll hot reload 研究 Maya CMake 构建 C++ 插件编译环境 Unreal Python 导出 MetaHuman 控制器关键帧 TA 工具人知乎分享 Python 代码规范 Python - Import 机制 Python doit 库 Python dependencies 库 Python blinker 库 Python marshmallow 库
Unreal C++ 工具开发最小实践
智伤帝 · 2022-07-15 · via 智伤帝的个人博客

前言

  Unreal 的学习浩瀚且博杂,有时候一个最小 Demo 就是很好的学习起点。
  想起我以前翻阅 UE 的源码一大堆的文件,看得我是无比头疼。
  偶然间发现 CSDN YakSue 写了好多篇 Unreal 工具开发的 介绍。
  虽然没有配上 Github 链接,但是源码都在文章里面体现了。
  对于工具开发的不同模块都大有裨益。
  于是我将这些内容整合到一起,并且详细讲解其中实现的核心点。

Custom Asset

https://yaksue.blog.csdn.net/article/details/107646900
https://github.com/FXTD-ODYSSEY/Unreal-Playground/tree/main/Plugins/Yaksue/TestAssetEditorPlg

  创建一个自定义的 Asset 需要有三个类

  1. Asset (UObject)
  2. AssetFactory (UFactory)
  3. AssetTypeActions (FAssetTypeActions_Base)

  Asset 描述对象本身的数据
  AssetFactory 描述如何创建对象
  AssetTypeActions 返回对象显示的信息

  AssetTypeActions 包含方法 GetName GetTypeColor GetSupportedClass GetCategories 用来描述对应的信息。
  GetCategories 会分配 Asset 所属的位置。

  这个方式默认打开的窗口是 Details Panel.
  如果想要自定义打开的窗口需要添加 FAssetEditorToolkit
  AssetTypeActions 添加 OpenAssetEditor 方法将 Toolkit 生成并初始化。

1
2
3
4
5
6
7
FAssetEditorToolkit
GetToolkitFName
GetBaseToolkitName
GetWorldCentricTabPrefix
GetWorldCentricTabColorScale
Initialize
RegisterTabSpawners

  RegisterTabSpawners 通过这个方法注册生产 Tab 的 ID
  后续通过 Initialize 方法调用 AddTab 将 Register 的 Tab 生成。
  最后通过 FAssetEditorToolkit::InitAssetEditor 完成 Toolkit 的初始化


  如果不想将 Asset 放到 EAssetTypeCategories::Misc 的分类中。
  也可以构建一个新的标签附上去。
  只是需要将 factory 相关的 GetMenuCategories 放入去掉。
  我之前没有去掉,一直很疑惑为啥自定义菜单没有生效。

1
2
3
4
5
6
7
8
9
10
11
FYaksueTestAssetTypeActions::FYaksueTestAssetTypeActions()
{

IAssetTools &AssetTools = FModuleManager::LoadModuleChecked<FAssetToolsModule>("AssetTools").Get();
AssetCategory = AssetTools.RegisterAdvancedAssetCategory(FName(TEXT("Custom Assets")), LOCTEXT("CustomAssetCategory", "Custom Assets"));
}

uint32 FYaksueTestAssetTypeActions::GetCategories()
{
return AssetCategory;
}

  构造函数注册新的分类,头文件需要添加上定义 FYaksueTestAssetTypeActions(); EAssetTypeCategories::Type AssetCategory;

Custom Filter

https://yaksue.blog.csdn.net/article/details/120929455
https://github.com/FXTD-ODYSSEY/Unreal-Playground/tree/main/Plugins/Yaksue/TestCustomFilter

  继承 UContentBrowserFrontEndFilterExtension 可以通过 override AddFrontEndFilterExtensions 方法扩展 filter。
  生成一个 FFrontendFilter 子类,然后通过 AddFrontEndFilterExtensions 将过滤对象添加到过滤列表里面。
  FFrontendFilter 最核心的方法就是 PassesFilter 它会将每个 item 传到这个函数返回 bool 来决定是否显示。

Slate

https://yaksue.blog.csdn.net/article/details/110084013

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

SNew(SOverlay)
+ SOverlay::Slot()
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot().FillWidth(0.3f)
[
SNew(SButton)1
]
+ SHorizontalBox::Slot().FillWidth(0.7f)
[
SNew(SVerticalBox)
+ SVerticalBox::Slot().FillHeight(0.5f)
[
SNew(SButton)
]
+ SVerticalBox::Slot().FillHeight(0.5f)
[
SNew(SButton)
]
]
]
+ SOverlay::Slot()
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot().FillWidth(1.0f)
+ SHorizontalBox::Slot().AutoWidth()
[
SNew(SVerticalBox)
+ SVerticalBox::Slot().FillHeight(1.0f)
+ SVerticalBox::Slot().AutoHeight()
[
SNew(SBox)
.HeightOverride(128)
.WidthOverride(128)
[
SNew(SButton)
]
]
+ SVerticalBox::Slot().FillHeight(1.0f)
]
+ SHorizontalBox::Slot().FillWidth(1.0f)
]

  使用 Unreal Slate 构建窗口,通过代码的属性结构来描述 UI 的构成和配置。

alt

DockTab Layout

https://yaksue.blog.csdn.net/article/details/109321869

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65

void FTestLayoutWindowModule::StartupModule()
{


FTestLayoutWindowStyle::Initialize();
FTestLayoutWindowStyle::ReloadTextures();

FTestLayoutWindowCommands::Register();

PluginCommands = MakeShareable(new FUICommandList);

PluginCommands->MapAction(
FTestLayoutWindowCommands::Get().OpenLayoutWindow,
FExecuteAction::CreateRaw(this, &FTestLayoutWindowModule::PluginButtonClicked),
FCanExecuteAction());

FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked<FLevelEditorModule>("LevelEditor");

{
TSharedPtr<FExtender> MenuExtender = MakeShareable(new FExtender());
MenuExtender->AddMenuExtension("WindowLayout", EExtensionHook::After, PluginCommands, FMenuExtensionDelegate::CreateRaw(this, &FTestLayoutWindowModule::AddMenuExtension));

LevelEditorModule.GetMenuExtensibilityManager()->AddExtender(MenuExtender);
}

{
TSharedPtr<FExtender> ToolbarExtender = MakeShareable(new FExtender);
ToolbarExtender->AddToolBarExtension("Settings", EExtensionHook::After, PluginCommands, FToolBarExtensionDelegate::CreateRaw(this, &FTestLayoutWindowModule::AddToolbarExtension));

LevelEditorModule.GetToolBarExtensibilityManager()->AddExtender(ToolbarExtender);
}

FGlobalTabmanager::Get()->RegisterNomadTabSpawner(TestLayoutWindowTabName, FOnSpawnTab::CreateRaw(this, &FTestLayoutWindowModule::OnSpawnPluginTab))
.SetDisplayName(LOCTEXT("FTestLayoutWindowTabTitle", "TestLayoutWindow"))
.SetMenuType(ETabSpawnerMenuType::Hidden);


FGlobalTabmanager::Get()->RegisterNomadTabSpawner(InnerTabName, FOnSpawnTab::CreateLambda([](const FSpawnTabArgs& SpawnTabArgs)
{
return
SNew(SDockTab)
.TabRole(ETabRole::NomadTab)
[
SNew(STextBlock)
.Text(FText::FromString("InnerTab"))
];
}))
.SetDisplayName(LOCTEXT("InnerTab", "InnerTab"))
.SetMenuType(ETabSpawnerMenuType::Hidden);


FGlobalTabmanager::Get()->RegisterNomadTabSpawner(InnerTabName2, FOnSpawnTab::CreateLambda([](const FSpawnTabArgs& SpawnTabArgs)
{
return
SNew(SDockTab)
.TabRole(ETabRole::NomadTab)
[
SNew(STextBlock)
.Text(FText::FromString("InnerTab2"))
];
}))
.SetDisplayName(LOCTEXT("InnerTab2", "InnerTab2"))
.SetMenuType(ETabSpawnerMenuType::Hidden);
}

  核心处理是在插件加载的时候 StartupModule 调用 RegisterNomadTabSpawner 注册 Tab

1
2
3
4
void FTestLayoutWindowModule::PluginButtonClicked()
{
FGlobalTabmanager::Get()->InvokeTab(TestLayoutWindowTabName);
}

  点击 GUI 会触发 Tab 生成,调用 OnSpawnPluginTab 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
TSharedRef<SDockTab> FTestLayoutWindowModule::OnSpawnPluginTab(const FSpawnTabArgs& SpawnTabArgs)
{

const TSharedRef<SDockTab> NomadTab = SNew(SDockTab)
.TabRole(ETabRole::NomadTab);


if (!TabManager.IsValid())
{
TabManager = FGlobalTabmanager::Get()->NewTabManager(NomadTab);
}


if (!TabManagerLayout.IsValid())
{
TabManagerLayout = FTabManager::NewLayout("TestLayoutWindow")
->AddArea
(
FTabManager::NewPrimaryArea()
->SetOrientation(Orient_Vertical)
->Split
(
FTabManager::NewStack()
->SetSizeCoefficient(.4f)
->AddTab(InnerTabName, ETabState::OpenedTab)
)
->Split
(
FTabManager::NewStack()
->SetSizeCoefficient(.4f)
->AddTab(InnerTabName2, ETabState::OpenedTab)
)
);

}


TSharedRef<SWidget> TabContents = TabManager->RestoreFrom(TabManagerLayout.ToSharedRef(), TSharedPtr<SWindow>()).ToSharedRef();


NomadTab->SetContent(
TabContents
);

return NomadTab;
}

  这里将之前注册的 Tab 唤起。

Viewport

https://yaksue.blog.csdn.net/article/details/109258860

  引入默认的 SEditorViewport
  然后 override 方法 MakeEditorViewportClient

1
2
3
4
5
TSharedRef<FEditorViewportClient> STestLevelEditorViewport::MakeEditorViewportClient()
{
TSharedPtr<FEditorViewportClient> EditorViewportClient = MakeShareable(new FEditorViewportClient(nullptr));
return EditorViewportClient.ToSharedRef();
}

  然后Slate 代码直接使用 SNew(STestLevelEditorViewport) 初始化界面即可。
  不过这个方式沿用了 Viewport ,如何构建一个自定义 Viewport 呢?


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
TSharedRef<FEditorViewportClient> STestEditorViewport::MakeEditorViewportClient()
{
PreviewScene = MakeShareable(new FPreviewScene());


{

UStaticMesh* SM = LoadObject<UStaticMesh>(NULL, TEXT("StaticMesh'/Engine/EngineMeshes/Cube.Cube'"), NULL, LOAD_None, NULL);

UStaticMeshComponent* SMC = NewObject<UStaticMeshComponent>();
SMC->SetStaticMesh(SM);

PreviewScene->AddComponent(SMC, FTransform::Identity);
}

TSharedPtr<FEditorViewportClient> EditorViewportClient = MakeShareable(new FEditorViewportClient(nullptr, PreviewScene.Get()));

return EditorViewportClient.ToSharedRef();
}

  新建一个自定义的 FPreviewScene ,可以将物体实例化添加到场景当中。
  将 PreviewScene 传入到 FEditorViewportClient 中,这样 Viewport 就显示独立的场景。

1
2
3
4
5
TSharedPtr<SWidget> STestEditorViewport::MakeViewportToolbar()
{
return SNew(SCommonEditorViewportToolbarBase, SharedThis(this));
}

  使用上面的代码可以构建出默认 Viewport 的 Toolbar。

GraphEditor

https://yaksue.blog.csdn.net/article/details/107945507
https://yaksue.blog.csdn.net/article/details/108020797
https://yaksue.blog.csdn.net/article/details/108227439
https://yaksue.blog.csdn.net/article/details/109347063

EditorMode

版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 智伤帝的个人博客

打赏

  • 微信

    微信

  • 支付宝

    支付宝