Lyra技术解析 - 编辑器模块 | ebp
文章

Lyra技术解析 - 编辑器模块

这是一系列关于我从Epic的Lyra项目中学到的知识笔记。该项目声称展示了当前虚幻引擎框架下的最佳实践。其中有些内容是我之前不了解的,有些则已经知晓,但认为仍然值得记录。

Lyra技术解析 - 编辑器模块

The content in this post is based on Unreal Engine 5.5.4

分离的.uproject与模块命名

Epic可能采用了某种自动化项目设置方法,或是后期手动重命名了uproject文件。对比EGS平台预编译版本和Github上的Lyra项目,两者的项目名称并不一致——一个名为LyraStarterGame,另一个则简称为Lyra

From EGS Lyra from EGS

From Github Lyra from Github

虽然这并非关键问题,但观察Source目录会发现,无论是Lyra还是LyraStarterGame都不是实际模块名称(如果通过向导创建项目,模块名通常会与项目名相同)。真正的模块名为LyraGameLyraEditor。这揭示了一个重要设计理念:uproject文件和文件夹名称代表的是项目本身,而在Source代码层面,开发者可以对模块名称进行更精细的控制,二者无需强制保持一致。

自定义引擎类

Lyra项目包含两个自定义引擎扩展类:LyraGameEngineLyraEditorEngine。本文将重点解析LyraEditorEngine的实现。

UGameEngine, UEditorEngine, UUnrealEdEngine

UUnrealEdEngine继承自UEditorEngine,后者专门处理编辑器内的交互逻辑(如选择Actor行为、PIE会话前的自定义代码注入等)。通常我们应该基于UUnrealEdEngine进行扩展,因为UEditorEngine是更高层次的抽象基类,而UUnrealEdEngine已实现了完整的编辑器功能。若直接继承UEditorEngine,可能需要重新实现大量已有功能。

当以Commandlet模式运行引擎时,UEditorEngine会更有价值(详见Commandlet Documentation)。这对RBS、DevOps等自动化流程非常有用。

Lyra扩展编辑器引擎主要出于两个目的:

  • 游戏特性插件可见性:Lyra将所有核心玩法模块置于Plugins/GameFeature目录,利用GameFeature系统管理。默认在内容浏览器显示这些插件内容能提升开发体验。
  • 临时解决方案:通过编辑器引擎在PIE启动时通知ULyraDeveloperSettingsULyraPlatformEmulationSettings。这是过渡方案,Epic正在开发更完善的设置暴露机制。

首帧初始化逻辑

由于所有游戏模式都以GameFeature形式存在于Plugins目录,Lyra通过ULyraEditorEngineTick函数中的FirstTickSetup实现默认显示。这是虚幻引擎的常见模式——利用首帧Tick执行一次性初始化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void ULyraEditorEngine::Tick(float DeltaSeconds, bool bIdleMode)
{
	Super::Tick(DeltaSeconds, bIdleMode);
	
	FirstTickSetup();
}

void ULyraEditorEngine::FirstTickSetup()
{
	if (bFirstTickSetup)
	{
		return;
	}

	bFirstTickSetup = true;

	// Force show plugin content on load.
	GetMutableDefault<UContentBrowserSettings>()->SetDisplayPluginFolders(true);
}

开发者设置与平台模拟配置

如代码注释所述,当前实现直接耦合了ULyraDeveloperSettingsULyraPlatformEmulationSettings。更优解是通过可绑定委托让非编辑器模块进行订阅,但由于需要避免游戏模块对编辑器模块的依赖,这种实现会相对复杂。

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
FGameInstancePIEResult ULyraEditorEngine::PreCreatePIEInstances(const bool bAnyBlueprintErrors, const bool bStartInSpectatorMode, const float PIEStartTime, const bool bSupportsOnlinePIE, int32& InNumOnlinePIEInstances)
{
	if (const ALyraWorldSettings* LyraWorldSettings = Cast<ALyraWorldSettings>(EditorWorld->GetWorldSettings()))
	{
		if (LyraWorldSettings->ForceStandaloneNetMode)
		{
			EPlayNetMode OutPlayNetMode;
			PlaySessionRequest->EditorPlaySettings->GetPlayNetMode(OutPlayNetMode);
			if (OutPlayNetMode != PIE_Standalone)
			{
				PlaySessionRequest->EditorPlaySettings->SetPlayNetMode(PIE_Standalone);

				FNotificationInfo Info(LOCTEXT("ForcingStandaloneForFrontend", "Forcing NetMode: Standalone for the Frontend"));
				Info.ExpireDuration = 2.0f;
				FSlateNotificationManager::Get().AddNotification(Info);
			}
		}
	}

	//@TODO: Should add delegates that a *non-editor* module could bind to for PIE start/stop instead of poking directly
	GetDefault<ULyraDeveloperSettings>()->OnPlayInEditorStarted();
	GetDefault<ULyraPlatformEmulationSettings>()->OnPlayInEditorStarted();

	//
	FGameInstancePIEResult Result = Super::PreCreatePIEServerInstance(bAnyBlueprintErrors, bStartInSpectatorMode, PIEStartTime, bSupportsOnlinePIE, InNumOnlinePIEInstances);

	return Result;
}

