找回密码
 立即注册
查看: 230|回复: 0

《调教UE5:编纂器拓展指南》自定义数据类型

[复制链接]
发表于 2024-7-15 18:09 | 显示全部楼层 |阅读模式
《调教UE5》系列记录的是笔者在开发UE引擎中总结的些许经验。文中所有不雅概念和结论仅代表个人见解,作为自学笔记和日后反思之参考,亦可能存在谬误或过时之处。如有讹夺和不妥,恳请多加斧正。
<hr/><hr/>目录
不成忽略的事前筹备 ~
ㅤ创建 CustomDataType 模块
ㅤ创建 CustomDataTypeEditor 模块
自定义数据类型 ~♡
ㅤ新建数据类型
ㅤFactory
ㅤAssetTypeActions
为自定义数据类型构建编纂器 ~♡
导入和导出 ~♡
ㅤ导入为自定义资产类型
ㅤ重导入自定义资产类型
ㅤ导出自定义资产类型
<hr/>\large\textbf{珍贵的不是倡议誓言的决心,} \\ \large\textbf{而是恪守誓言的行为。} \\
<hr/>本章将探索自定义数据类型的方式。
本章中使用的案例原型来自 Creating a Custom Asset Type with its own Editor in C++ ,笔者仅在此基础长进行了些许学习探讨,有兴趣的读者也可前往原文阅读。
不成忽略的事前筹备 ~

在正式开始之前,我们需要筹备两个新模块:一个“CustomDataType”运行时模块(Runtime)和一个“CustomDataTypeEditor”编纂器模块(Editor)。运行时模块存放自定义数据类型,编纂器模块则负责存放所有与操作自定义数据类型相关的部门,诸如创建、改削和导入导出等行为。



在名为“ExtendEditor”的项目中组织如下的文件布局。



创建 CustomDataType 模块

新建“CustomDataType”模块中的文件必要文件。
  1. // Source/CustomDataType/CustomDataType.Build.cs
  2. using UnrealBuildTool;
  3. public class CustomDataType : ModuleRules
  4. {
  5.         public CustomDataType(ReadOnlyTargetRules Target) : base(Target)
  6.         {
  7.                 PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
  8.        
  9.                 PublicDependencyModuleNames.AddRange(new string[]
  10.                 {
  11.                         ”Core”,
  12.                         ”CoreUObject”,
  13.                         ”Engine”,
  14.                         ”InputCore”,
  15.                 });
  16.                 PrivateDependencyModuleNames.AddRange(new string[] { });
  17.         }
  18. }
复制代码
  1. // Source/CustomDataType/Public/CustomDataType.h
  2. #pragma once
  3. #include ”CoreMinimal.h”
  4. #include ”Modules/ModuleManager.h”
  5. class FCustomDataTypeModule : public IModuleInterface
  6. {
  7. public:
  8.         virtual void StartupModule() override;
  9.         virtual void ShutdownModule() override;
  10. };
复制代码
  1. // Source/CustomDataType/Private/CustomDataType.cpp
  2. #pragma once
  3. #include ”CustomDataType.h”
  4. IMPLEMENT_MODULE(FCustomDataTypeModule, CustomDataType)
  5. void FCustomDataTypeModule::StartupModule()
  6. {
  7.         IModuleInterface::StartupModule();
  8. }
  9. void FCustomDataTypeModule::ShutdownModule()
  10. {
  11.         IModuleInterface::ShutdownModule();
  12. }
复制代码
将“CustomDataType”模块添加到项目依赖中。
  1. // Source/ExtendEditor.Target.cs
  2. using UnrealBuildTool;
  3. using System.Collections.Generic;
  4. public class ExtendEditorTarget : TargetRules
  5. {
  6.         public ExtendEditorTarget( TargetInfo Target) : base(Target)
  7.         {
  8.                 Type = TargetType.Game;
  9.                 DefaultBuildSettings = BuildSettingsVersion.V2;
  10.                 ExtraModuleNames.AddRange( new string[] { ”ExtendEditor”, ”CustomDataType” } );
  11.         }
  12. }
复制代码
  1. // Source/ExtendEditor.uproject
  2. {
  3.         ”FileVersion”: 3,
  4.         ”EngineAssociation”: ”5.0”,
  5.         ”Category”: ””,
  6.         ”Description”: ””,
  7.         ”Modules”: [
  8.                 {
  9.                         ”Name”: ”ExtendEditor”,
  10.                         ”Type”: ”Runtime”,
  11.                         ”LoadingPhase”: ”Default”
  12.                         ]
  13.                 },
  14.                 {
  15.                         ”Name”: ”CustomDataType”,
  16.                         ”Type”: ”Runtime”,
  17.                         ”LoadingPhase”: ”Default”
  18.                 }
  19.         ]
  20. }
复制代码

创建 CustomDataTypeEditor 模块

新建“CustomDataTypeEditor”模块中的文件必要文件。
  1. // Source/CustomDataTypeEditor/CustomDataTypeEditor.Build.cs
  2. using UnrealBuildTool;
  3. public class CustomDataTypeEditor : ModuleRules
  4. {
  5.         public CustomDataTypeEditor(ReadOnlyTargetRules Target) : base(Target)
  6.         {
  7.                 PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
  8.        
  9.                 PublicDependencyModuleNames.AddRange(new string[]
  10.                 {
  11.                         ”Core”,
  12.                         ”CoreUObject”,
  13.                         ”Engine”,
  14.                         ”InputCore”,
  15.                         ”CustomDataType”,
  16.                 });
  17.                 PrivateDependencyModuleNames.AddRange(new string[] { });
  18.         }
  19. }
复制代码
  1. // Source/CustomDataTypeEditor/Public/CustomDataTypeEditor.h
  2. #pragma once
  3. #include ”CoreMinimal.h”
  4. #include ”Modules/ModuleManager.h”
  5. class FCustomDataTypeEditorModule : public IModuleInterface
  6. {
  7. public:
  8.         virtual void StartupModule() override;
  9.         virtual void ShutdownModule() override;
  10. };
复制代码
  1. // Source/CustomDataTypeEditor/Private/CustomDataTypeEditor.cpp
  2. #pragma once
  3. #include ”CustomDataTypeEditor.h”
  4. IMPLEMENT_MODULE(FCustomDataTypeEditorModule, CustomDataTypeEditor)
  5. void FCustomDataTypeEditorModule::StartupModule()
  6. {
  7.         IModuleInterface::StartupModule();
  8. }
  9. void FCustomDataTypeEditorModule::ShutdownModule()
  10. {
  11.         IModuleInterface::ShutdownModule();
  12. }
