UE4自定义资产类型编辑器实现

这篇具有很好参考价值的文章主要介绍了UE4自定义资产类型编辑器实现。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

在虚幻引擎中,资产是具有持久属性的对象,可以在编辑器中进行操作。 Unreal 附带多种资源类型,从 UStaticMesh 到 UMetasoundSources 等等。 自定义资源类型是实现专门对象的好方法,这些对象需要专门构建的编辑器来进行高效操作。 通过在插件中实现这些类型,它们可以在项目和开发人员之间轻松共享。
UE4自定义资产类型编辑器实现

推荐:用 NSDT设计器 快速搭建可编程3D场景。

在本教程中,我们将编写一个插件,将自定义资源类型添加到引擎中。 我们的资产类型将代表我们可以从中抽取样本的正态分布。 我们将设置一个编辑器来显示分布的概率密度函数 (PFD),并让我们同时编辑其平均值和标准差。

2、创建插件

要继续操作,请打开一个空白的 C++ Unreal 游戏项目。 首先导航到顶部菜单栏中的“编辑”>“插件”,然后单击对话框窗口左上角的“添加”。 选择“Blank”插件模板,输入名称“AssetTutorialPlugin”,然后单击“创建插件”。
UE4自定义资产类型编辑器实现

插件创建完成后,切换到 Visual Studio。 应出现一个对话框,要求你重新加载修改后的解决方案。 单击“重新加载全部”并在出现提示时停止调试。 如果创建插件时 Visual Studio 未打开,请打开项目文件夹,右键单击 .uproject 文件并单击“生成 Visual Studio 项目文件”,然后打开生成的 .sln 文件。
UE4自定义资产类型编辑器实现

在 Visual Studio 中,在解决方案资源管理器中找到项目的 Plugins 文件夹。 它应该具有如上所示的结构。 .uplugin 文件包含有关您的插件的信息以及启用插件时要加载的模块列表。 模块包含代码和编译设置(在模块的 .Build.cs 文件中设置)。

3、添加编辑器模块

Unreal 为我们创建了一个与我们的插件同名的模块。 它在我们的 .uplugin 文件中作为运行时模块列出。 为了实现我们的自定义资产编辑器,我们需要一个未加载到打包游戏中的附加编辑器模块。

在文件资源管理器中打开项目文件夹,导航到 “Plugins\AssetTutorialPlugin\Source” 并创建“AssetTutorialPlugin”模块文件夹的副本。 将副本重命名为“AssetTutorialPluginEditor”,并将所有文件名和文件内容中出现的所有“AssetTutorialPlugin”替换为“AssetTutorialPluginEditor”。 然后导航回项目的根文件夹,右键单击 .uproject 文件并重新生成 Visual Studio 项目文件。 打开 .uplugin 文件并编辑“模块”列表以包含新的编辑器模块,如下所示。

	"Modules": [
		{
			"Name": "AssetTutorialPlugin",
			"Type": "Runtime",
			"LoadingPhase": "Default"
		},
		{
			"Name": "AssetTutorialPluginEditor",
			"Type": "Editor",
			"LoadingPhase": "Default"
		}
	]

4、创建自定义资产类型

通过在 Visual Studio 中构建和调试项目并将构建配置设置为“开发编辑器”来重新启动虚幻编辑器,然后导航到顶部菜单栏中的“工具>新建 C++ 类…”。 切换到对话框顶部的“All Classes”,然后选择“Object”作为父类。 单击“下一步”,将“Class Type”设置为“Public”,输入“NormalDistribution”作为名称,然后从名称输入字段旁边的下拉菜单中选择“AssetTutorialPlugin (Runtime)”作为目标模块。 然后点击“创建班级”。 创建完成后,切换回 Visual Studio 并重新加载解决方案(出现提示时停止调试)。

我们现在将声明并定义我们的自定义资产类型。 在此步骤中,你决定资产类型应具有哪些属性以及它支持哪些操作。 出于本教程的目的,我们将创建一个简单的资产类型,允许使用 std::normal_distribution 从具有给定均值和标准差的正态分布中抽取样本。

5、声明自定义资产类型

打开新创建的“NormalDistribution.h”以声明自定义资源类型,如下所示。

#pragma once

#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include <random>
#include "NormalDistribution.generated.h"

UCLASS(BlueprintType)
class ASSETTUTORIALPLUGIN_API UNormalDistribution : public UObject
{
	GENERATED_BODY()
public:
	UNormalDistribution();

	UPROPERTY(EditAnywhere)
	float Mean;

	UPROPERTY(EditAnywhere)
	float StandardDeviation;

	UFUNCTION(BlueprintCallable)
	float DrawSample();

	UFUNCTION(CallInEditor)
	void LogSample();
private:
	std::mt19937 RandomNumberGenerator;
};