配置自定义引擎类

定义完自定义的GameEngine和EditorEngine后,需要在DefaultEngine.ini文件的[/Script/Engine.Engine]节点下进行配置。

1
2
3
4
[/Script/Engine.Engine]
GameEngine=/Script/LyraGame.LyraGameEngine
UnrealEdEngine=/Script/LyraEditor.LyraEditorEngine
EditorEngine=/Script/LyraEditor.LyraEditorEngine

自定义可配置变量

DefaultEngine.ini是存储引擎设置的配置文件,但我们也可以创建项目专属的配置变量。只需在类声明中使用config说明符并指定目标配置文件,例如Lyra中的ULyraDeveloperSettings就配置为使用EditorPerProjectUserSettings文件。

1
2
3
4
5
UCLASS(config=EditorPerProjectUserSettings, MinimalAPI)
class ULyraDeveloperSettings : public UDeveloperSettingsBackedByCVars
{
	//...
}

要使变量可配置,还需在属性声明中添加config说明符。以Lyra中的CommonEditorMaps数组为例:

1
2
3
4
5
#if WITH_EDITORONLY_DATA
	/** A list of common maps that will be accessible via the editor toolbar */
	UPROPERTY(config, EditAnywhere, BlueprintReadOnly, Category=Maps, meta=(AllowedClasses="/Script/Engine.World"))
	TArray<FSoftObjectPath> CommonEditorMaps;
#endif

在配置文件中,可以通过+符号添加数组元素。

1
2
3
4
5
6
7
; Some commonly used editor maps that will be displayed in the editor task bar
[/Script/LyraGame.LyraDeveloperSettings]
+CommonEditorMaps=/Game/System/FrontEnd/Maps/L_LyraFrontEnd.L_LyraFrontEnd
+CommonEditorMaps=/Game/System/DefaultEditorMap/L_DefaultEditorOverview.L_DefaultEditorOverview
+CommonEditorMaps=/ShooterMaps/Maps/L_Expanse.L_Expanse
+CommonEditorMaps=/ShooterCore/Maps/L_ShooterGym.L_ShooterGym
+CommonEditorMaps=/ShooterTests/Maps/L_ShooterTest_DeviceProperties.L_ShooterTest_DeviceProperties

GetOptions 元数据

GetOptions元数据能让属性在编辑器中显示为下拉菜单,其选项由指定函数动态生成。例如ULyraPlatformEmulationSettings中的PretendPlatform成员,通过返回平台ID列表的函数实现下拉选项。

该特性同样适用于函数参数。如下例所示,通过UPARAM宏配合Meta说明符,ProfileName参数将显示为碰撞预设下拉菜单:

Get Options GetOptions Meta

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
/**
 * Platform emulation settings
 */
UCLASS(config=EditorPerProjectUserSettings, MinimalAPI)
class ULyraPlatformEmulationSettings : public UDeveloperSettingsBackedByCVars
{
	// ...
private:
	UPROPERTY(EditAnywhere, config, Category=PlatformEmulation, meta=(GetOptions=GetKnownPlatformIds))
	FName PretendPlatform;

	// ...
	UFUNCTION()
	TArray<FName> GetKnownPlatformIds() const;
	
	//...
}