复制代码
将“CustomDataTypeEditor”模块添加到项目依赖中。
  1. // Source/ExtendEditorEditor.Target.cs
  2. using UnrealBuildTool;
  3. using System.Collections.Generic;
  4. public class ExtendEditorEditorTarget : TargetRules
  5. {
  6.         public ExtendEditorEditorTarget( TargetInfo Target) : base(Target)
  7.         {
  8.                 Type = TargetType.Editor;
  9.                 DefaultBuildSettings = BuildSettingsVersion.V2;
  10.                 ExtraModuleNames.AddRange( new string[]
  11.                 {
  12.                         ”ExtendEditor”,
  13.                         ”CustomDataType”,
  14.                         ”CustomDataTypeEditor”
  15.                 } );
  16.         }
  17. }
复制代码
  1. // Source/ExtendEditor.uproject
  2. {
  3.         ”FileVersion”: 3,
  4.         ”EngineAssociation”: ”5.0”,
  5.         ”Category”: ””,
  6.         ”Description”: ””,
  7.         ”Modules”: [
  8.                 {
  9.                         ”Name”: ”ExtendEditor”,
  10.                         ”Type”: ”Runtime”,
  11.                         ”LoadingPhase”: ”Default”
  12.                 },
  13.                 {
  14.                         ”Name”: ”CustomDataType”,
  15.                         ”Type”: ”Runtime”,
  16.                         ”LoadingPhase”: ”Default”
  17.                 },
  18.                 {
  19.                         ”Name”: ”CustomDataTypeEditor”,
  20.                         ”Type”: ”Editor”,
  21.                         ”LoadingPhase”: ”Default”
  22.                 }
  23.         ]
  24. }
复制代码

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

自定义数据类型 ~♡

要在 UE5 中添加一个可在 ContentBrowser 中进行基本操作的自定义数据类型,一共需要三样内容:

  • 自定义的数据类型
  • 与该数据类型关联的 Factory
  • 与该数据类型关联的 AssetTypeActions



新建数据类型

首先来新建自定义数据类型,我们将新的数据类型定名为“UCustomNormalDistribution”,担任自 UObject。这个数据类型用于描述一个正态分布,并有一个函数用于给出符合该正态分布的随机值。



在 CustomDataType 文件夹下新建文件“CustomNormalDistribution.h”和“CustomNormalDistribution.cpp”。

  1. // CustomNormalDistribution.h
  2. #pragma once
  3. #include ”CoreMinimal.h”
  4. #include <random>
  5. #include ”CustomNormalDistribution.generated.h”
  6. UCLASS(BlueprintType)
  7. class CUSTOMDATATYPE_API UCustomNormalDistribution : public UObject
  8. {
  9.         GENERATED_BODY()
  10.        
  11. public:
  12.         UCustomNormalDistribution();
  13.         UFUNCTION(BlueprintCallable)
  14.         float DrawSample();
  15.         UFUNCTION(CallInEditor)
  16.         void LogSample();
  17. public:
  18.         UPROPERTY(EditAnywhere)
  19.         float Mean;
  20.         UPROPERTY(EditAnywhere)
  21.         float StandardDeviation;
  22. private:
  23.         std::mt19937 RandomNumberGenerator;
  24. };
复制代码
  1. // CustomNormalDistribution.cpp
  2. #pragma once
  3. #include ”CustomNormalDistribution.h”
  4. UCustomNormalDistribution::UCustomNormalDistribution()
  5.         : Mean(0.5f)
  6.         , StandardDeviation(0.2f)
  7. {}
  8. float UCustomNormalDistribution::DrawSample()
  9. {
  10.         return std::normal_distribution<>(Mean, StandardDeviation)(RandomNumberGenerator);
  11. }
  12. void UCustomNormalDistribution::LogSample()
  13. {
  14.         UE_LOG(LogTemp, Log, TEXT(”%f”), DrawSample())
  15. }
复制代码

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

Factory

UFactory 是一个工厂类,用于创建和导入新对象。它是一个抽象类,不能直接实例化,它的子类必需实现 FactoryCreateNew 函数,以便在 ContentBrowser 中创建资产。
可以理解为 UFactory 定义了资源创建的方式,除了必需实现的 FactoryCreateNew 函数,还可以通过定义内部变量或覆写虚函数的方式来自定义相关的法则。



UFactory 内部变量



UFactory 部门函数

为 UCustomNormalDistribution 创建相关的 Factory,我们将它定名为“UCustomNormalDistributionFactory”,担任自 UFactory。



在 CustomDataTypeEditor 文件夹下新建文件“CustomNormalDistributionFactory.h”和“CustomNormalDistributionFactory.cpp”。



在 UCustomNormalDistributionFactory 的构造函数中将工厂与 UCustomNormalDistribution 绑定,并允许其创建新资产的行为。
  1. // CustomNormalDistributionFactory.h
  2. #pragma once
  3. #include ”CoreMinimal.h”
  4. #include ”Factories/Factory.h”
  5. #include ”CustomNormalDistributionFactory.generated.h”
  6. UCLASS()
  7. class UCustomNormalDistributionFactory : public UFactory
  8. {
  9.         GENERATED_BODY()
  10. public:
  11.         UCustomNormalDistributionFactory();
  12.         virtual UObject* FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override;
  13. };
复制代码
  1. // CustomNormalDistributionFactory.cpp
  2. #include ”CustomNormalDistributionFactory.h”
  3. #include ”CustomNormalDistribution.h”
  4. UCustomNormalDistributionFactory::UCustomNormalDistributionFactory()
  5. {
  6.         SupportedClass = UCustomNormalDistribution::StaticClass();
  7.         bCreateNew = true;
  8. }
  9. UObject* UCustomNormalDistributionFactory::FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn)
  10. {
  11.         return NewObject<UCustomNormalDistribution>(InParent, Class, Name, Flags, Context);
  12. }
复制代码
  1. // CustomDataTypeEditor.Build.cs
  2. using UnrealBuildTool;
  3. public class CustomDataTypeEditor : ModuleRules
  4. {
  5.         public CustomDataTypeEditor(ReadOnlyTargetRules Target) : base(Target)
  6.         {
  7.                 PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
  8.        
  9.                 PublicDependencyModuleNames.AddRange(new string[]
  10.                 {
  11.                         ”Core”,
  12.                         ”CoreUObject”,
  13.                         ”Engine”,
  14.                         ”InputCore”,
  15.                         ”CustomDataType”,
  16.                         ”UnrealEd”,
  17.                 });
  18.                 PrivateDependencyModuleNames.AddRange(new string[] { });
  19.         }
  20. }
复制代码
{♡☘♡☘♡  \quad今天的捉弄结束了 \quad ♡☘♡☘♡}\\

AssetTypeActions

FAssetTypeActions_Base 是所有 AssetTypeActions 的基类,提供有关特定资产类型的操作和其他信息。它是可选的,但一般默认需要提供,否则无法完成诸如双击打开资产编纂器等操作。
当编纂器在 ContentBrowser 中措置资产操作(创建资产、双击打开编纂资源等)时,会搜寻是否有对应的 AssetTypeActionsClass 被注册。
AssetTypeActions 中有各种与资产操作以及资产信息显示有关的虚函数,我们可以通过覆写它们来自定义资产操作的行为和资产显示方式。



