LiteralliJeff 发表于 2023-3-10 19:29

《调教UE5:编辑器拓展指南》常用模块

《调教UE5》系列记录的是笔者在开发UE引擎中总结的些许经验。文中所有观点和结论仅代表个人见解,作为自学笔记和日后反思之参考,亦可能存在谬误或过时之处。如有错漏和不当,恳请多加指正。<hr/><hr/>目录

经典原味基础款 !
ObjectTools
PackageTools
AssetTools
百变缤纷大拼盘 !
AssetRegistry
AssetRegistryHelpers
EditorSubsystems
(Plugin) EditorAssetLibrary 和 EditorScriptingUtilities
ContentBrowserModule
(Plugin) EditorUtilityLibrary 和 Blutility
AssetViewUtils
提前预防很重要 !
10. Transaction (Undo/Redo)<hr/>\large \textbf{用好小道具,拿捏小情绪。} \\
<hr/>来啦?快进来!今天来一起稍稍放松下吧!~
本章将对在进行编辑器拓展时经常用到的一些模块,作出概述性质的介绍说明。
本章作为本指南的工具备忘录,内容可能会随笔者的日常使用进行增补或更替。
目前,本指南基于 UE5.0.3 引擎版本进行编写,读者需前往下载对应的引擎版本,以免在尝试过程中因引擎版本差异造成不必要的麻烦。

经典原味基础款 !

ObjectTools

ObjectTools 包含一些和 UObject 操作相关的工具。在 ObjectTools.h 文件中可以查看。它包含一些奇奇怪怪的东西,但也一些我们能够一眼看懂的函数。例如下面这些。

[*]DuplicateObjects
[*]ConsolidateObjects
[*]CopyReferences
[*]SelectActorsInLevelDirectlyReferencingObject
[*]DeleteObjects
[*]DeleteAssets
[*]ForceReplaceReferences
[*]RenameSingleObject
比较有意思的是 DeleteAssets,它难道不应该在 Asset 相关的文件里吗?还真有。在 AssetViewUtils.h 里有一个同名函数,如果你去看它的定义,会发现是这样的。




回到 ObjectTools.cpp 中来,对比 DeleteObjects 与 DeleteAssets 的实现代码。
会发现后者仅比前者多了一步 CleanupAfterSuccessfulDelete 的操作来清理 UPackage。
既然它们是如此之像,放在一起那我们也没什么好抱怨的了。

{\quad今天的捉弄结束了 \quad }\\

PackageTools

既然有 ObjectTools,那怎么能没有 PackageTools 呢?要不然我 UPackage 不是很没有牌面!
PackageTools 包含一些和 UPackage 操作相关的工具,在 PackageTools.h 文件中可以查看。它们看上去都很亲切,鲜有奇奇怪怪的东西。例如下面这些。

[*]GetObjectsInPackages
[*]LoadPackage
[*]UnloadPackages
[*]ReloadPackages
[*]ExportPackages
[*]SavePackagesForObjects
[*]FindOrCreatePackageForAssetType
此外,在 PackageHelperFunctions.h 中还有一些辅助工具。

[*]SearchDirectoryRecursive
[*]NormalizePackageNames
[*]SavePackageHelper


{\quad今天的捉弄结束了 \quad }\\

AssetTools

ObjectTools 和 PackageTools 都有了,那 AssetTools 也不能落下吧?要不然我 Asset 岂不是很没有面子!
AssetTools 包含一些和 Asset 操作相关的工具,在 IAssetTools.h 中可以查看函数说明,在 UAssetTools.h 中可以查看函数实现,至于使用,则需要通过 FAssetToolsModule 来完成,在 AssetToolsModule.h 中可以查看。
AssetTools 的主要用途是注册 Asset,亦是自定义数据类型中重要的一环。其中的 RegisterAssetTypeActions 可以为特定的 Asset 分配特定的 Actions,就是出现在 Asset 右键菜单中的那些操作。我们在稍后的章节中还会详细介绍。

AssetTools 还有一些注册 Asset 以外的其他功能(Asset 操作的老祖宗都在这里)。