TArray<FName> ULyraPlatformEmulationSettings::GetKnownPlatformIds() const
{
	TArray<FName> Results;

#if WITH_EDITOR
	Results.Add(NAME_None);
	Results.Append(UPlatformSettingsManager::GetKnownAndEnablePlatformIniNames());
#endif

	return Results;
}

这种设计允许通过内联宏来修饰函数参数,极大提升了编辑器交互的灵活性。

1
2
UFUNCTION(BlueprintCallable, ...)
static ENGINE_API bool LineTraceSingleByProfile(..., UPARAM(Meta=(GetOptions="Engine.KismetSystemLibrary.GetCollisionProfileNames")) FName ProfileName, ...);

编辑器Toast通知设置

有时我们需要在编辑器中显示Toast通知,这可以通过FSlateNotificationManager类实现。以下示例展示了如何在ULyraDeveloperSettings类中通知开发者某些设置已配置:通过创建FNotificationInfo对象并传递给FSlateNotificationManager类的AddNotification函数。

Toast Notification

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
FGameInstancePIEResult ULyraEditorEngine::PreCreatePIEInstances(const bool bAnyBlueprintErrors, const bool bStartInSpectatorMode, const float PIEStartTime, const bool bSupportsOnlinePIE, int32& InNumOnlinePIEInstances)
{
	if (const ALyraWorldSettings* LyraWorldSettings = Cast<ALyraWorldSettings>(EditorWorld->GetWorldSettings()))
	{
		if (LyraWorldSettings->ForceStandaloneNetMode)
		{
			EPlayNetMode OutPlayNetMode;
			PlaySessionRequest->EditorPlaySettings->GetPlayNetMode(OutPlayNetMode);
			if (OutPlayNetMode != PIE_Standalone)
			{
				PlaySessionRequest->EditorPlaySettings->SetPlayNetMode(PIE_Standalone);

				FNotificationInfo Info(LOCTEXT("ForcingStandaloneForFrontend", "Forcing NetMode: Standalone for the Frontend"));
				Info.ExpireDuration = 2.0f;
				FSlateNotificationManager::Get().AddNotification(Info);
			}
		}
	}
}

void ULyraDeveloperSettings::OnPlayInEditorStarted() const
{
	// Show a notification toast to remind the user that there's an experience override set
	if (ExperienceOverride.IsValid())
	{
		FNotificationInfo Info(FText::Format(
			LOCTEXT("ExperienceOverrideActive", "Developer Settings Override\nExperience {0}"),
			FText::FromName(ExperienceOverride.PrimaryAssetName)
		));
		Info.ExpireDuration = 2.0f;
		FSlateNotificationManager::Get().AddNotification(Info);
	}
}

创建新资产类

当现有类无法满足需求时,我们可能需要创建更特殊的类型,比如为自定义类创建专属编辑器,或者注册直接在内容浏览器中显示的新资产类型(作为新资产类型而非蓝图类)。

New Asset Type

本文不涉及具体创建方法,因为Community Post已有详细说明。

提升编译性能

UE5.1引入了UE_INLINE_GENERATED_CPP_BY_NAME宏来提升编译性能(参见UE5.1 Official Release Note)。Lyra中大多数类都在cpp文件开头包含其他头文件后使用这个宏,例如:

1
2
3
4
5
6
7
8
9
10
// Copyright Epic Games, Inc. All Rights Reserved.

#include "LyraGameViewportClient.h"

#include "CommonUISettings.h"
#include "ICommonUIModule.h"

#include UE_INLINE_GENERATED_CPP_BY_NAME(LyraGameViewportClient)

//... Actual Class Implementation

自定义PIE行为

如前所述,我们可以为PIE模式编写不同的行为逻辑。由于PIE是仅发生在编辑器中的情况,我们可以在PIE启动时调用游戏模块函数。

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
/**
 * FLyraEditorModule
 */
class FLyraEditorModule : public FDefaultGameModuleImpl
{
	// ...
	virtual void StartupModule() override
	{
		FGameEditorStyle::Initialize();

		if (!IsRunningGame())
		{
			// ...
			FEditorDelegates::BeginPIE.AddRaw(this, &ThisClass::OnBeginPIE);
			FEditorDelegates::EndPIE.AddRaw(this, &ThisClass::OnEndPIE);
		}
		// ...
	}