FAssetTypeActions_Base 部门函数

为 UCustomNormalDistribution 创建相关的 AssetTypeActions,我们将它定名为“FCustomNormalDistributionActions”,担任自 FAssetTypeActions_Base。



在 CustomDataTypeEditor 文件夹下新建文件“CustomNormalDistributionActions.h”和“CustomNormalDistributionActions.cpp”。



我们通过 FCustomNormalDistributionActions 自定义在 ContentBrowser 中使用右键菜单创建 UCustomNormalDistribution 时的颜色和友好显示名称。而且我们为 UCustomNormalDistribution  这个特定资产类型的右键菜单里添加一个“Custom Action”按钮。稍后,我们将注册一个单独的 Category 供从右键菜单创建 UCustomNormalDistribution 时使用。
  1. // CustomNormalDistributionActions.h
  2. #pragma once
  3. #include ”CoreMinimal.h”
  4. #include ”AssetTypeActions_Base.h”
  5. class FCustomNormalDistributionActions : public FAssetTypeActions_Base
  6. {
  7. public:
  8.         FCustomNormalDistributionActions(EAssetTypeCategories::Type InAssetCategory);
  9.        
  10.         virtual UClass* GetSupportedClass() const override;
  11.         virtual FText GetName() const override;
  12.         virtual FColor GetTypeColor() const override;
  13.         virtual uint32 GetCategories() override;
  14.        
  15.         virtual void GetActions(const TArray<UObject*>& InObjects, FMenuBuilder& MenuBuilder) override;
  16.         virtual bool HasActions(const TArray<UObject*>& InObjects) const override;
  17. private:
  18.         EAssetTypeCategories::Type AssetCategory;
  19. };
复制代码
  1. // CustomNormalDistributionActions.cpp
  2. #include ”CustomNormalDistributionActions.h”
  3. #include ”CustomNormalDistribution.h”
  4. FCustomNormalDistributionActions::FCustomNormalDistributionActions(EAssetTypeCategories::Type InAssetCategory)
  5.         : AssetCategory(InAssetCategory)
  6. {
  7. }
  8. UClass* FCustomNormalDistributionActions::GetSupportedClass() const
  9. {
  10.         return UCustomNormalDistribution::StaticClass();
  11. }
  12. FText FCustomNormalDistributionActions::GetName() const
  13. {
  14.         return INVTEXT(”Custom Normal Distribution”);
  15. }
  16. FColor FCustomNormalDistributionActions::GetTypeColor() const
  17. {
  18.         return FColor::Orange;
  19. }
  20. uint32 FCustomNormalDistributionActions::GetCategories()
  21. {
  22.         return AssetCategory;
  23. }
  24. void FCustomNormalDistributionActions::GetActions(const TArray<UObject*>& InObjects, FMenuBuilder& MenuBuilder)
  25. {
  26.         FAssetTypeActions_Base::GetActions(InObjects, MenuBuilder);
  27.        
  28.         MenuBuilder.AddMenuEntry(
  29.                 FText::FromString(”Custom Action”),
  30.                 FText::FromString(”This is a custom action”),
  31.                 FSlateIcon(),
  32.                 FUIAction()
  33.                 );
  34. }
  35. // HasActions() 必需返回 true 以使 GetActions() 有效
  36. bool FCustomNormalDistributionActions::HasActions(const TArray<UObject*>& InObjects) const
  37. {
  38.         return true;
  39. }
复制代码
在 CustomDataTypeEditor 模块启动时注册 FCustomNormalDistributionActions。
  1. // CustomDataTypeEditor.h
  2. #pragma once
  3. #include ”CoreMinimal.h”
  4. #include ”Modules/ModuleManager.h”
  5. #include ”CustomNormalDistributionActions.h”
  6. class FCustomDataTypeEditorModule : public IModuleInterface
  7. {
  8. public:
  9.         /** IModuleInterface implementation */
  10.         virtual void StartupModule() override;
  11.         virtual void ShutdownModule() override;
  12. private:
  13.         // 记录注册的 AssetTypeActions 以供模块停用时卸载
  14.         TSharedPtr<FCustomNormalDistributionActions> CustomNormalDistributionActions;
  15. };
复制代码
  1. // CustomDataTypeEditor.cpp
  2. #pragma once
  3. #include ”CustomDataTypeEditor.h”
  4. #include ”AssetToolsModule.h”
  5. #include ”AssetTypeCategories.h”
  6. IMPLEMENT_MODULE(FCustomDataTypeEditorModule, CustomDataTypeEditor)
  7. void FCustomDataTypeEditorModule::StartupModule()
  8. {
  9.         // 注册新的 Category
  10.         EAssetTypeCategories::Type Category =
  11.                 FAssetToolsModule::GetModule().Get().RegisterAdvancedAssetCategory(
  12.                         FName(TEXT(”Example”)), FText::FromString(”Example”));
  13.         // 注册 AssetTypeActions
  14.         CustomNormalDistributionActions = MakeShared<FCustomNormalDistributionActions>(Category);
  15.         FAssetToolsModule::GetModule().Get().RegisterAssetTypeActions(
  16.                 CustomNormalDistributionActions.ToSharedRef());
  17. }
  18. void FCustomDataTypeEditorModule::ShutdownModule()
  19. {
  20.         if (FModuleManager::Get().IsModuleLoaded(”AssetTools”))
  21.         {
  22.                 FAssetToolsModule::GetModule().Get().UnregisterAssetTypeActions(
  23.                         CustomNormalDistributionActions.ToSharedRef());
  24.         }
  25. }
复制代码
  1. // CustomDataTypeEditor.Build.cs
  2. using UnrealBuildTool;
  3. public class CustomDataTypeEditor : ModuleRules
  4. {
  5.         public CustomDataTypeEditor(ReadOnlyTargetRules Target) : base(Target)
  6.         {
  7.                 PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
  8.        
  9.                 PublicDependencyModuleNames.AddRange(new string[]
  10.                 {
  11.                         ”Core”,
  12.                         ”CoreUObject”,
  13.                         ”Engine”,
  14.                         ”InputCore”,
  15.                         ”CustomDataType”,
  16.                         ”UnrealEd”,
  17.                         ”AssetTools”,
  18.                 });
  19.                 PrivateDependencyModuleNames.AddRange(new string[] { });
  20.         }
  21. }
复制代码
编译并重启编纂器,我们可以通过右键菜单创建新的 UCustomNormalDistribution 资产,在该资产上单击右键,会看到右键菜单里添加了一个 Custom Action 按钮。





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

为自定义数据类型构建编纂器 ~♡



双击资产打开编纂器这个操作的入口在 UAssetEditorSubsystem::OpenEditorForAsset(),我们可以顺藤摸瓜来寻找合适的介入时机。



