UE5-C++入门教程(二)---编写Editor类别的自定义模型实现小球规划路线的可视化

前言


1.基本概念:模板和插件

  • *Modules(模块): 在Unreal Engine中,模块是引擎功能的基本构建块。每个模块封装了一组相关的功能,如渲染、物理、动画、音频等。模块可以是系统级别的,也可以是项目级别的。系统级别的模块通常是引擎的一部分,而项目级别的模块则专门为特定项目定制。

    • 模块可以包含C++源代码、头文件、蓝图文件、配置文件、资源文件等。开发者可以通过添加、修改或删除模块来定制Unreal Engine的功能。模块的这种结构允许开发者只包含他们项目所需的组件,从而优化性能和资源使用。
    • 上次教程我们创建的UE5Turtorial项目就是一个Module
  • *Plugins(插件): 插件是Unreal Engine中的一个特殊类型的模块,它允许开发者在不修改引擎源代码的情况下添加或扩展功能。插件可以是Epic Games提供的,也可以是第三方开发者创建的。插件通常包含一个或多个模块,它们被打包在一起,可以轻松地在不同的项目之间共享。

    • 插件可以提供从视觉效果到游戏逻辑的各种功能。开发者可以通过Unreal Engine的插件市场下载和安装插件,也可以创建自己的插件并将其分享给其他人。

2.创建自定义模块

2-1 为项目介绍模块--修改.uproject
  • 由于反复进退ue5并且进行重复编译调试连接很麻烦,这里提出一个书写自定义模块的方式,下面我们来不借助编译器和UE5,手动为项目添加一个模块

  • 这里我们随便使用一个编辑器打开你的项目根目录,这里我使用的是VScode

  • 找到项目工程文件夹Source文件夹下你的项目模块,找到你项目的.uproject文件

  • 找到模组

    • Type用于指定模型的类型,这里介绍两种今天我们会用上的

      • Runtime Module运行时模块是在游戏运行时加载的模块,它们包含游戏逻辑、游戏模式、关卡脚本等。这些模块在游戏启动时加载,并在游戏运行期间保持活动状态。
      • Editor Module:编辑器模块是专门为Unreal Engine编辑器设计的模块,它们包含编辑器工具、自定义资产类型、编辑器扩展等。这些模块只在编辑器中加载和使用。
    • LoadingPhase定义了模块加载的时机。

      • Default:这是默认的加载阶段,模块在引擎初始化后加载。
      • PreLoadingScreen:模块在加载屏幕显示之前加载,通常用于初始化需要快速完成的任务。
      • PostEngineInit:模块在引擎初始化完成后加载,通常用于执行需要引擎完全初始化后才能进行的任务。
json 复制代码
"Modules": [
	{
		"Name": "UE5Tutorial",
		"Type": "Runtime",
		"LoadingPhase": "Default",
		"AdditionalDependencies": [
			"Engine"
		]
	}
],
  • 创建一个自定义模组,设定为Editor
json 复制代码
{
	"Name": "UE5TutorialEditor",
	"Type": "Editor",
	"LoadingPhase": "PostEngineInit"
}

2-2 为模块添加到编译组中
  • 然后找到Source目录下的.Target.cs文件,添加我们新的模块"UE5TutorialEditor.Target.cs"
cs 复制代码
// Copyright Epic Games, Inc. All Rights Reserved.

using UnrealBuildTool;
using System.Collections.Generic;

public class UE5TutorialEditorTarget : TargetRules
{
	public UE5TutorialEditorTarget( TargetInfo Target) : base(Target)
	{
		Type = TargetType.Editor;
		DefaultBuildSettings = BuildSettingsVersion.V5;
		IncludeOrderVersion = EngineIncludeOrderVersion.Unreal5_4;
		ExtraModuleNames.Add("UE5Tutorial");
	}
}
  • 添加我们的新模块
cs 复制代码
//ExtraModuleNames.Add("UE5Tutorial");
ExtraModuleNames.AddRange(new string[] { "UE5Tutorial", "UE5TutorialEditor" });

  • 紧接着我们为新的模块创建一个新的文件夹,注意需要和模块名同名UE5TutorialEditor,并把我们原本项目文件夹下的UE5Tutorial.Build.cs拷贝一份到新的自定义模块的文件夹下,并改名为UE5TutorialEditor.Build.cs

  • 然后我们打开UE5TutorialEditor.Build.cs,把里头对应的类名修改,并将"UnrealEd","UE5Tutorial"添加至依赖中

    • "UnrealEd" 模块指的是Unreal Engine的编辑器模块。
    • "UE5Tutorial"模块是我们上一节写的移动小球的模块
cs 复制代码
// Copyright Epic Games, Inc. All Rights Reserved.

using UnrealBuildTool;