我们的自定义资产类型的声明方式与任何其他 UObject 派生类类似,因此我们包含 . generated.h 文件并确保调用 UCLASS() 和 GENERATED_BODY() 宏。

6、定义自定义资产类型

现在打开“NormalDistribution.cpp”来定义自定义资产类型的功能,如下所示。

#include "NormalDistribution.h"

UNormalDistribution::UNormalDistribution()
    : Mean(0.5f)
    , StandardDeviation(0.2f)
{}

float UNormalDistribution::DrawSample()
{
    return std::normal_distribution<>(Mean, StandardDeviation)(RandomNumberGenerator);
}

void UNormalDistribution::LogSample()
{
    UE_LOG(LogTemp, Log, TEXT("%f"), DrawSample())
}

Unreal 现在可以识别我们的 NormalDistribution 类型,正如你在构建项目并重新启动 Unreal 编辑器时看到的那样,然后打开“Tools>Class Viewer”,确保取消选中“Actors Only”过滤器并搜索“NormalDistribution” 。 但是,我们还无法通过在内容浏览器中右键单击来创建 NormalDistribution 资源。 为了实现这一点,我们需要将 UNormalDistribution 注册为资产类型并提供一个工厂来创建新实例。

7、注册自定义资产类型

再次打开“工具>新建 C++ 类…”对话框。 这次,选择“None”作为父类,将“Class Type”设置为“Public”,将类命名为“NormalDistributionActions”,并选择“AssetTutorialPluginEditor(编辑器)”作为目标模块。 然后单击“创建类”并像以前一样返回到 Visual Studio。

我们需要实现一个继承自 IAssetTypeActions 的类来向引擎注册我们的资产类型。 通过重写界面的方法,我们可以设置资产在编辑器的内容浏览器中的外观和行为。 我们可以选择名称、类别、颜色、右键单击资产时上下文菜单的操作等。

打开“NormalDistributionActions.h”为我们的资产类型声明资产类型操作,如下所示。 请注意,类名称为“FNormalDistributionAssetTypeActions”,以符合 Unreal 命名约定。

#pragma once

#include "CoreMinimal.h"
#include "AssetTypeActions_Base.h"

class FNormalDistributionAssetTypeActions : public FAssetTypeActions_Base
{
public:
	UClass* GetSupportedClass() const override;
	FText GetName() const override;
	FColor GetTypeColor() const override;
	uint32 GetCategories() override;
};

使用“NormalDistributionActions.cpp”定义资产类型操作的函数,如下所示。

#include "NormalDistributionActions.h"
#include "NormalDistribution.h"

UClass* FNormalDistributionAssetTypeActions::GetSupportedClass() const
{
    return UNormalDistribution::StaticClass();
}

FText FNormalDistributionAssetTypeActions::GetName() const
{
    return INVTEXT("Normal Distribution");
}

FColor FNormalDistributionAssetTypeActions::GetTypeColor() const
{
    return FColor::Cyan;
}

uint32 FNormalDistributionAssetTypeActions::GetCategories()
{
    return EAssetTypeCategories::Misc;
}

8、注册资产类型操作

FNormalDistributionAssetTypeActions 不是从 UObject 派生的,因此引擎和编辑器不知道它的存在。 我们需要手动将其注册到引擎的AssetToolsModule中。 由于我们希望只要插件处于活动状态,我们的自定义资源类型就可以在编辑器中使用,因此手动注册的好地方是 FAssetTutorialPluginEditorModule 类的 StartupModule() 函数。 当首次加载模块并调用 StartupModule() 函数时,将创建此类型的唯一对象。 因此我们可以使用它来执行模块范围的设置和注册。 当模块关闭时,我们还将取消注册资产类型操作。

打开“AssetTutorialPluginEditor.h”来声明我们的编辑器模块类,如下所示。

#pragma once

#include "CoreMinimal.h"
#include "Modules/ModuleManager.h"
#include "NormalDistributionActions.h"

class FAssetTutorialPluginEditorModule : public IModuleInterface
{
public:
	void StartupModule() override;
	void ShutdownModule() override;
private:
	TSharedPtr<FNormalDistributionAssetTypeActions> NormalDistributionAssetTypeActions;
};

打开“AssetTutorialPluginEditor.cpp”来定义我们的编辑器模块类的函数,如下所示。

#include "AssetTutorialPluginEditor.h"

void FAssetTutorialPluginEditorModule::StartupModule()
{
	NormalDistributionAssetTypeActions = MakeShared<FNormalDistributionAssetTypeActions>();
	FAssetToolsModule::GetModule().Get().RegisterAssetTypeActions(NormalDistributionAssetTypeActions.ToSharedRef());
}