当 UAssetEditorSubsystem::OpenEditorForAsset() 开始执行时,会调用 IAssetTypeActions 中的 OpenAssetEditor() 虚函数,这时如果没有提供任何子类的重写,则会默认打开一个 SimpleAssetEditor,查看得知,FSimpleAssetEditor 是一个担任自 FAssetEditorToolkit 的类。
因此我们要做的就是在本身的 AssetTypeActions 中重写 OpenAssetEditor(),然后用它调用自定义的 FAssetEditorToolkit 类,来打开本身的编纂器。我们假设将这个 FAssetEditorToolkit 类定名为“FCustomNormalDistributionEditorToolkit”。于是这个流程变成了如下这样。



当我们双击打开一个 UObject 时,这个 UObject 将以输入参数的形式逐级传递,并最终抵达 InitEditor()。
InitEditor() 负责规划窗口布局并调用 FAssetEditorToolkit::InitAssetEditor()。RegisterTabSpawners() 向规划好的布局中填充内容。这是我们需要存眷的两个重点函数。
每个 AssetEditor 都有本身的 TabSpawners,而每个 AssetEditor 都是在运行时动态创建的。因此每次打开一个新的 AssetEditor 时,都需要从头注册 TabSpawners。

弄清楚道理后就好操作了。首先在 CustomNormalDistributionActions 中重写 OpenAssetEditor()。
这里暂时忽略 OpenAssetEditor() 的 EditWithinLevelEditor 输入,只传递 InObjects 输入。而且假设接下来的 FCustomNormalDistributionEditorToolkit 里的入口函数为 InitEditor()。
  1. // CustomNormalDistributionActions.h
  2. #pragma once
  3. #include ”CoreMinimal.h”
  4. #include ”AssetTypeActions_Base.h”
  5. class FCustomNormalDistributionActions : public FAssetTypeActions_Base
  6. {
  7. public:
  8.         ...
  9.         virtual void OpenAssetEditor(const TArray<UObject*>& InObjects, TSharedPtr<class IToolkitHost> EditWithinLevelEditor) override;
  10. };
复制代码
  1. // CustomNormalDistributionActions.cpp
  2. #include ”CustomNormalDistributionEditorToolkit.h”
  3. void FCustomNormalDistributionActions::OpenAssetEditor(const TArray<UObject*>& InObjects,
  4.         TSharedPtr<IToolkitHost> EditWithinLevelEditor)
  5. {
  6.         MakeShared<FCustomNormalDistributionEditorToolkit>()->InitEditor(InObjects);
  7. }
复制代码
然后在 CustomDayaTypeEditor 模块下新建 FCustomNormalDistributionEditorToolkit 类。
RegisterTabSpawners() 和 UnregisterTabSpawners() 是关键函数。如前所述,我们在 InitEditor() 创建布局,并在 RegisterTabSpawners() 中注册 TabSpawners。
我们提前假设了已经存在一个 SCustomNormalDistributionWidget 小部件类,我们在稍后很快会来创建它。


  1. // CustomNormalDistributionEditorToolkit.h
  2. #pragma once
  3. #include ”CoreMinimal.h”
  4. #include ”CustomNormalDistribution.h”
  5. #include ”Toolkits/AssetEditorToolkit.h”
  6. class FCustomNormalDistributionEditorToolkit : public FAssetEditorToolkit
  7. {
  8. public:
  9.         // 外部调用的入口,它可以是任意名字,可以具有任意参数。
  10.         void InitEditor(const TArray<UObject*>& InObjects);
  11.         // 必需实现的虚函数
  12.         virtual void RegisterTabSpawners(const TSharedRef<class FTabManager>& TabManager) override;
  13.         virtual void UnregisterTabSpawners(const TSharedRef<class FTabManager>& TabManager) override;
  14.         virtual FName GetToolkitFName() const override { return ”CustomNormalDistributionEditor”; }
  15.         virtual FText GetBaseToolkitName() const override { return INVTEXT(”Custom Normal Distribution Editor”); }
  16.         virtual FString GetWorldCentricTabPrefix() const override { return ”Custom Normal Distribution”; }
  17.         virtual FLinearColor GetWorldCentricTabColorScale() const override { return {}; }
  18.         float GetMean() const;
  19.         float GetStandardDeviation() const;
  20.         void SetMean(float Mean);
  21.         void SetStandardDeviation(float StandardDeviation);
  22.        
  23. private:
  24.        
  25.         UCustomNormalDistribution* NormalDistribution = nullptr;
  26. };