public class UE5TutorialEditor : ModuleRules
{
	public UE5TutorialEditor(ReadOnlyTargetRules Target) : base(Target)
	{
		PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
	
		PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "EnhancedInput","UnrealEd","UE5Tutorial"});

		PrivateDependencyModuleNames.AddRange(new string[] {  });

		// Uncomment if you are using Slate UI
		// PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" });
		
		// Uncomment if you are using online features
		// PrivateDependencyModuleNames.Add("OnlineSubsystem");

		// To include OnlineSubsystemSteam, add it to the plugins section in your uproject file with the Enabled attribute set to true
	}
}

2-3 实现模块类--继承自IModuleInterface
  • IModuleInterface 是一个接口,它定义了一个模块与其他模块或系统交互的标准方式。这个接口允许模块提供一组功能,这些功能可以在模块被加载后由其他模块调用,而不需要直接引用模块的内部实现。

  • IModuleInterface 的主要目的是为了实现模块之间的解耦,使得模块可以独立地开发、测试和更新,同时仍然能够与其他模块进行交互。通过实现 IModuleInterface,一个模块可以公开一组函数,这些函数可以被其他模块在需要时调用。

  • 我们创建如下两个文件

  • UE5TutorialEditor.hpp

cpp 复制代码
#pragma once

#include "Engine.h"
#include "Modules/ModuleInterface.h"
#include "Modules/ModuleManager.h"
#include "UnrealEd.h"

DECLARE_LOG_CATEGORY_EXTERN(UE5TutorialEditor,All,All)

class FUE5TutorialEditorModule:public IModuleInterface
{
//public:
	//virtual void StartupModule()override;
	//virtual void ShutdownModule()override;
};
  • UE5TutorialEditor.cpp
cpp 复制代码
#include "UE5TutorialEditor.h"
#include "Modules/ModuleInterface.h"
#include "Modules/ModuleManager.h"

#include "UnrealEd.h"


IMPLEMENT_GAME_MODULE(FUE5TutorialEditorModule, UE5TutorialEditor);

2-4 编译
  • 回到VS2022,点击绿色透明箭头,开始构建项目,我们会发现代码新的模块的库被创建

  • 在新开的UE中刷新VS2022

  • VS2022就有新的模块被添加进来了

  • 如果有报错,请检查代码拼写,不要漏掉了;或者拼错了函数


2-5 检查

  • 在菜单栏的工具找到调试选择模块

  • 可以看到我们的模块已经被添加进来


3.创建自定义可视化UI的ComponentVisualizer

  • 工具里我们新建一个C++类(具体方式上一节我们讲过,这里快速略过)
3-1 创建可视化类
  • 我们创建一个Empty的FMoveComponentVisualizer类,继承自FComponentVisualizer

    • FComponentVisualizer 是 Unreal Engine (UE) 中的一个类,它用于在 UE 编辑器中可视化场景中的组件。组件(Components)是 UE 中的一个核心概念,代表游戏对象(Actors)上的各种功能,如静态网格、骨骼网格、相机、灯光等。
    • FComponentVisualizer 类用于在编辑器中绘制组件的可视表示,这通常包括组件的边界框、碰撞体、骨骼等。开发人员可以使用这个类来创建自定义的可视化效果,以便更好地理解组件在场景中的位置和方向。
  • 这里我们实现基类的虚函数DrawVisualization

    • const UActorComponent* Component: 指向要绘制的组件的指针。这是一个常量指针,意味着你不会在这个函数中修改组件的状态。
    • const FSceneView* View: 指向当前场景视图的指针。场景视图包含了渲染场景所需的所有信息,如相机设置、视口大小等。这也是一个常量指针。
    • FPrimitiveDrawInterface* PDI: 指向用于绘制原语(primitives)的接口的指针。原语可以是线、点、三角形等,用于在 3D 空间中绘制形状。通过这个接口,你可以添加自定义的绘制调用到场景中。
  • FMoveComponentVisualizer.hpp

cpp 复制代码
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "ComponentVisualizer.h"
#include "UE5Tutorial/MoveComponent.h"
/**
 * 
 */
class UE5TUTORIALEDITOR_API FMoveComponentVisualizer: public FComponentVisualizer
{
public:
	void DrawVisualization(const UActorComponent* Component,const FSceneView*View,FPrimitiveDrawInterface* PDI )override;
};
  • FMoveComponentVisualizer.cpp
    • 这里我们获取UMoveComponent,如果存在,我们就绘制一条线,起点和终点分别是小球的起点和终点
cpp 复制代码
// Fill out your copyright notice in the Description page of Project Settings.


#include "MoveComponentVisualizer.h"