void FAssetTutorialPluginEditorModule::ShutdownModule()
{
	if (!FModuleManager::Get().IsModuleLoaded("AssetTools")) return;
	FAssetToolsModule::GetModule().Get().UnregisterAssetTypeActions(NormalDistributionAssetTypeActions.ToSharedRef());
}

IMPLEMENT_MODULE(FAssetTutorialPluginEditorModule, AssetTutorialPluginEditor)

9、添加模块依赖项

现在编译将会失败,因为我们的资产类型操作类位于 AssetTutorialPluginEditor 模块中,而我们的 UNormalDistribution 则始终位于 AssetTutorialPlugin 模块中。 我们需要添加运行时模块作为编辑器模块的依赖项。 此外,我们需要添加对 UnrealEd 模块的依赖项,这是注册资产类型操作所需的。

打开“AssetTutorialPluginEditor.Build.cs”并编辑以“PrivateDependencyModuleNames.AddRange(…)”开头的语句,如下所示。

		PrivateDependencyModuleNames.AddRange(
			new string[]
			{
				"CoreUObject",
				"Engine",
				"Slate",
				"SlateCore",
				"AssetTutorialPlugin",
				"UnrealEd"
				// ... add private dependencies that you statically link with here ...	
			}
			);

10、创建工厂

在内容浏览器中右键单击以创建新资产时,我们的正态分布资产类型仍然没有显示。 但我们已经快到了! 再次在虚幻编辑器中使用“Tools>New C++ Class…”,切换到“All Classes”,搜索“factory”并选择“Factory”作为父类。 您可能需要折叠一些层次结构,例如“ActorFactory”,直到仅出现“Factory”。 选择“Public”,将其命名为“NormalDistributionFactory”,然后选择“AssetTutorialPluginEditor(编辑器)”作为目标模块。 创建类并切换回 Visual Studio。

从 UFactory 派生的类用于指定所选资产类型的创建或导入逻辑。 有些工厂在创建资产时会打开一个对话框来收集用户的设置。 我们将创建一个最小工厂,当请求资产类型的新实例时,它基本上只是包装对 NewObject() 的调用。

打开“NormalDistributionFactory.h”并声明我们的工厂类型,如下所示。

#pragma once

#include "CoreMinimal.h"
#include "Factories/Factory.h"
#include "NormalDistributionFactory.generated.h"

UCLASS()
class UNormalDistributionFactory : public UFactory
{
    GENERATED_BODY()
public:
    UNormalDistributionFactory();
    UObject* FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn);
};

打开“NormalDistributionFactory.cpp”并定义构造函数和 FactoryCreateNew(),如下所示。 在构造函数中将 bCreateNew 设置为 true 将允许我们在内容浏览器中创建我们类型的资源!

#include "NormalDistributionFactory.h"
#include "NormalDistribution.h"

UNormalDistributionFactory::UNormalDistributionFactory()
{
    SupportedClass = UNormalDistribution::StaticClass();
    bCreateNew = true;
}

UObject* UNormalDistributionFactory::FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn)
{
    return NewObject<UNormalDistribution>(InParent, Class, Name, Flags, Context);
}

UNormalDistributionFactory 确实派生自 UObject,因此当加载其模块时,其 UCLASS 会自动注册到引擎。 编译并运行虚幻编辑器,在内容浏览器中右键单击,导航至“Miscellaneous>Normal Distribution”,看看我们已经取得了什么成果。
UE4自定义资产类型编辑器实现

创建一个新的正态分布资产并双击它以打开其编辑器。 你将看到默认的资产编辑器,它仅显示可用于编辑资产属性的详细信息视图。 请注意,我们有一个“Log Sample”按钮,因为我们将“CallInEditor”说明符添加到 UNormalDistribution 中 LogSample 函数的 UFUNCTION() 声明中。 你可以使用平均值和标准差,按“记录样本”并检查输出日志以验证我们的资产是否按预期工作。
UE4自定义资产类型编辑器实现

11、创建资产编辑器

我想使用概率分布函数的交互式图来编辑我的正态分布。 为此,我们需要创建一个 Slate 小部件来绘制一些线条,然后我们需要让引擎知道我们要在资源编辑器中使用它。

12、创建交互式 PDF 绘图板小部件

“Tools>New C++ Class…”,“None”作为父类,设置为“Public”,命名为“SNormalDistributionWidget”,选择“AssetTutorialPluginEditor(编辑器)”作为目标模块。 创建类并切换回 Visual Studio。

Slate 是 Unreal 的 UI 框架,可用于在应用程序的窗口中定位和绘制交互式文本、线条、纹理、材质等。 Unreal 附带了大量的小部件,从处理布局的面板小部件(如 SHorizontalBox)到显示(可能是动态)内容的 STextBlock 之类的叶小部件。

现在,我们将创建一个叶子小部件,它将显示 PDF 绘图,并让我们通过在其上拖动鼠标来编辑分布。 打开“SNormalDistributionWidget.h”并声明我们的小部件,如下所示。