[*]CreateAsset
[*]DuplicateAsset
[*]RenameAssets
[*]FindSoftReferencesToObject
[*]ImportAssets
[*]ExportAssets
[*]CreateUniqueAssetName
[*]MigratePackages
[*]FixupReferencers
[*]SyncBrowserToAssets
下面以示例看一下 AssetTools 的其他功能。
// 在ContentBrowser中创建一个Asset并保存。
// 摘录自 https://forums.unrealengine.com/t/how-to-create-new-assets-in-c/264944

// 加载必要的模块
FAssetToolsModule& AssetToolsModule =
        FModuleManager::Get().LoadModuleChecked<FAssetToolsModule>("AssetTools");
FContentBrowserModule& ContentBrowserModule =
        FModuleManager::LoadModuleChecked<FContentBrowserModule>("ContentBrowser");
FAssetRegistryModule& AssetRegistryModule =
        FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
IAssetRegistry& AssetRegistry = AssetRegistryModule.Get();

// 生成唯一的Asset名称
FString Name, PackageName;
AssetToolsModule.Get().CreateUniqueAssetName(
        TEXT("/Game/AssetFolder/AssetName"), TEXT(""), PackageName, Name);
const FString PackagePath = FPackageName::GetLongPackagePath(PackageName);

// 创建 object 和 package
UPackage* package = CreatePackage(*PackageName);
UBlueprintFactory* MyFactory =
        NewObject<UBlueprintFactory>(UBlueprintFactory::StaticClass()); // 可以省略,将使用默认工厂
UObject* NewObject = AssetToolsModule.Get().CreateAsset(
        Name, PackagePath, UBlueprint::StaticClass(), MyFactory);
UPackage::Save(
        package, NewObject, RF_Public | RF_Standalone,
        *FPackageName::LongPackageNameToFilename(PackageName, FPackageName::GetAssetPackageExtension()));

// 通知 AssetRegistry
AssetRegistry.AssetCreated(NewObject);

// 告诉内容浏览器显示新创建的资产
TArray<UObject*> Objects;
Objects.Add(NewObject);
ContentBrowserModule.Get().SyncBrowserToAssets(Objects);


{\quad今天的捉弄结束了 \quad }\\

百变缤纷大拼盘 !

AssetRegistry

AssetRegistry 是资产信息的管理器。它将有关资产的 FAssetData 缓存在 UE 的后台,可用于在不加载资产的情况下查询此 FAssetData。因此,AssetRegistry 与 FAssetData 具有紧密的联系。
编辑器使用 AssetRegistry 来收集引用、数据验证、ContentBrowser 的高级搜索等。
我们可以通过 AssetRegistry 来执行类似的任务,例如根据 FARFilter 来搜索 FAssetData 以找到符合要求的 Asset。
在下面的代码中,我们通过 AssetData.GetTagValue() 来搜索名为“Triangles”的 TagValue,并找到 Triangles 值小于 300 的 StaticMesh。
TagValue 就是我们经常在 ContentBrowser 中看到的资产面板中的属性。



每一个条目都是一个 TagValue

将下面的代码复制到任意按钮的 Action 函数中。
// 搜索 /Game/ 路径下所有 Triangles 小于 300 的StaticMesh

int32 MaxTriangles = 300;

IAssetRegistry& AssetRegistry = FAssetRegistryModule::GetRegistry();

FARFilter ARFilter;
ARFilter.ClassNames.Add(UStaticMesh::StaticClass()->GetFName());
ARFilter.PackagePaths.Add(TEXT("/Game/"));
ARFilter.bRecursivePaths = true;

TArray<FSoftObjectPath> AssetList;
TArray<FAssetData> Assets;

AssetRegistry.GetAssets(ARFilter, Assets);
AssetList.Reserve(Assets.Num());

for(int32 i = 0; i < Assets.Num(); i++)
{
        const FAssetData& AssetData = Assets;
       
        int32 TriangleCount;
        if(AssetData.GetTagValue<int32>("Triangles", TriangleCount)
                && TriangleCount < MaxTriangles)
        {
                AssetList.Add(AssetData.ToSoftObjectPath());
        }
}

AssetList.Shrink();

for(const FSoftObjectPath& Asset : AssetList)
{
        FString Message = Asset.ToString();
        GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Red, Message);
}