复制代码
  1. // CustomNormalDistributionEditorToolkit.cpp
  2. #pragma once
  3. #include ”CustomNormalDistributionEditorToolkit.h”
  4. #include ”Widgets/Docking/SDockTab.h”
  5. #include ”SCustomNormalDistributionWidget.h”
  6. #include ”Modules/ModuleManager.h”
  7. void FCustomNormalDistributionEditorToolkit::InitEditor(const TArray<UObject*>& InObjects)
  8. {
  9.         NormalDistribution = Cast<UCustomNormalDistribution>(InObjects[0]);
  10.         const TSharedRef<FTabManager::FLayout> Layout =
  11.                 FTabManager::NewLayout(”CustomNormalDistributionEditorLayout”)
  12.                 ->AddArea
  13.                 (
  14.                         FTabManager::NewPrimaryArea()->SetOrientation(Orient_Vertical)
  15.                         ->Split
  16.                         (
  17.                                 FTabManager::NewSplitter()
  18.                                 ->SetSizeCoefficient(0.6f)
  19.                                 ->SetOrientation(Orient_Horizontal)
  20.                                 ->Split
  21.                                 (
  22.                                         FTabManager::NewStack()
  23.                                         ->SetSizeCoefficient(0.8f)
  24.                                         ->AddTab(”CustomNormalDistributionPDFTab”, ETabState::OpenedTab)
  25.                                 )
  26.                                 ->Split
  27.                                 (
  28.                                         FTabManager::NewStack()
  29.                                         ->SetSizeCoefficient(0.2f)
  30.                                         ->AddTab(”CustomNormalDistributionDetailsTab”, ETabState::OpenedTab)
  31.                                 )
  32.                         )
  33.                         ->Split
  34.                         (
  35.                                 FTabManager::NewStack()
  36.                                 ->SetSizeCoefficient(0.4f)
  37.                                 ->AddTab(”OutputLog”, ETabState::OpenedTab)
  38.                         )
  39.                 );
  40.         FAssetEditorToolkit::InitAssetEditor(EToolkitMode::Standalone, {}, ”CustomNormalDistributionEditor”, Layout, true, true, InObjects);
  41. }
  42. void FCustomNormalDistributionEditorToolkit::RegisterTabSpawners(const TSharedRef<class FTabManager>& InTabManager)
  43. {
  44.         FAssetEditorToolkit::RegisterTabSpawners(InTabManager);
  45.         WorkspaceMenuCategory =
  46.                 InTabManager->AddLocalWorkspaceMenuCategory(INVTEXT(”CustomNormalDistributionTabs”));
  47.         // 注册 SCustomNormalDistributionWidget TabSpawner
  48.         InTabManager->RegisterTabSpawner(”CustomNormalDistributionPDFTab”,
  49.                 FOnSpawnTab::CreateLambda([=](const FSpawnTabArgs&)
  50.                 {
  51.                         return SNew(SDockTab)
  52.                         [
  53.                                 SNew(SCustomNormalDistributionWidget)
  54.                                 .Mean(this, &FCustomNormalDistributionEditorToolkit::GetMean)
  55.                                 .StandardDeviation(this, &FCustomNormalDistributionEditorToolkit::GetStandardDeviation)
  56.                                 .OnMeanChanged(this, &FCustomNormalDistributionEditorToolkit::SetMean)
  57.                                 .OnStandardDeviationChanged(this, &FCustomNormalDistributionEditorToolkit::SetStandardDeviation)
  58.                         ];
  59.                 }))
  60.         .SetDisplayName(INVTEXT(”PDF”))
  61.         .SetGroup(WorkspaceMenuCategory.ToSharedRef());
  62.         // 创建 CustomNormalDistribution DetailsView
  63.         FPropertyEditorModule& PropertyEditorModule =
  64.                 FModuleManager::GetModuleChecked<FPropertyEditorModule>(”PropertyEditor”);
  65.         FDetailsViewArgs DetailsViewArgs;
  66.         DetailsViewArgs.NameAreaSettings = FDetailsViewArgs::HideNameArea;
  67.         TSharedRef<IDetailsView> DetailsView = PropertyEditorModule.CreateDetailView(DetailsViewArgs);
  68.         DetailsView->SetObjects(TArray<UObject*>{ NormalDistribution });
  69.         // 注册 CustomNormalDistribution DetailsView TabSpawner
  70.         InTabManager->RegisterTabSpawner(”CustomNormalDistributionDetailsTab”,
  71.                 FOnSpawnTab::CreateLambda([=](const FSpawnTabArgs&)
  72.                 {
  73.                         return SNew(SDockTab)
  74.                         [
  75.                                 DetailsView
  76.                         ];
  77.                 }))
  78.         .SetDisplayName(INVTEXT(”Details”))
  79.         .SetGroup(WorkspaceMenuCategory.ToSharedRef());
  80. }
  81. void FCustomNormalDistributionEditorToolkit::UnregisterTabSpawners(const TSharedRef<class FTabManager>& InTabManager)
  82. {
  83.         FAssetEditorToolkit::UnregisterTabSpawners(InTabManager);
  84.         InTabManager->UnregisterTabSpawner(”CustomNormalDistributionPDFTab”);
  85.         InTabManager->UnregisterTabSpawner(”CustomNormalDistributionDetailsTab”);
  86. }
  87. float FCustomNormalDistributionEditorToolkit::GetMean() const
  88. {
  89.         return NormalDistribution->Mean;
  90. }
  91. float FCustomNormalDistributionEditorToolkit::GetStandardDeviation() const
  92. {
  93.         return NormalDistribution->StandardDeviation;
  94. }
  95. void FCustomNormalDistributionEditorToolkit::SetMean(float Mean)
  96. {
  97.         NormalDistribution->Modify();
  98.         NormalDistribution->Mean = Mean;
  99. }
  100. void FCustomNormalDistributionEditorToolkit::SetStandardDeviation(float StandardDeviation)
  101. {
  102.         NormalDistribution->Modify();
  103.         NormalDistribution->StandardDeviation = StandardDeviation;
  104. }
复制代码
继续在 CustomDayaTypeEditor 模块下新建 SCustomNormalDistributionWidget 小部件类。
向 UCustomNormalDistribution 传递数据的逻辑位于 OnMouseMove() 中。OnPaint() 读取 UCustomNormalDistribution 中的数据并按照读取到的数据绘制由512个点组成的线段图形。


  1. // SCustomNormalDistributionWidget.h
  2. #pragma once
  3. #include ”CoreMinimal.h”
  4. #include ”Widgets/SLeafWidget.h”
  5. DECLARE_DELEGATE_OneParam(FOnMeanChanged, float /*NewMean*/)
  6. DECLARE_DELEGATE_OneParam(FOnStandardDeviationChanged, float /*NewStandardDeviation*/)
  7. class SCustomNormalDistributionWidget : public SLeafWidget
  8. {
  9. public:
  10.        
  11.         SLATE_BEGIN_ARGS(SCustomNormalDistributionWidget)
  12.                 : _Mean(0.5f)
  13.                 , _StandardDeviation(0.2f)
  14.         {}
  15.        
  16.         SLATE_ATTRIBUTE(float, Mean)
  17.         SLATE_ATTRIBUTE(float, StandardDeviation)
  18.         SLATE_EVENT(FOnMeanChanged, OnMeanChanged)
  19.         SLATE_EVENT(FOnStandardDeviationChanged, OnStandardDeviationChanged)
  20.        
  21.         SLATE_END_ARGS()
  22. public:
  23.        
  24.         void Construct(const FArguments& InArgs);
  25.         virtual int32 OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const override;
  26.         virtual FVector2D ComputeDesiredSize(float) const override;
  27.         virtual FReply OnMouseButtonDown(const FGeometry& AllottedGeometry, const FPointerEvent& MouseEvent) override;
  28.         virtual FReply OnMouseButtonUp(const FGeometry& AllottedGeometry, const FPointerEvent& MouseEvent) override;
  29.         virtual FReply OnMouseMove(const FGeometry& AllottedGeometry, const FPointerEvent& MouseEvent) override;
  30. private:
  31.        
  32.         TAttribute<float> Mean;
  33.         TAttribute<float> StandardDeviation;
  34.         FOnMeanChanged OnMeanChanged;
  35.         FOnStandardDeviationChanged OnStandardDeviationChanged;
  36.         FTransform2D GetPointsTransform(const FGeometry& AllottedGeometry) const;
  37. };
