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

推荐订阅源

S
SegmentFault 最新的问题
A
About on SuperTechFans
NISL@THU
NISL@THU
V
Visual Studio Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
B
Blog RSS Feed
Engineering at Meta
Engineering at Meta
GbyAI
GbyAI
美团技术团队
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Google DeepMind News
Google DeepMind News
小众软件
小众软件
博客园 - Franky
罗磊的独立博客
The Cloudflare Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
酷 壳 – CoolShell
酷 壳 – CoolShell
量子位
Hugging Face - Blog
Hugging Face - Blog
云风的 BLOG
云风的 BLOG
MongoDB | Blog
MongoDB | Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
B
Blog
The GitHub Blog
The GitHub Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
P
Proofpoint News Feed
有赞技术团队
有赞技术团队
Hacker News - Newest:
Hacker News - Newest: "LLM"
Cyberwarzone
Cyberwarzone
C
CXSECURITY Database RSS Feed - CXSecurity.com
Project Zero
Project Zero
Security Latest
Security Latest
L
Lohrmann on Cybersecurity
AWS News Blog
AWS News Blog
The Hacker News
The Hacker News
I
Intezer
J
Java Code Geeks
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
P
Privacy International News Feed
月光博客
月光博客
A
Arctic Wolf
Apple Machine Learning Research
Apple Machine Learning Research
D
Darknet – Hacking Tools, Hacker News & Cyber Security
博客园_首页
WordPress大学
WordPress大学
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
T
Tor Project blog
博客园 - 三生石上(FineUI控件)
The Last Watchdog
The Last Watchdog
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org

智伤帝的个人博客

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 许可协议。转载请注明来自 智伤帝的个人博客

打赏

  • 微信

    微信

  • 支付宝

    支付宝