将一些 StaticMesh 放入 /Game/ 路径下。



点击按钮,就可以看到符合要求的 StaticMesh 路径被打印了出来。



AssetRegistry 的功能远不止于此,它还可以做很多与 Asset 相关的事情,例如 GetDependencies 和 GetReferencers 诸如此类。在 AssetRegistry.h 文件中可以查看它们。

AssetRegistryHelpers

AssetRegistryHelpers 中是一些与 FAssetData 有关的操作。在 AssetRegistryHelpers.h 文件中可以查看它们。
它们大多是一些比较简单的封装,但是一个了解 FAssetData 常用操作的好地方。

{\quad今天的捉弄结束了 \quad }\\

EditorSubsystems

EditorSubsystems 位于 UnrealEd 模块下,它含有与编辑器操作相关的一系列 Subsystem。



在前面,Object,Package,Asset 都有了,那么 Actor 呢?Actor 就没有排面了吗?
于是在 EditorActorSubsystem 中,都是与 Actor 操作相关的函数。



AssetEditorSubsystem 中则是与 Asset 编辑器相关的一些函数。



{\quad今天的捉弄结束了 \quad }\\

(Plugin) EditorAssetLibrary 和 EditorScriptingUtilities

EditorAssetLibrary 同样包含一些和 Asset 操作相关的工具,但是属于更上一层的封装。同时它还包含一些别的东西。
EditorAssetLibrary 用于使用 ContentBrowser 执行大多数常见功能。在执行有关于 ContentBrowser 有关的基本操作时将会非常方便,例如如下的一些函数。在 EditorAssetLibrary.h 文件中可以查看更多函数。

[*]LoadAsset
[*]FindAssetData
[*]DoesAssetExist
[*]DoesDirectoryExist
[*]ConsolidateAssets
[*]DeleteAsset
[*]DeleteDirectory
[*]DuplicateAsset
[*]DuplicateDirectory
[*]RenameAsset
[*]RenameDirectory
[*]SaveAsset
[*]SaveDirectory
[*]DoesDirectoryHaveAssets
[*]MakeDirectory
[*]ListAssets
[*]SyncBrowserToObjects

EditorAssetLibrary 属于 EditorScriptingUtilities 模块工具其中之一,EditorScriptingUtilities 还有很多其他的工具。例如 EditorLevelLibrary,提供与 Level 相关的一些操作。

[*]GetSelectedLevelActors
[*]SetSelectedLevelActors
[*]PilotLeelActor
EditorStaticMeshLibrary,EditorDialogLibrary 等等一些,它们在拓展 Editor 时也非常有用。

{\quad今天的捉弄结束了 \quad }\\

ContentBrowserModule

对于想要进行 ContentBrowser 相关的操作而言,ContentBrowserModule 是必不可少的内容。
ContentBrowser 有一个全局单例,通过它来进行 ContentBrowser 操作。这些操作函数的实现,可以在 ContentBrowserSingleton.h 文件查看。
// 通过 ContentBrowser单例对ContentBrowser进行操作

FContentBrowserModule& ContentBrowserModule = FModuleManager::LoadModuleChecked<FContentBrowserModule>("ContentBrowser");

ContentBrowserModule.Get().something...



{\quad今天的捉弄结束了 \quad }\\

(Plugin) EditorUtilityLibrary 和 Blutility

EditorUtilityLibrary 提供在蓝图中可使用的 Editor Utility 工具。它包括 GetSelectedAssets,GetCurrentContentBrowserPath 等一些实用功能。

EditorUtilityLibrary 所属的 Blutility 模块还有一些其他的东西。EditorUtilitySubsystem 还提供了如 SpawnAndRegisterTab,DoesTabExist,CloseTabByID 等功能。

{\quad今天的捉弄结束了 \quad }\\
AssetViewUtils

AssetViewUtils 是 AssetTools 下的 AssetView 常用工具集。里面包含了一些与 Asset 相关的方便好用的工具。可以在 AssetViewUtils.h 文件查看。
下面列出部分实用工具。

[*]OpenEditorForAsset
[*]LoadAssetsIfNeeded
[*]GetUnloadedAssets
[*]CopyAssets
[*]MoveAssets
[*]DeleteAssets
[*]RenameFolder
[*]CopyFolders
[*]MoveFolders
[*]DeleteFolders
[*]SavePackages
[*]DoesFolderExist