复制代码
  1. // SCustomNormalDistributionWidget.cpp
  2. #pragma once
  3. #include ”SCustomNormalDistributionWidget.h”
  4. void SCustomNormalDistributionWidget::Construct(const FArguments& InArgs)
  5. {
  6.     Mean = InArgs._Mean;
  7.     StandardDeviation = InArgs._StandardDeviation;
  8.     OnMeanChanged = InArgs._OnMeanChanged;
  9.     OnStandardDeviationChanged = InArgs._OnStandardDeviationChanged;
  10. }
  11. int32 SCustomNormalDistributionWidget::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const
  12. {
  13.     const int32 NumPoints = 512;
  14.     TArray<FVector2D> Points;
  15.     Points.Reserve(NumPoints);
  16.     const FTransform2D PointsTransform = GetPointsTransform(AllottedGeometry);
  17.     for (int32 PointIndex = 0; PointIndex < NumPoints; ++PointIndex)
  18.     {
  19.         const float X = PointIndex / (NumPoints - 1.0);
  20.         const float D = (X - Mean.Get()) / StandardDeviation.Get();
  21.         const float Y = FMath::Exp(-0.5f * D * D);
  22.         Points.Add(PointsTransform.TransformPoint(FVector2D(X, Y)));
  23.     }
  24.     FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Points);
  25.     return LayerId;
  26. }
  27. FVector2D SCustomNormalDistributionWidget::ComputeDesiredSize(float) const
  28. {
  29.     return FVector2D(200.0, 200.0);
  30. }
  31. FReply SCustomNormalDistributionWidget::OnMouseButtonDown(const FGeometry& AllottedGeometry, const FPointerEvent& MouseEvent)
  32. {
  33.     if (GEditor && GEditor->CanTransact() && ensure(!GIsTransacting))
  34.         GEditor->BeginTransaction(TEXT(””), INVTEXT(”Edit Normal Distribution”), nullptr);
  35.     return FReply::Handled().CaptureMouse(SharedThis(this));
  36. }
  37. FReply SCustomNormalDistributionWidget::OnMouseButtonUp(const FGeometry& AllottedGeometry, const FPointerEvent& MouseEvent)
  38. {
  39.     if (GEditor) GEditor->EndTransaction();
  40.     return FReply::Handled().ReleaseMouseCapture();
  41. }
  42. FReply SCustomNormalDistributionWidget::OnMouseMove(const FGeometry& AllottedGeometry, const FPointerEvent& MouseEvent)
  43. {
  44.     if (!HasMouseCapture()) return FReply::Unhandled();
  45.     const FTransform2D PointsTransform = GetPointsTransform(AllottedGeometry);
  46.     const FVector2D LocalPosition = AllottedGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition());
  47.     const FVector2D NormalizedPosition = PointsTransform.Inverse().TransformPoint(LocalPosition);
  48.     if (OnMeanChanged.IsBound())
  49.         OnMeanChanged.Execute(NormalizedPosition.X);
  50.     if (OnStandardDeviationChanged.IsBound())
  51.         OnStandardDeviationChanged.Execute(FMath::Max(0.025f, FMath::Lerp(0.025f, 0.25f, NormalizedPosition.Y)));
  52.     return FReply::Handled();
  53. }
  54. FTransform2D SCustomNormalDistributionWidget::GetPointsTransform(const FGeometry& AllottedGeometry) const
  55. {
  56.     const double Margin = 0.05 * AllottedGeometry.GetLocalSize().GetMin();
  57.     const FScale2D Scale((AllottedGeometry.GetLocalSize() - 2.0 * Margin) * FVector2D(1.0, -1.0));
  58.     const FVector2D Translation(Margin, AllottedGeometry.GetLocalSize().Y - Margin);
  59.     return FTransform2D(Scale, Translation);
  60. }
复制代码
  1. // CustomDataTpeEditor.Build.cs
  2. using UnrealBuildTool;
  3. public class CustomDataTypeEditor : ModuleRules
  4. {
  5.         public CustomDataTypeEditor(ReadOnlyTargetRules Target) : base(Target)
  6.         {
  7.                 PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
  8.        
  9.                 PublicDependencyModuleNames.AddRange(new string[]
  10.                 {
  11.                         ”Core”,
  12.                         ”CoreUObject”,
  13.                         ”Engine”,
  14.                         ”InputCore”,
  15.                         ”CustomDataType”,
  16.                         ”UnrealEd”,
  17.                         ”AssetTools”,
  18.                         ”Slate”,
  19.                         ”SlateCore”,
  20.                 });
  21.                 PrivateDependencyModuleNames.AddRange(new string[] { });
  22.         }
  23. }
复制代码
编译并重启编纂器,创建一个 UCustomNormalDistribution 资产,双击打开编纂器。



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

导入和导出 ~♡

导入为自定义资产类型

探索发现,新建和导入的功能似乎无法同时存在于一个工厂里,概念上确实讲得通。如果有读者知道如何让他们共存的方式,欢迎奉告我。
为了能够实现导入的功能,我们需要增加一个新的工厂。


从文件导入

先来到 CustomNormalDistribution.h 里,添加一个路径变量,这个变量用于记录数据的导入来源。
  1. // CustomNormalDistribution.h
  2. UCLASS(BlueprintType)
  3. class CUSTOMDATATYPE_API UCustomNormalDistribution : public UObject
  4. {
  5.         GENERATED_BODY()
  6.        
  7. public:
  8.         UCustomNormalDistribution();
  9.         UFUNCTION(BlueprintCallable)
  10.         float DrawSample();
  11.         UFUNCTION(CallInEditor)
  12.         void LogSample();
  13. public:
  14.         UPROPERTY(EditAnywhere)
  15.         float Mean;
  16.         UPROPERTY(EditAnywhere)
  17.         float StandardDeviation;
  18. // 新增部门
  19. #if WITH_EDITORONLY_DATA
  20.         UPROPERTY(VisibleAnywhere)
  21.         FString SourceFilePath;
  22. #endif
  23. private:
  24.         std::mt19937 RandomNumberGenerator;
  25. };
复制代码
然后创建新工厂。在 CustomDataTypeEditor 文件夹下创建“CustomNormalDistributionImportFactory.h”和“CustomNormalDistributionImportFactory.cpp”。



我们需要重写虚函数 FactoryCreateText() 和 FactoryCanImport(),GetValueFromFile() 是一个辅助函数,辅佐我们从文件读取参数。
  1. // CustomNormalDistributionImportFactory.h
  2. #pragma once
  3. #include ”CoreMinimal.h”
  4. #include ”Factories/Factory.h”
  5. #include ”CustomNormalDistributionImportFactory.generated.h”
  6. UCLASS()
  7. class UCustomNormalDistributionImportFactory : public UFactory
  8. {
  9.         GENERATED_BODY()
  10. public:
  11.         UCustomNormalDistributionImportFactory();
  12.         virtual UObject* FactoryCreateText(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, const TCHAR* Type, const TCHAR*& Buffer, const TCHAR* BufferEnd, FFeedbackContext* Warn) override;
  13.         virtual bool FactoryCanImport(const FString& Filename) override;
  14.         FString GetValueFromFile(const TCHAR*& Buffer, FString SectionName, FString VarName);
  15. };
复制代码
首先,我们需要在构造函数里设定 UCustomNormalDistributionImportFactory 的行为。我们添加一个可导入的后缀名 .cnd,并在 FactoryCanImport() 中进行判断。我们假设数据的文件格式与 .ini 文件类似,它具有一些 Section,每个 Section 下有名称分歧的参数,且 Section 之间可能具有同名参数,就像这样:



这样的好处是,由于我们使用了与 .ini 文件同样的格式,我们可以使用 ConfigCacheIni.h 中的现有的功能来读取文件。当然也可以替换为任意的自定义法则。
  1. // CustomNormalDistributionImportFactory.cpp
  2. #include ”CustomNormalDistributionImportFactory.h”
  3. #include ”CustomNormalDistribution.h”
  4. #include ”EditorFramework/AssetImportData.h”
  5. // #include ”Misc/ConfigCacheIni.h”
  6. UCustomNormalDistributionImportFactory::UCustomNormalDistributionImportFactory()
  7. {
  8.         SupportedClass = UCustomNormalDistribution::StaticClass();
  9.         // 必需封锁可新建
  10.         // 添加可导入的文件名后缀
  11.         // 开启可导入
  12.         // 导入的文件格式为 Text(另一种格式为二进制)
  13.         bCreateNew = false;
  14.         Formats.Add(TEXT(”cnd;Custom Normal Distribution”));
  15.         bEditorImport = true;
  16.         bText = true;
  17. }
  18. UObject* UCustomNormalDistributionImportFactory::FactoryCreateText(UClass* InClass, UObject* InParent, FName InName,
  19.         EObjectFlags Flags, UObject* Context, const TCHAR* Type, const TCHAR*& Buffer, const TCHAR* BufferEnd,
  20.         FFeedbackContext* Warn)
  21. {
  22.         GEditor->GetEditorSubsystem<UImportSubsystem>()->OnAssetPreImport.Broadcast(this, InClass, InParent, InName, Type);
  23.         // 查抄传入的类型和后缀名
  24.         if (InClass != UCustomNormalDistribution::StaticClass()
  25.                 || FCString::Stricmp(Type, TEXT(”cnd”)) != 0) return nullptr;
  26.         UCustomNormalDistribution* Data = CastChecked<UCustomNormalDistribution>(
  27.                 NewObject<UCustomNormalDistribution>(InParent, InName, Flags));
  28.         // 从文件获取值
  29.         Data->Mean = FCString::Atof(*GetValueFromFile(Buffer, ”[MySection]”, ”Mean”));
  30.         Data->StandardDeviation = FCString::Atof(*GetValueFromFile(Buffer, ”[MySection]”, ”StandardDeviation”));
  31.         // 从文件获取值的另一种方式。我们特意将文件内容的书写格式与.ini相似,所以也可以借用.ini方式措置。
  32.         // FConfigCacheIni Config(EConfigCacheType::Temporary);
  33.         // Config.LoadFile(CurrentFilename);
  34.         // Config.GetFloat(TEXT(”MySection”), TEXT(”Mean”), Data->Mean, CurrentFilename);
  35.         // Config.GetFloat(TEXT(”MySection”), TEXT(”StandardDeviation”), Data->StandardDeviation, CurrentFilename);
  36.         // 储存导入的路径
  37.         Data->SourceFilePath = UAssetImportData::SanitizeImportFilename(CurrentFilename, Data->GetPackage());
  38.         GEditor->GetEditorSubsystem<UImportSubsystem>()->OnAssetPostImport.Broadcast(this, Data);
  39.        
  40.         return Data;
  41. }
  42. bool UCustomNormalDistributionImportFactory::FactoryCanImport(const FString& Filename)
  43. {
  44.         return FPaths::GetExtension(Filename).Equals(TEXT(”cnd”));
  45. }
  46. FString UCustomNormalDistributionImportFactory::GetValueFromFile(const TCHAR*& Buffer, FString SectionName, FString VarName)
  47. {
  48.         FString Str(Buffer);
  49.         Str = Str.Replace(TEXT(”\r”), TEXT(””));
  50.        
  51.         TArray<FString> Lines;
  52.         Str.ParseIntoArray(Lines, TEXT(”\n”), true);
  53.        
  54.         bool bInSection = false;
  55.        
  56.         for (FString Line : Lines)
  57.         {
  58.                 if (Line == SectionName)
  59.                 {
  60.                         bInSection = true;
  61.                 }
  62.                 else if (Line.StartsWith(”[”) && Line.EndsWith(”]”))
  63.                 {
  64.                         bInSection = false;
  65.                 }
  66.                 if (bInSection)
  67.                 {
  68.                         int32 Pos = Line.Find(”=”);
  69.                         if (Pos != INDEX_NONE)
  70.                         {
  71.                                 FString Name = Line.Left(Pos);
  72.                                 FString Value = Line.Mid(Pos + 1);
  73.                                 if (Name == VarName)
  74.                                 {
  75.                                         return Value;
  76.                                 }
  77.                         }
  78.                 }
  79.         }
  80.        
  81.         return ””;
  82. }
复制代码
编译并重启编纂器,拖拽我们的文件到 ContentBrowser 或使用面板上的导入按钮,此刻就可以导入本身的 .cnd 文件了。



TODO:导入时对话框



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

重导入自定义资产类型

重导入需要实现三个虚函数:CanReimport(),SetReimportPaths() 和 Reimport()。我们可以将重导入的逻辑写在之前的 UCustomNormalDistributionImportFactory 工厂里。



来到 CustomNormalDistributionImportFactory.h 拓展 UCustomNormalDistributionImportFactory 工厂类。
  1. // CustomNormalDistributionImportFactory.h
  2. #pragma once
  3. #include ”CoreMinimal.h”
  4. #include ”Factories/Factory.h”
  5. #include ”EditorReimportHandler.h” // 新增头文件
  6. #include ”CustomNormalDistributionImportFactory.generated.h”
  7. UCLASS()
  8. class UCustomNormalDistributionImportFactory : public UFactory, public FReimportHandler
  9. {
  10.         GENERATED_BODY()
  11. public:
  12.         UCustomNormalDistributionImportFactory();
  13.         virtual UObject* FactoryCreateText(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, const TCHAR* Type, const TCHAR*& Buffer, const TCHAR* BufferEnd, FFeedbackContext* Warn) override;
  14.         virtual bool FactoryCanImport(const FString& Filename) override;
  15.         FString GetValueFromFile(const TCHAR*& Buffer, FString SectionName, FString VarName);
  16.         // 新增 Reimporter 虚函数实现
  17.         virtual bool CanReimport(UObject* Obj, TArray<FString>& OutFilenames) override;
  18.         virtual void SetReimportPaths(UObject* Obj, const TArray<FString>& NewReimportPaths) override;
  19.         virtual EReimportResult::Type Reimport(UObject* Obj) override;
  20. };