#pragma once

#include "CoreMinimal.h"
#include "Widgets/SLeafWidget.h"

DECLARE_DELEGATE_OneParam(FOnMeanChanged, float /*NewMean*/)
DECLARE_DELEGATE_OneParam(FOnStandardDeviationChanged, float /*NewStandardDeviation*/)

class SNormalDistributionWidget : public SLeafWidget
{
public:
	SLATE_BEGIN_ARGS(SNormalDistributionWidget)
		: _Mean(0.5f)
		, _StandardDeviation(0.2f)
		{}
		SLATE_ATTRIBUTE(float, Mean)
		SLATE_ATTRIBUTE(float, StandardDeviation)
		SLATE_EVENT(FOnMeanChanged, OnMeanChanged)
		SLATE_EVENT(FOnStandardDeviationChanged, OnStandardDeviationChanged)
	SLATE_END_ARGS()

	void Construct(const FArguments& InArgs);

	int32 OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const override;
	FVector2D ComputeDesiredSize(float) const override;

	FReply OnMouseButtonDown(const FGeometry& AllottedGeometry, const FPointerEvent& MouseEvent) override;
	FReply OnMouseButtonUp(const FGeometry& AllottedGeometry, const FPointerEvent& MouseEvent) override;
	FReply OnMouseMove(const FGeometry& AllottedGeometry, const FPointerEvent& MouseEvent) override;
private:
	TAttribute<float> Mean;
	TAttribute<float> StandardDeviation;

	FOnMeanChanged OnMeanChanged;
	FOnStandardDeviationChanged OnStandardDeviationChanged;

	FTransform2D GetPointsTransform(const FGeometry& AllottedGeometry) const;
};

让我们对上面的声明进行一些详细说明。 我们首先声明一些委托类型:FOnMeanChanged 和 FOnStandardDeviationChanged。 这些类型的对象可以绑定到其他对象的成员函数,当我们的小部件触发某些事件时,这些对象会做出反应。 通过使用委托,我们的小部件与 UNormalDistribution 实现保持分离。

我们继续声明我们的 SLeafWidget 派生类型,利用一些 Slate 宏来使用 Slate 的声明语法来实例化我们的小部件。 像 Mean 和 StandardDeviation 这样的 Slate 属性也可以使用委托对象进行初始化,这样我们就可以在需要时轮询其他对象来获取这些值。

当我们的小部件在声明性 Synatx 中实例化时,Slate 会调用 Construct() 成员函数。 其余的公共函数重写虚拟 SWidget 成员函数来定义我们的小部件的行为。 Slate 会给我们分配一个特定的 FGeometry,它代表屏幕上我们可以绘制的一个矩形。 它会考虑我们想要的尺寸,但我们不能依赖分配的几何形状为该尺寸。 我们希望绘图有一个动态边距,能够响应分配的几何图形,这就是 GetPointsTransform() 的目的。

接下来,打开“SNormalDistributionWidget.cpp”来定义我们的小部件的功能,如下所示。

#include "SNormalDistributionWidget.h"
#include "Editor.h"

void SNormalDistributionWidget::Construct(const FArguments& InArgs)
{
    Mean = InArgs._Mean;
    StandardDeviation = InArgs._StandardDeviation;
    OnMeanChanged = InArgs._OnMeanChanged;
    OnStandardDeviationChanged = InArgs._OnStandardDeviationChanged;
}

int32 SNormalDistributionWidget::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const
{
    const int32 NumPoints = 512;
    TArray<FVector2D> Points;
    Points.Reserve(NumPoints);
    const FTransform2D PointsTransform = GetPointsTransform(AllottedGeometry);
    for (int32 PointIndex = 0; PointIndex < NumPoints; ++PointIndex)
    {
        const float X = PointIndex / (NumPoints - 1.0);
        const float D = (X - Mean.Get()) / StandardDeviation.Get();
        const float Y = FMath::Exp(-0.5f * D * D);
        Points.Add(PointsTransform.TransformPoint(FVector2D(X, Y)));
    }
    FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Points);
    return LayerId;
}

FVector2D SNormalDistributionWidget::ComputeDesiredSize(float) const
{
    return FVector2D(200.0, 200.0);
}

FReply SNormalDistributionWidget::OnMouseButtonDown(const FGeometry& AllottedGeometry, const FPointerEvent& MouseEvent)
{
    if (GEditor && GEditor->CanTransact() && ensure(!GIsTransacting))
        GEditor->BeginTransaction(TEXT(""), INVTEXT("Edit Normal Distribution"), nullptr);
    return FReply::Handled().CaptureMouse(SharedThis(this));
}