	void OnBeginPIE(bool bIsSimulating)
	{
		ULyraExperienceManager* ExperienceManager = GEngine->GetEngineSubsystem<ULyraExperienceManager>();
		check(ExperienceManager);
		ExperienceManager->OnPlayInEditorBegun();
	}
	//...
}

自定义编辑器按钮扩展

我们可以创建自定义编辑器按钮来执行开发时验证或自动化任务,这基本上只需要我们创建FToolMenuEntry并将其添加到属于UToolMenuFToolMenuSection中:LevelEditor.LevelEditorToolBar.PlayToolBar

Custom Toolbar Buttons

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
66
67
68
69
70
71
72
73
74
75
/**
 * FLyraEditorModule
 */
class FLyraEditorModule : public FDefaultGameModuleImpl
{
	// ...
	virtual void StartupModule() override
	{
		FGameEditorStyle::Initialize();

		if (!IsRunningGame())
		{
			// ...
			if (FSlateApplication::IsInitialized())
			{
				ToolMenusHandle = UToolMenus::RegisterStartupCallback(FSimpleMulticastDelegate::FDelegate::CreateStatic(&RegisterGameEditorMenus));
			}

			// ...
		}
	}
	// ...
}

static void RegisterGameEditorMenus()
{
	UToolMenu* Menu = UToolMenus::Get()->ExtendMenu("LevelEditor.LevelEditorToolBar.PlayToolBar");
	FToolMenuSection& Section = Menu->AddSection("PlayGameExtensions", TAttribute<FText>(), FToolMenuInsert("Play", EToolMenuInsertType::After));

	// Uncomment this to add a custom toolbar that is displayed during PIE
	// Useful for making easy access to changing game state artificially, adding cheats, etc
	// FToolMenuEntry BlueprintEntry = FToolMenuEntry::InitComboButton(
	// 	"OpenGameMenu",
	// 	FUIAction(
	// 		FExecuteAction(),
	// 		FCanExecuteAction::CreateStatic(&HasPlayWorld),
	// 		FIsActionChecked(),
	// 		FIsActionButtonVisible::CreateStatic(&HasPlayWorld)),
	// 	FOnGetContent::CreateStatic(&YourCustomMenu),
	// 	LOCTEXT("GameOptions_Label", "Game Options"),
	// 	LOCTEXT("GameOptions_ToolTip", "Game Options"),
	// 	FSlateIcon(FAppStyle::GetAppStyleSetName(), "LevelEditor.OpenLevelBlueprint")
	// );
	// BlueprintEntry.StyleNameOverride = "CalloutToolbar";
	// Section.AddEntry(BlueprintEntry);

	FToolMenuEntry CheckContentEntry = FToolMenuEntry::InitToolBarButton(
		"CheckContent",
		FUIAction(
			FExecuteAction::CreateStatic(&CheckGameContent_Clicked),
			FCanExecuteAction::CreateStatic(&HasNoPlayWorld),
			FIsActionChecked(),
			FIsActionButtonVisible::CreateStatic(&HasNoPlayWorld)),
		LOCTEXT("CheckContentButton", "Check Content"),
		LOCTEXT("CheckContentDescription", "Runs the Content Validation job on all checked out assets to look for warnings and errors"),
		FSlateIcon(FGameEditorStyle::GetStyleSetName(), "GameEditor.CheckContent")
	);
	CheckContentEntry.StyleNameOverride = "CalloutToolbar";
	Section.AddEntry(CheckContentEntry);

	FToolMenuEntry CommonMapEntry = FToolMenuEntry::InitComboButton(
		"CommonMapOptions",
		FUIAction(
			FExecuteAction(),
			FCanExecuteAction::CreateStatic(&HasNoPlayWorld),
			FIsActionChecked(),
			FIsActionButtonVisible::CreateStatic(&CanShowCommonMaps)),
		FOnGetContent::CreateStatic(&GetCommonMapsDropdown),
		LOCTEXT("CommonMaps_Label", "Common Maps"),
		LOCTEXT("CommonMaps_ToolTip", "Some commonly desired maps while using the editor"),
		FSlateIcon(FAppStyle::GetAppStyleSetName(), "Icons.Level")
	);
	CommonMapEntry.StyleNameOverride = "CalloutToolbar";
	Section.AddEntry(CommonMapEntry);
}