复制代码
与导入文件分歧,重导入时我们要使用辅助类 FileHelper 来本身读取文件内容。
  1. // CustomNormalDistributionImportFactory.cpp
  2. #include ”Misc/FileHelper.h”
  3. bool UCustomNormalDistributionImportFactory::CanReimport(UObject* Obj, TArray<FString>& OutFilenames)
  4. {
  5.         UCustomNormalDistribution* Data = Cast<UCustomNormalDistribution>(Obj);
  6.         if (Data)
  7.         {
  8.                 OutFilenames.Add(UAssetImportData::ResolveImportFilename(
  9.                         Data->SourceFilePath, Data->GetPackage()));
  10.                 return true;
  11.         }
  12.         return false;
  13. }
  14. void UCustomNormalDistributionImportFactory::SetReimportPaths(UObject* Obj, const TArray<FString>& NewReimportPaths)
  15. {
  16.         UCustomNormalDistribution* Data = Cast<UCustomNormalDistribution>(Obj);
  17.         if (Data && ensure(NewReimportPaths.Num() == 1))
  18.         {
  19.                 Data->SourceFilePath =
  20.                         UAssetImportData::SanitizeImportFilename(NewReimportPaths[0], Data->GetPackage());
  21.         }
  22. }
  23. EReimportResult::Type UCustomNormalDistributionImportFactory::Reimport(UObject* Obj)
  24. {
  25.         UCustomNormalDistribution* Data = Cast<UCustomNormalDistribution>(Obj);
  26.         if (!Data)
  27.         {
  28.                 return EReimportResult::Failed;
  29.         }
  30.         const FString Filename =
  31.                 UAssetImportData::ResolveImportFilename(Data->SourceFilePath, Data->GetPackage());
  32.         if (!FPaths::GetExtension(Filename).Equals(TEXT(”cnd”)))
  33.         {
  34.                 return EReimportResult::Failed;
  35.         }
  36.         CurrentFilename = Filename;
  37.         FString LoadedData;
  38.         if (FFileHelper::LoadFileToString(LoadedData, *CurrentFilename))
  39.         {
  40.                 const TCHAR* LoadedDataChar = *LoadedData;
  41.                 Data->Modify();
  42.                 Data->MarkPackageDirty();
  43.                 Data->Mean = FCString::Atof(*GetValueFromFile(LoadedDataChar, ”[MySection]”, ”Mean”));
  44.                 Data->StandardDeviation =
  45.                         FCString::Atof(*GetValueFromFile(LoadedDataChar, ”[MySection]”, ”StandardDeviation”));
  46.                 Data->SourceFilePath =
  47.                         UAssetImportData::SanitizeImportFilename(CurrentFilename, Data->GetPackage());
  48.         }
  49.         return EReimportResult::Succeeded;
  50. }
复制代码
然后我们给 UCustomNormalDistribution 资产的右键菜单添加一个“Reimport”按钮,调用 UCustomNormalDistributionImportFactory::Reimport。来到 UCustomNormalDistributionActions.h 中添加如下函数。
  1. // UCustomNormalDistributionActions.h
  2. class FCustomNormalDistributionActions : public FAssetTypeActions_Base
  3. {
  4. public:
  5.         ...
  6.         void ExecuteReimport(TArray<TWeakObjectPtr<class UCustomNormalDistribution>> Objects);
  7. ...
  8. };
复制代码
实现 ExecuteReimport()。
  1. // UCustomNormalDistributionActions.cpp
  2. #include ”EditorReimportHandler.h”
  3. void FCustomNormalDistributionActions::ExecuteReimport(TArray<TWeakObjectPtr<UCustomNormalDistribution>> Objects)
  4. {
  5.         for (auto ObjIt = Objects.CreateConstIterator(); ObjIt; ++ObjIt)
  6.         {
  7.                 auto Object = (*ObjIt).Get();
  8.                 if (Object)
  9.                 {
  10.                         FReimportManager::Instance()->Reimport(Object, /*bAskForNewFileIfMissing=*/true);
  11.                 }
  12.         }
  13. }
复制代码
编译并重启编纂器,导入一个 .cnd 文件资产,就可以通过右键菜单中的 Reimport 进行重导入了。

TODO:从新文件重导入资产



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

导出自定义资产类型

我们可以担任一个 UExporter 类,来定义自定义数据类型的导出操作。



在 CustomDataTypeEditor 文件夹下新建“CustomNormalDistributionExporter.h”和“CustomNormalDistributionExporter.cpp”文件。



与 UFactory 类似,我们需要在构造函数中设定 UCustomNormalDistributionExporter 的行为,并重写 SupportsObject() 和 ExportText() 两个虚函数。
此处使用 ExportText(),因为我们但愿直接将数据保留为 .cnd 文件,如果要保留为二进制,则可以使用 ExportBinary()。
  1. // CustomNormalDistributionExporter.h
  2. #pragma once
  3. #include ”CoreMinimal.h”
  4. #include ”Exporters/Exporter.h”
  5. #include ”CustomNormalDistributionExporter.generated.h”
  6. UCLASS()
  7. class UCustomNormalDistributionExporter : public UExporter
  8. {
  9.         GENERATED_BODY()
  10.        
  11. public:
  12.         UCustomNormalDistributionExporter();
  13.        
  14.         virtual bool SupportsObject(UObject* Object) const override;
  15.         virtual bool ExportText(const FExportObjectInnerContext* Context, UObject* Object, const TCHAR* Type, FOutputDevice& Ar, FFeedbackContext* Warn, uint32 PortFlags) override;
  16.        
  17. };
复制代码
要使用 ExportText(),则需要将 bText 设置为 true,否则默认不会调用 ExportText() 而是 ExportBinary()。
  1. // CustomNormalDistributionExporter.cpp
  2. #pragma once
  3. #include ”CustomNormalDistributionExporter.h”
  4. #include ”CustomNormalDistribution.h”
  5. UCustomNormalDistributionExporter::UCustomNormalDistributionExporter()
  6. {
  7.         SupportedClass = UCustomNormalDistribution::StaticClass();
  8.         PreferredFormatIndex = 0;
  9.         FormatExtension.Add(TEXT(”cnd”));
  10.         FormatDescription.Add(TEXT(”Custom Normal Distribution”));
  11.         bText = true;
  12. }
  13. bool UCustomNormalDistributionExporter::SupportsObject(UObject* Object) const
  14. {
  15.         return (SupportedClass && Object->IsA(SupportedClass));
  16. }
  17. bool UCustomNormalDistributionExporter::ExportText(const FExportObjectInnerContext* Context, UObject* Object,
  18.         const TCHAR* Type, FOutputDevice& Ar, FFeedbackContext* Warn, uint32 PortFlags)
  19. {
  20.         UCustomNormalDistribution* Data = Cast<UCustomNormalDistribution>(Object);
  21.         if (!Data)
  22.         {
  23.                 return false;
  24.         }
  25.        
  26.         // 输出内容
  27.         Ar.Log(TEXT(”[MySection]\r\n”));
  28.         Ar.Logf(TEXT(”Mean=%f\r\n”), Data->Mean);
  29.         Ar.Logf(TEXT(”StandardDeviation=%f”), Data->StandardDeviation);
  30.        
  31.         return true;
  32. }
复制代码
此刻编译并重启编纂器,任意改削一个 UCustomNormalDistribution 资产,并导出,可以看到它被正确导出到磁盘了。



本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-12-22 23:44 , Processed in 0.104165 second(s), 27 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表