FReply SNormalDistributionWidget::OnMouseButtonUp(const FGeometry& AllottedGeometry, const FPointerEvent& MouseEvent)
{
    if (GEditor) GEditor->EndTransaction();
    return FReply::Handled().ReleaseMouseCapture();
}

FReply SNormalDistributionWidget::OnMouseMove(const FGeometry& AllottedGeometry, const FPointerEvent& MouseEvent)
{
    if (!HasMouseCapture()) return FReply::Unhandled();
    const FTransform2D PointsTransform = GetPointsTransform(AllottedGeometry);
    const FVector2D LocalPosition = AllottedGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition());
    const FVector2D NormalizedPosition = PointsTransform.Inverse().TransformPoint(LocalPosition);
    if (OnMeanChanged.IsBound())
        OnMeanChanged.Execute(NormalizedPosition.X);
    if (OnStandardDeviationChanged.IsBound())
        OnStandardDeviationChanged.Execute(FMath::Max(0.025f, FMath::Lerp(0.025f, 0.25f, NormalizedPosition.Y)));
    return FReply::Handled();
}

FTransform2D SNormalDistributionWidget::GetPointsTransform(const FGeometry& AllottedGeometry) const
{
    const double Margin = 0.05 * AllottedGeometry.GetLocalSize().GetMin();
    const FScale2D Scale((AllottedGeometry.GetLocalSize() - 2.0 * Margin) * FVector2D(1.0, -1.0));
    const FVector2D Translation(Margin, AllottedGeometry.GetLocalSize().Y - Margin);
    return FTransform2D(Scale, Translation);
}

上面显示的实现的一些细节:在 OnPaint() 中,我们传递了一个“OutDrawElements”列表,我们可以向其中添加文本、线条等来构建视觉表示。 我们的 PDF 在多个点进行评估,x 值范围从 0 到 1。计算出的“PointTransform”负责将点从其原始空间放置到“AllottedGeometry”指定的空间。 使用 FSlateDrawElement::MakeLines() 添加将变换后的点连接到绘制元素的线后,我们只需返回“LayerId”,因为我们只绘制 1 层元素。

为了在通过将鼠标拖动到小部件上来设置平均值和标准差时启用撤消/重做,我们分别在 OnMouseButtonDown() 和 OnMouseButtonUp() 中的 GEditor 上使用 BeginTransaction() 和 EndTransaction()。 我们还捕获任何单击的鼠标,直到释放鼠标按钮,这样即使在拖出小部件时我们也可以编辑我们的分布。

在 OnMouseMove() 中,我们仅在当前捕获鼠标时更新分布。 平均值和标准偏差是根据鼠标的位置计算的,考虑到分配的几何形状和我们当前的点变换,然后在它们被绑定时调用事件处理程序。

最后,GetPointsTransform() 会考虑动态边距并翻转 y 轴,因为 Slate 小部件的原点位于左上角。

13、创建资产编辑器工具包

现在我们有了一个小部件,我们仍然需要在打开正态分布资产编辑器时显示它。 有两种方法可以解决此问题:要么保留显示正在编辑的资产的详细信息视图的默认资产编辑器,要么创建并注册从 IDetailCustomization 派生的类。 这样的类可以将交互式 PDF 图添加到正态分布的所有详细视图中。 另一种方法是创建一个从 FAssetEditorToolkit 派生的类,并使用我们的自定义资产编辑器覆盖默认资产编辑器。 我们将采用后一种方式,因为它使我们能够在默认资产编辑器布局中包含输出日志。

最后一次,运行虚幻编辑器,转到“Tools>New C++ Class…”,选择“None”作为父级,将其设置为“Public”,将其命名为“NormalDistributionEditorToolkit”并选择“AssetTutorialPluginEditor (Editor)” 作为目标模块。 创建类并切换回 Visual Studio。

使用我们自己的资产编辑器工具包,我们可以定义资产编辑器的布局并注册选项卡生成器,这些选项卡生成器用于使用包含我们选择的小部件的选项卡填充我们的布局。

打开“NormalDistributionEditorToolkit.h”来声明我们的资产编辑器工具包类,如下所示。

#pragma once

#include "CoreMinimal.h"
#include "NormalDistribution.h"
#include "Toolkits/AssetEditorToolkit.h"

class FNormalDistributionEditorToolkit : public FAssetEditorToolkit
{
public:
	void InitEditor(const TArray<UObject*>& InObjects);

	void RegisterTabSpawners(const TSharedRef<class FTabManager>& TabManager) override;
	void UnregisterTabSpawners(const TSharedRef<class FTabManager>& TabManager) override;

	FName GetToolkitFName() const override { return "NormalDistributionEditor"; }
	FText GetBaseToolkitName() const override { return INVTEXT("Normal Distribution Editor"); }
	FString GetWorldCentricTabPrefix() const override { return "Normal Distribution "; }
	FLinearColor GetWorldCentricTabColorScale() const override { return {}; }