检查内容按钮 - 工具栏按钮

检查内容按钮的实现很直接,点击时会执行验证任务,通过调用CheckGameContent_Clicked函数实现。在本例中,它调用了UEditorValidator::ValidateCheckedOutContent函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
FToolMenuEntry CheckContentEntry = FToolMenuEntry::InitToolBarButton(
	"CheckContent",
	FUIAction(
		FExecuteAction::CreateStatic(&CheckGameContent_Clicked),
		// ...
		),
		// ...
		FSlateIcon(FGameEditorStyle::GetStyleSetName(), "GameEditor.CheckContent")
	);

static void CheckGameContent_Clicked()
{
	UEditorValidator::ValidateCheckedOutContent(/*bInteractive=*/true, EDataValidationUsecase::Manual);
}

常用地图下拉菜单 - 组合按钮

这个实现会复杂些。点击按钮时不会立即执行操作,而是显示包含所有相关地图的下拉菜单。当用户点击某个地图时,才会调用OpenCommonMap_Clicked

这里的逻辑是创建一个ComboButton而非普通ToolBarButton,并使用FOnGetContent创建下拉菜单。FMenuBuilder类用于构建菜单,我们可以用AddMenuEntry函数添加条目。每个条目包含显示名称、工具提示和点击时要执行的操作。点击选项时会响应OpenEditorForAsset函数,在编辑器中打开选中的地图。

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
static TSharedRef<SWidget> GetCommonMapsDropdown()
{
	FMenuBuilder MenuBuilder(true, nullptr);
	
	for (const FSoftObjectPath& Path : GetDefault<ULyraDeveloperSettings>()->CommonEditorMaps)
	{
		if (!Path.IsValid())
		{
			continue;
		}
		
		const FText DisplayName = FText::FromString(Path.GetAssetName());
		MenuBuilder.AddMenuEntry(
			DisplayName,
			LOCTEXT("CommonPathDescription", "Opens this map in the editor"),
			FSlateIcon(),
			FUIAction(
				FExecuteAction::CreateStatic(&OpenCommonMap_Clicked, Path.ToString()),
				FCanExecuteAction::CreateStatic(&HasNoPlayWorld),
				FIsActionChecked(),
				FIsActionButtonVisible::CreateStatic(&HasNoPlayWorld)
			)
		);
	}

	return MenuBuilder.MakeWidget();
}

static void OpenCommonMap_Clicked(const FString MapPath)
{
	if (ensure(MapPath.Len()))
	{
		GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->OpenEditorForAsset(MapPath);
	}
}

注意for (const FSoftObjectPath& Path : GetDefault<ULyraDeveloperSettings>()->CommonEditorMaps)这行代码,它获取的是之前提到的CommonEditorMaps属性,即ULyraDeveloperSettings类中的可配置属性。这是使用GetDefault函数从ini文件访问类默认设置的好例子。

1
2
3
4
5
6
7
; Some commonly used editor maps that will be displayed in the editor task bar
[/Script/LyraGame.LyraDeveloperSettings]
+CommonEditorMaps=/Game/System/FrontEnd/Maps/L_LyraFrontEnd.L_LyraFrontEnd
+CommonEditorMaps=/Game/System/DefaultEditorMap/L_DefaultEditorOverview.L_DefaultEditorOverview
+CommonEditorMaps=/ShooterMaps/Maps/L_Expanse.L_Expanse
+CommonEditorMaps=/ShooterCore/Maps/L_ShooterGym.L_ShooterGym
+CommonEditorMaps=/ShooterTests/Maps/L_ShooterTest_DeviceProperties.L_ShooterTest_DeviceProperties

编辑器样式的单例模式

在上面的例子中,我们创建按钮时也定义了它们的图标外观。

1
2
3
4
5
	FToolMenuEntry CheckContentEntry = FToolMenuEntry::InitToolBarButton(
		"CheckContent",
		// ...
		FSlateIcon(FGameEditorStyle::GetStyleSetName(), "GameEditor.CheckContent")
	);

虽然可以直接定义按钮图标,但我们也可以将它们集中管理。在本例中,GameEditor.CheckContent实际上是在简单的FGameEditorStyle单例中设置的。初始化时,它创建StyleInstance并注册到FSlateStyleRegistry。这是虚幻引擎中的常见模式,用于集中管理样式和资源。实际图标位于Content/Editor/Slate/Icons/CheckContent.svg

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
TSharedPtr< FSlateStyleSet > FGameEditorStyle::StyleInstance = nullptr;