{\quad今天的捉弄结束了 \quad }\\

提前预防很重要 !

Transaction (Undo/Redo)

在拓展编辑器时,给服务对象提供反悔的空间也是非常重要的事项。一个贴心的功能怎么能少得了 Undo 和 Redo 操作呢?
UE5 中的 Transaction 是针对于 UObject 而言的,具体来说,它通过储存 UObject 中带有 UPROPERTY 标记的成员变量,在需要 Undo 时恢复它们的数据来达到撤回的效果。
使用 Transaction

① 为 UObject 对象或子对象中需要支持 Undo/Redo 操作的成员变量添加 UPROPERTY 标记。
UCLASS()
class THEMODULE_API URevocableObject : public UObject
{
        GENERATED_BODY()

public:
        UPROPERTY()
        float RevocableFloat;
        UPROPERTY()
        int RevocableInt;

        float IrrevocableFloat;
};
那如果我们希望 IrrevocableValue 可以在蓝图中进行编辑,又不希望它支持 Undo/Redo 操作应该如何呢?可以使用“NonTransactional”关键字。
UCLASS()
class THEMODULE_API URevocableObject : public UObject
{
        GENERATED_BODY()

public:
        UPROPERTY()
        float RevocableFloat;
        UPROPERTY()
        int RevocableInt;

        UPROPERTY(EditAnywhere, NonTransactional)
        float IrrevocableFloat;
};

② 将需要支持 Undo/Redo 操作的 Uobject 对象或子对象设置 RF_Transactional 标记。
可以在生成 Uobject 对象或子对象时设置。
URevocableObject* RevocableObject = NewObject<URevocableObject>(GetTransientPackage(), NAME_None, RF_Transactional);
或者 Uobject 对象或子对象已生成了,通过 SetFlags() 设置。
RevocableObject->SetFlags(RF_Transactional);
③ 储存一次修改记录。Transaction 提供了两种方法来发起记录一次修改。
第一种方法通过 BeginTransaction 和 EndTransaction 来标记记录的范围。
// 开始记录修改范围,在这个范围内被修改的支持 Undo/Redo 操作的成员变量被修改都会被标记
GEditor->BeginTransaction(FText::FromString("Name in the Undo History"));

// 先将RevocableObject中的成员变量储存起来
RevocableObject->Modify();
// 修改支持 Undo/Redo 操作的成员变量,后续Undo时会变为 Modify() 时的值。
RevocableObject->RevocableFloat = 3.14;

// 结束记录修改范围,此后即使支持 Undo/Redo 操作的成员变量被修改也不会被追溯
GEditor->EndTransaction();

// 超出了修改记录范围,不会被追溯
RevocableObject->RevocableInt = 432;

另一种方法使用 FScopedTransaction。
{
        // FScopedTransaction 记录定义处到该作用域结束为修改范围
        const FScopedTransaction ScopedTransaction(FText::FromString("Name in the Undo History"));

        // 先将RevocableObject中的成员变量储存起来
        RevocableObject->Modify();
        // 修改支持 Undo/Redo 操作的成员变量,后续Undo时会变为 Modify() 时的值。
        RevocableObject->RevocableFloat = 3.14;
}

// 超出了修改记录范围,不会被追溯
RevocableObject->RevocableInt = 432;

额外的操作点

我们可以在 Uobject 对象或子对象中设置额外的操作点,以在进行 Undo 之前或之后执行额外的操作。
UCLASS()
class THEMODULE_API URevocableObject : public UObject
{
        GENERATED_BODY()

public:
        virtual void PreEditUndo() override
        {
                Super::PreEditUndo();
                // 在撤回之前执行某些操作
        }

        virtual void PostEditUndo() override
        {
                Super::PostEditUndo();
                // 再撤回之后执行某些操作
        }

        UPROPERTY()
        float RevocableFloat;
        UPROPERTY()
        int RevocableInt;

        UPROPERTY(EditAnywhere, NonTransactional)
        float IrrevocableFloat;
};


页: [1]
查看完整版本: 《调教UE5:编辑器拓展指南》常用模块