	float GetMean() const;
	float GetStandardDeviation() const;
	void SetMean(float Mean);
	void SetStandardDeviation(float StandardDeviation);
private:
	UNormalDistribution* NormalDistribution;
};

我们将在 InitEditor() 中创建布局,并在相应的函数中(取消)注册选项卡生成器。 我们还为 FAssetEditorToolkit 的纯虚拟成员函数提供重写。 请注意,我们不会在以世界为中心的模式下使用此编辑器。 此外,我们保留一个指向我们正在编辑的 UNormalDistribution 的简单指针。 请注意,一般来说,这样的指针是危险的,因为垃圾收集器可能会在我们不注意的情况下销毁 UObject 派生类的该对象。 但在这种情况下,可以安全地假设只要编辑器打开,资产就会保留在内存中,并且如果在编辑时删除资产,编辑器会自动关闭。 我们的工具包中还有一些资产属性的 getter 和 setter,我们将使用它们绑定到小部件的委托参数。

现在打开“NormalDistributionEditorToolkit.cpp”来定义我们的工具包的函数,如下所示。

#include "NormalDistributionEditorToolkit.h"
#include "Widgets/Docking/SDockTab.h"
#include "SNormalDistributionWidget.h"
#include "Modules/ModuleManager.h"

void FNormalDistributionEditorToolkit::InitEditor(const TArray<UObject*>& InObjects)
{
	NormalDistribution = Cast<UNormalDistribution>(InObjects[0]);

	const TSharedRef<FTabManager::FLayout> Layout = FTabManager::NewLayout("NormalDistributionEditorLayout")
	->AddArea
	(
		FTabManager::NewPrimaryArea()->SetOrientation(Orient_Vertical)
		->Split
		(
			FTabManager::NewSplitter()
			->SetSizeCoefficient(0.6f)
			->SetOrientation(Orient_Horizontal)
			->Split
			(
				FTabManager::NewStack()
				->SetSizeCoefficient(0.8f)
				->AddTab("NormalDistributionPDFTab", ETabState::OpenedTab)
			)
			->Split
			(
				FTabManager::NewStack()
				->SetSizeCoefficient(0.2f)
				->AddTab("NormalDistributionDetailsTab", ETabState::OpenedTab)
			)
		)
		->Split
		(
			FTabManager::NewStack()
			->SetSizeCoefficient(0.4f)
			->AddTab("OutputLog", ETabState::OpenedTab)
		)
	);
	FAssetEditorToolkit::InitAssetEditor(EToolkitMode::Standalone, {}, "NormalDistributionEditor", Layout, true, true, InObjects);
}

void FNormalDistributionEditorToolkit::RegisterTabSpawners(const TSharedRef<class FTabManager>& InTabManager)
{
	FAssetEditorToolkit::RegisterTabSpawners(InTabManager);

	WorkspaceMenuCategory = InTabManager->AddLocalWorkspaceMenuCategory(INVTEXT("Normal Distribution Editor"));

	InTabManager->RegisterTabSpawner("NormalDistributionPDFTab", FOnSpawnTab::CreateLambda([=](const FSpawnTabArgs&)
	{
		return SNew(SDockTab)
		[
			SNew(SNormalDistributionWidget)
			.Mean(this, &FNormalDistributionEditorToolkit::GetMean)
			.StandardDeviation(this, &FNormalDistributionEditorToolkit::GetStandardDeviation)
			.OnMeanChanged(this, &FNormalDistributionEditorToolkit::SetMean)
			.OnStandardDeviationChanged(this, &FNormalDistributionEditorToolkit::SetStandardDeviation)
		];
	}))
	.SetDisplayName(INVTEXT("PDF"))
	.SetGroup(WorkspaceMenuCategory.ToSharedRef());

	FPropertyEditorModule& PropertyEditorModule = FModuleManager::GetModuleChecked<FPropertyEditorModule>("PropertyEditor");
	FDetailsViewArgs DetailsViewArgs;
	DetailsViewArgs.NameAreaSettings = FDetailsViewArgs::HideNameArea;
	TSharedRef<IDetailsView> DetailsView = PropertyEditorModule.CreateDetailView(DetailsViewArgs);
	DetailsView->SetObjects(TArray<UObject*>{ NormalDistribution });
	InTabManager->RegisterTabSpawner("NormalDistributionDetailsTab", FOnSpawnTab::CreateLambda([=](const FSpawnTabArgs&)
	{
		return SNew(SDockTab)
		[
			DetailsView
		];
	}))
	.SetDisplayName(INVTEXT("Details"))
	.SetGroup(WorkspaceMenuCategory.ToSharedRef());
}