void FGameEditorStyle::Initialize()
{
	if ( !StyleInstance.IsValid() )
	{
		StyleInstance = Create();
		FSlateStyleRegistry::RegisterSlateStyle( *StyleInstance );
	}
}

TSharedRef< FSlateStyleSet > FGameEditorStyle::Create()
{
	TSharedRef<FSlateStyleSet> StyleRef = MakeShareable(new FSlateStyleSet(FGameEditorStyle::GetStyleSetName()));
	StyleRef->SetContentRoot(FPaths::EngineContentDir() / TEXT("Editor/Slate"));
	StyleRef->SetCoreContentRoot(FPaths::EngineContentDir() / TEXT("Slate"));

	FSlateStyleSet& Style = StyleRef.Get();

	const FVector2D Icon16x16(16.0f, 16.0f);
	const FVector2D Icon20x20(20.0f, 20.0f);
	const FVector2D Icon40x40(40.0f, 40.0f);
	const FVector2D Icon64x64(64.0f, 64.0f);

	// Toolbar 
	{
		Style.Set("GameEditor.CheckContent", new GAME_IMAGE_BRUSH_SVG("Icons/CheckContent", Icon20x20));
	}

	return StyleRef;
}

这个模式适用于多种情况。我们只需要找到合适的地方进行Initialize初始化。对Lyra来说,这实际上就是FLyraEditorModuleStartupModule函数的第一行。

1
2
3
4
5
	virtual void StartupModule() override
	{
		FGameEditorStyle::Initialize();
		// ...
	}

自动控制台命令

FAutoConsoleCommandWithWorldArgsAndOutputDevice类让我们可以轻松创建可执行的控制台命令,典型语法如下:

1
2
3
4
5
6
7
8
9
FAutoConsoleCommandWithWorldArgsAndOutputDevice GCreateRedirectorPackage(
	TEXT("Lyra.CreateRedirectorPackage"),
	TEXT("Usage:\n")
	TEXT("  Lyra.CreateRedirectorPackage RedirectorName TargetPackage"),
	FConsoleCommandWithWorldArgsAndOutputDeviceDelegate::CreateStatic(
		[](const TArray<FString>& Params, UWorld* World, FOutputDevice& Ar)
	{
		// ... Implementation
	}));

如上所示,实际实现是通过绑定到FConsoleCommandWithWorldArgsAndOutputDeviceDelegatelambda函数完成的。WithWorldArgsAndOutputDevice部分描述了委托签名:第一个参数是TArray<FString>,第二个是UWorld*,第三个是FOutputDevice&。用户在控制台输入的所有参数都会作为TArray<FString>传递给第一个参数,我们可以像普通数组一样提取参数。

Lyra中有三个示例命令:

  • GCheckChaosMeshCollisionCmd
    • 用于检查给定资产的网格碰撞
  • GCreateRedirectorPackage
    • 为给定资产创建重定向器包
  • GDiffCollectionReferenceSupport
    • 检查新旧集合间的引用差异

MinimalAPI和LYRAGAME_API

这里没有太多复杂内容,只需注意需要使用PROJECT_API宏来导出函数,而MinimalAPI用于导出类。

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
 * Manager for experiences - primarily for arbitration between multiple PIE sessions
 */
UCLASS(MinimalAPI)
class ULyraExperienceManager : public UEngineSubsystem
{
	GENERATED_BODY()

public:
#if WITH_EDITOR
	LYRAGAME_API void OnPlayInEditorBegun();
	// ...
}

监控编辑器性能

虚幻引擎内置了编辑器性能监控器,可以在编辑器偏好设置中启用。这个工具可以监控编辑器性能并识别潜在瓶颈,特别适用于大型项目或需要优化游戏性能的情况。

在编辑器偏好设置中启用性能工具: Enable Editor Performance Monitor

然后,我们可以在底部工具栏查看性能报告: Editor Performance Monitor

每个相关类别都有提示说明潜在问题是什么。 Editor Performance Monitor

本文由作者按照 CC BY 4.0 进行授权