前言
- 本教程将以图文教程的形式讲述如何快速入门通过C++使用UE5.4进行项目编写。
- UE5的教程系列
- UE5与ROS2实战->基于UE5和ROS2的激光雷达+深度RGBD相机小车的仿真指南(一)—UnrealCV获取深度+分割图像-CSDN博客
- 本系列教程默认读者有C++使用基础,故不对一些C++基础知识进行详细讲解
- 本系列教程使用的是最新的
UE5.4.3
和VS2022
作为使用实例 - 关于本节:上一节我们介绍了如何创建Actor及其Component,并使用自定义MoveComponent完成了制定目标小球的追踪和速度设置,由于每次运行我们都需要开始仿真且需要反复进行编译整个项目,这往往是非常浪费时间和资源的,本节我们引入模型和Editor的概念,在不需要启用仿真和编译整个项目的前提下,我们完成在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
:模块在引擎初始化完成后加载,通常用于执行需要引擎完全初始化后才能进行的任务。
"Modules": [
{
"Name": "UE5Tutorial",
"Type": "Runtime",
"LoadingPhase": "Default",
"AdditionalDependencies": [
"Engine"
]
}
],
- 创建一个自定义模组,设定为
Editor
{
"Name": "UE5TutorialEditor",
"Type": "Editor",
"LoadingPhase": "PostEngineInit"
}
2-2 为模块添加到编译组中
- 然后找到
Source
目录下的.Target.cs
文件,添加我们新的模块"UE5TutorialEditor.Target.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");
}
}
- 添加我们的新模块
//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"
模块是我们上一节写的移动小球的模块
// 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
#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
#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
// 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
,如果存在,我们就绘制一条线,起点和终点分别是小球的起点和终点
- 这里我们获取
// 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
);
}
}
- 由于小球的
StartRelativeLocation
和EndRelativeLocation
属性是private
的,这里我们需要为UMoveComponent
添加友元FMoveComponentVisualizer
,打开MoveComponent.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
#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
类型的组件。
- 首先检查
#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的模型,使我们可以在不编译整个项目且不开启仿真的前提下可视化小球的规划路线。
- 感谢大家对本教程的支持,如有错误,欢迎指出。