void FNormalDistributionEditorToolkit::UnregisterTabSpawners(const TSharedRef<class FTabManager>& InTabManager)
{
	FAssetEditorToolkit::UnregisterTabSpawners(InTabManager);
	InTabManager->UnregisterTabSpawner("NormalDistributionPDFTab");
	InTabManager->UnregisterTabSpawner("NormalDistributionDetailsTab");
}

float FNormalDistributionEditorToolkit::GetMean() const
{
	return NormalDistribution->Mean;
}

float FNormalDistributionEditorToolkit::GetStandardDeviation() const
{
	return NormalDistribution->StandardDeviation;
}

void FNormalDistributionEditorToolkit::SetMean(float Mean)
{
	NormalDistribution->Modify();
	NormalDistribution->Mean = Mean;
}

void FNormalDistributionEditorToolkit::SetStandardDeviation(float StandardDeviation)
{
	NormalDistribution->Modify();
	NormalDistribution->StandardDeviation = StandardDeviation;
}

上面 InitEditor() 的实现首先从 InObjects 参数获取正在编辑的资源对象。 请注意,编辑器可以支持同时编辑多个资源。 我们的示例编辑器没有,只是抓取数组中的第一个对象。 然后我们使用 FTabManager::NewLayout() 定义布局。 使用 AddTab() 添加选项卡时,我们使用自己的选项卡类型:“NormalDistributionPDFTab”和“NormalDistributionDetailsTab”。 我们的工具包还提供了这些选项卡类型名称的选项卡生成器。 引擎已提供“OutputLog”类型的选项卡生成器。 InitEditor() 最后调用父类的 InitAssetEditor() 函数,该函数将处理繁重的工作。

在定义 RegisterTabSpawners() 和 UnregisterTabSpawners() 时,我们也要注意调用父类的实现,因为它们包含一些逻辑。 然后我们传递一些 lambda 委托,这些委托在调用时使用 Slate 的声明性语法简单地创建 SDockTab 及其内容。 创建 SNormalDistributionWidget 时,我们将资产属性的 getter 和 setter 传递给“NormalDistributionWidget.h”中声明的相应属性和事件。 我们还通过对 RegisterTabSpawner() 返回值进行链接函数调用来设置选项卡的名称和组。 我们不仅为 PDF 小部件注册一个选项卡生成器,还为基本详细信息视图注册一个选项卡生成器,我们可以使用 FPropertyEditorModule 创建该视图。

设置器实现中的一个重要细节是对资产对象上的Modify() 的调用。 这会弄脏资产,以便我们可以保存更改,并确保事务缓冲区在编辑之前填充对象的状态,从而启用撤消/重做。

14、使用我们的资产编辑器工具包

差不多了。 当请求资产编辑器时,我们仍然需要创建工具包类的实例。 打开“NormalDistributionActions.h”并将以下成员函数声明添加到 FNormalDistributionAssetTypeActions。

	void OpenAssetEditor(const TArray<UObject*>& InObjects, TSharedPtr<class IToolkitHost> EditWithinLevelEditor) override;

接下来,打开“NormalDistributionActions.cpp”并添加以下行来定义 OpenAssetEditor()。

#include "NormalDistributionEditorToolkit.h"

void FNormalDistributionAssetTypeActions::OpenAssetEditor(const TArray<UObject*>& InObjects, TSharedPtr<class IToolkitHost> EditWithinLevelEditor)
{
    MakeShared<FNormalDistributionEditorToolkit>()->InitEditor(InObjects);
}

你可能想知道 MakeShared() 返回的临时 TSharedRef。 创建的 FNormalDistributionEditorToolkit 不会在 TSharedRef 超出范围时被销毁,因为它通过 FAssetEditorToolkit 从 TSharedFromThis 派生,并且当它完全初始化时,它会在其他地方引用。

现在我们的资产类型操作已经设置完毕,以便在打开资产编辑器时使用我们的资产编辑器工具包,我们可以构建并返回虚幻编辑器,创建正态分布资产并通过单击并拖动其图来编辑其属性 概率分布函数。 如果我们想检查它的行为方式,可以单击详细信息选项卡中的“Log Sample”按钮,将采样值写入输出日志。

请注意,如果你在 InitEditor() 中更改资源工具包的默认布局,则需要在虚幻编辑器中重新加载默认布局以检查您的更改,因为布局会被缓存。 要重置虚幻编辑器布局,请单击自定义资源编辑器顶部菜单栏中的“窗口>加载布局>默认编辑器布局”。
UE4自定义资产类型编辑器实现

本教程的完整代码可从 这里 获取。


原文链接:虚幻引擎自定义资产类型 — BimAnt文章来源地址https://www.toymoban.com/news/detail-502580.html