void FMoveComponentVisualizer::DrawVisualization(const UActorComponent* Component, const FSceneView* View, FPrimitiveDrawInterface* PDI)
{
	const UMoveComponent* MoveComponent = Cast<UMoveComponent>(Component);
	if (MoveComponent)
	{
		PDI->DrawLine(
			MoveComponent->StartRelativeLocation,
			MoveComponent->EndRelativeLocation,
			FLinearColor::Red,
			SDPG_Foreground,
			5.0f
			);
	}
}
  • 由于小球的StartRelativeLocationEndRelativeLocation属性是private的,这里我们需要为UMoveComponent添加友元FMoveComponentVisualizer,打开MoveComponent.cpp,添加友元
cpp 复制代码
private:
	friend class FMoveComponentVisualizer;
	FVector StartRelativeLocation;
	UPROPERTY(EditAnywhere)
	float speed=1.0f;
	UPROPERTY(EditAnywhere)
	FVector EndRelativeLocation;
	FVector CurrentLocation;
	float stoppingDistance = 1.0f;
3-2 注册模型编辑可视化
  • 我们需要在编辑器中注册和注销一个自定义的组件可视化器 FMoveComponentVisualizer
  • UE5TutorialEditorModule.hpp
cpp 复制代码
#pragma once

#include "Engine.h"
#include "Modules/ModuleInterface.h"
#include "Modules/ModuleManager.h"
#include "UnrealEd.h"

#include "MoveComponentVisualizer.h"
DECLARE_LOG_CATEGORY_EXTERN(UE5TutorialEditor,All,All)

class FUE5TutorialEditorModule:public IModuleInterface
{
public:
	void StartupModule()override;
	void ShutdownModule()override;
};
  • UE5TutorialEditorModule.cpp
    • 首先检查 GUnrealEd(全局编辑器指针)是否有效。
    • 如果有效,它会创建一个新的 FMoveComponentVisualizer 实例,并使用 MakeShareable 函数将其转换为共享指针。
    • 它注册这个可视化器,以便它可以在编辑器中用于 UMoveComponent 类型的组件。
cpp 复制代码
#include "UE5TutorialEditor.h"
#include "Modules/ModuleInterface.h"
#include "Modules/ModuleManager.h"

#include "UnrealEd.h"


IMPLEMENT_GAME_MODULE(FUE5TutorialEditorModule, UE5TutorialEditor);
void FUE5TutorialEditorModule::StartupModule()
{
	if (GUnrealEd)
	{
		TSharedPtr<FMoveComponentVisualizer> MoveVisualizer = MakeShareable(new FMoveComponentVisualizer);
		if (MoveVisualizer.IsValid())
		{
			GUnrealEd->RegisterComponentVisualizer(UMoveComponent::StaticClass()->GetFName(), MoveVisualizer);
			MoveVisualizer->OnRegister();
		}

	}

}
void FUE5TutorialEditorModule::ShutdownModule()
{
	if (GUnrealEd)
	{
		GUnrealEd->UnregisterComponentVisualizer(UMoveComponent::StaticClass()->GetFName());


	}

}
3-3 编译
  • 我们在调试的模块中找到我们的模块,点击重新编译

  • 点开输出日志,不出意料的报错了,根据报错提示

  • 我们关闭实时代码编译

  • 重新点击重新编译


4. 结果展示

  • 我们修改Move组件的目标位置,可以发现线也随之改变

  • 值得一提的是,整个过程中我们不需要编译整个项目,也不需要进行仿真,这为我们进行代码调试时非常方便的。

  • 此外这条调试线将在开始仿真的时候会消失,因为这是供我们编译模型下使用,后续我们会介绍如何在运行过程中可视化


小结

  • 本节我们介绍了如何自定义module,并添加了一个Editor的模型,使我们可以在不编译整个项目且不开启仿真的前提下可视化小球的规划路线。
  • 感谢大家对本教程的支持,如有错误,欢迎指出。
相关推荐
唐诺5 小时前
几种广泛使用的 C++ 编译器
c++·编译器
冷眼看人间恩怨6 小时前
【Qt笔记】QDockWidget控件详解
c++·笔记·qt·qdockwidget
红龙创客6 小时前
某狐畅游24校招-C++开发岗笔试(单选题)
开发语言·c++
Lenyiin6 小时前
第146场双周赛:统计符合条件长度为3的子数组数目、统计异或值为给定值的路径数目、判断网格图能否被切割成块、唯一中间众数子序列 Ⅰ
c++·算法·leetcode·周赛·lenyiin
yuanbenshidiaos7 小时前
c++---------数据类型
java·jvm·c++
十年一梦实验室8 小时前
【C++】sophus : sim_details.hpp 实现了矩阵函数 W、其导数,以及其逆 (十七)
开发语言·c++·线性代数·矩阵
taoyong0018 小时前
代码随想录算法训练营第十一天-239.滑动窗口最大值
c++·算法
这是我588 小时前
C++打小怪游戏
c++·其他·游戏·visual studio·小怪·大型·怪物
fpcc8 小时前
跟我学c++中级篇——C++中的缓存利用
c++·缓存
呆萌很8 小时前
C++ 集合 list 使用
c++