到了这里,关于UE4自定义资产类型编辑器实现的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • UE5.1编辑器拓展【二、脚本化资产行为,快速更改资产名字,1.直接添加前缀或后缀2.通过资产类判断添加修改前缀】

    目录 了解相关的函数 第一种做法:自定义添加选择资产的前缀或后缀 代码 效果 第二种做法:通过映射来获取资产类型添加前缀和修改前缀 映射代码 代码 效果 在之前一章中,我们创建了插件,用来扩展编辑器的使用: UE5.1编辑器拓展【一、脚本化资产行为,通知,弹窗,

    2024年02月07日
    浏览(47)
  • Ubuntu20.04: UE4.27 中 Source Code 的编辑器下拉框没有 Rider选项

    最近想用 Rider 作为 UE4 开发的 IDE,但安装好 Rider 后,发现编辑器下拉框中没有 Rider 的选项,我检查了 UE4 的插件,发现 Rider Integration 插件已经安装且启用的。 环境:Ubuntu 20.04 + UE4.27 + Rider2023.2 在网上找了挺久,主要都是两种解决方案,有的人可以成功解决问题,但我尝试后

    2024年03月14日
    浏览(63)
  • 虚幻引擎UE编辑器卡顿问题

    使用虚幻引擎编辑器编辑蓝图页面卡顿,视角无法转动,只能放大缩小,甚至事件图表都无法拖动。 显卡驱动安装与N卡适配的studio版本。 在NVIDIA显卡控制面板3D设置,程序设置找到UE,设置UE OpenGL渲染为GPU。 设置里找到图形设置,添加UE,设置图形首选项为高性能。

    2024年02月15日
    浏览(87)
  • 虚幻UE 材质-材质编辑器节点 1

    之前的几篇文章基本上都是对一些材质名词进行讲解 而这篇文章会对材质编辑器中的常用节点和常用用法进行讲解 材质的大致用法我们在之前的文章也讲解的差不多,从这篇文章开始我们会对一些材质编辑器中的节点进行讲解, 并把常用的方法展示出来,供大家参考学习。

    2024年01月23日
    浏览(60)
  • UE5引擎编辑器插件开发归档

    下面是自己在学习编辑器界面开发学习内容的总结,有错误的地方希望大家指出,谢谢~ 学习的教程为:https://www.bilibili.com/video/BV1M84y1K7m4 新添加一个编辑器的插件,修改插件的设定,\\\"Type\\\": 从 Runtime改为Editor,\\\"LoadingPhase\\\": \\\"Default\\\"改为PreDefault 此处参考文档: https://blog.csdn.net/p

    2024年02月08日
    浏览(45)
  • UE编辑器格式化xml或json

    UE编辑器格式化XML数据,首先菜单【视图】=》【查看方式】=》【XML】,然后选中需要进行格式化的内容,点击菜单【格式】=》【重新缩进选择】  UE编辑器格式化JSON数据,首先菜单【视图】=》【查看方式】=》【JSON】,然后选中需要进行格式化的内容,点击菜单【格式】=》

    2024年02月11日
    浏览(54)
  • 在UE5编辑器环境中使用Python

    UE有很多Python方案,本文所讲述的Python为UE5官方内嵌版本方案,并且只能在编辑器环境下使用,使用该功能可以编写编辑器下的辅助工具,提升开发效率。 讲一讲UE5中调用Python的几种方式,首先是控制台下调用Python,将控制台的CMD图标换成Python,即可输入并执行Python语句:

    2024年02月11日
    浏览(49)
  • UE中创建异步任务编辑器工具(Editor Utility Tasks)

    在UE中我们往往需要执行一些编辑器下的异步任务,例如批量生成AO贴图、批量合并静态模型等,又不想阻碍主线程,因此可以使用Editor Utility Tasks直接创建UE编辑器下的异步任务。 如果你不太了解UE编辑器工具,可以参考这篇文章: https://blog.csdn.net/grayrail/article/details/1313097

    2024年02月10日
    浏览(95)
  • Unity 扩展自定义编辑器窗口

    在Assets文件夹路径下任意位置创建Editor文件夹,将扩展编辑器的代码放在Editor文件夹下 代码中首先引用命名空间 然后将创建的类继承自EditorWindow 然后通过扩展编辑器菜单功能调用创建窗口的方法 要注意方法中泛型参数需要传入的是自己代码的类,这个功能是根据后面OnGUI方

    2024年04月27日
    浏览(57)
  • FairyGUI编辑器自定义菜单扩展插件

    本文涉及到的软件有:FairyGUI,VSCode 代码环境涉及到了:Lua VSCode插件:EmmyLua 在编写FairyGUI编辑器菜单前,了解一下FairyGUIEditor的API会有效的帮助我们解决很多问题。FairyGUI的扩展是通过编辑器自带的插件功能实现的,插件中我使用的是lua环境模板。导入编辑器的LuaAPI,文件可

    2024年02月12日
    浏览(48)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包