UE5项目学习
参考课程:Unreal Engine 5 C++ The Ultimate Game Developer Course
UE版本:UE5.4.4
Getting Started
视频中称默认的StarterContent的Minimal_Default Map并不能保存修改,所以想要在此基础上进行修改,需要将其保存为一个新的Level。这作为StarterContent工具包的内容应该是容易理解的,我们并不应该也并不能去改变StarterContent中的内容,但经过实操却发现其实可以修改。
然后可以在Project Setting中的Maps & Modes设置 Editor启动的默认地图,这里存在路径名称似乎不统一的问题,即设置中显示路径为/Games/Maps,然后内容浏览器里我们是从/Content/Maps里访问的。
active level editor viewport
UE中按住鼠标左键,随着鼠标前进和后退,视角在视口中前进和后退,随着鼠标转动,视角随之转动,但并不会改变高度。按住鼠标右键,则随着鼠标移动可以抬头低头,左转右转,但并不会移动
随着地图增大,想要改变移动的速度,可以更改Camera Speed。
我们通过Level Editor中的Show查看相应展示的物体信息。
例如Minimal_Default中所见到的桌椅、地板、雕像都是Static Mesh。然后我们可以通过Show中的Collision看到物体的碰撞体积?似乎称之为Collision Volume,UE通过Collision Volume来计算判断物体之间的碰撞。
除此之外还有Lit展示了实际游戏中的照明效果。
Bookmark为相应记录的摄像机视角
GameView则会显示通常游玩游戏时的情况,显示可见的物体。
UE中通过Translation Mode选中物体后,其出现的坐标系统红色轴代表x轴,绿色轴代表y轴,蓝色轴代表z轴
UE中对物体的操作共有简单的选中、移动、旋转、缩放。
同时移动、旋转、缩放都有相应的Snap使之离散地变化,也可以取消这些Snap
也可以是否是世界坐标系或者物体自身的坐标系
移动、旋转、缩放都可以按住Alt键进行复制操作。
在Outliner中选中物体并且按F可以转到看到相应物体的视角
Realistic Landscapes
Quixel Bridge插件需要事先安装到引擎中,它可以在编辑器中访问 Megascans 库的所有功能,Megascans是由 Quixel 提供的一个高质量的 3D 资产库,通过高精度的扫描技术从现实世界中获取的,确保了其逼真的细节和高质量。
注意现在Megascans实际上迁移到了FAB, 所以实际上其实现在更确切地是下载FAB插件,并且在FAB商城中寻找资产,可以轻松点击Add to Project添加到项目中。
注意在Content Browser中,Material由绿线标注,Texure由红线标注,Static Mesh由蓝线标注
移动物体时按住Shift可以让视角跟随移动
传统构造开放世界的方式是随着角色的位置加载相应的地图,一次只有一张地图,而UE引擎应该是通过将地图切割成一个个的部分,然后根据角色的位置加载相应的部分。这叫做World Partitioning,使得我们无需设置多个地图并手动load an unload
然后我们创建一个Empty Open World
对于世界的Lighting和Atmosphere,我们总共会有如下要素
- Sky Atmosphere
- Directional Light
- Sky Light
- Exponential Height Fog
- Volumetric Clouds
他们共同组成了一片天空
Mobility:
- Static:在游戏中该光线不会发生变化,性能最快,允许baked lighting
- Stationary:光的颜色和强度可以在游戏中发生变化
- Movable:光可以在游戏中移动和改变,动态阴影的计算
Sky Light:
从场景较远处进行采样捕捉,模拟光照与反射效果,应该是用于全局光照,也可以在特定情况下进行捕捉,如静态光源只在构建光源时进行捕捉,而Stationary光源或者Movable光源则会在场景或关卡载入时进行一次捕捉,当然也可以手动刷新进行更新。除此之外,还可以让系统实时捕捉更新。
Exponential Height Fog(指数高度雾):
用于模拟高度雾效,根据高度计算雾的浓度和颜色,越靠近地面或低处,雾就越浓,体现出一种基于高度的指数衰减/增强。常会设置两种颜色以表现星球面对太阳与背对太阳的区别:面向太阳更亮,背对太阳更暗
Volumetric Clouds(体积云):
- 云层是动态的,可以随着时间或天气系统的变化而改变形状和位置。
- 云是三维体积渲染,而非简单的平面贴图,能够在视觉上表现出真实的云体积和层次感。
- 云的外观由材质(Material)驱动,通过调整材质参数来控制云的形状、密度、光照等。
- 支持光线散射,云层能对环境光或太阳光产生散射、遮蔽等效果,使其在日出、日落或不同天气中呈现逼真的光照变化。
Landscape:实际上
- 用于创建和管理地形和植被的系统。
- 支持多种地形类型,如平坦、山脉、河流等。
- 可以通过调整地形的高度、粗糙度、纹理等参数来模拟真实的地形效果。
Landscape注意子单元加载情况
End键快速对齐地面
Shift+2唤出Landscae的Sculpt
创建Material,命名规范M打头,配上下划线,然后是相应的名字,例如M_Stone
Material中有Base Color、Metallic、Roughness、Normal等参数,可以通过这些参数来控制材质的外观
想要使用多种Material给我们的Landscape,就要利用好layers,即创建Layer Blend,然后利用下载的Material中的相应的Texture,拖拽并与相应的layer关联
注意LayerBlend的名称中不可以有空格,注意选择BaseColor
材质实例(Material Instance)双击可进行修改,通过调整“Parameter Groups”选项中的参数更改材质 (可能不规范)
绘制时可以调整画笔尺寸(Brush Size)、衰减(Brush Falloff)和工具强度(Tool Strength)来改变绘制效果
Brush Size:决定绘制的区域大小
Brush Falloff:决定了画笔效果从中心到边缘的过渡平滑程度。值越高则边缘越柔和
Tool Strength:决定了画笔每次应用效果的强度
UE中的“Foliage”模式专门用于在场景中绘制大量重复资产,如树木、草、花朵等。
解释了植被工具的核心优势在于使用“实例”。实例允许多个相同的网格(Mesh)共享数据(如纹理、模型),只存储它们各自的位置、旋转和缩放信息。这极大地提高了渲染效率。
可以通过从内容浏览器拖拽或点击“+ Foliage”并搜索来添加植被资产,Static Mesh
画笔尺寸(Brush Size): 控制绘制区域的大小。
绘制密度(Paint Density): 控制绘制区域中的植被数量
可以通过按住 Shift 键切换到Erase模式擦除
可以通过勾选或取消勾选植被列表中的复选框来决定当前要绘制哪些植被类型。
风摆效果在材质实例中相应可以调整
启用了风摆的植被不能使用 Nanite。Nanite 是虚幻引擎 5 的核心技术,用于高效渲染极高多边形几何体,但它不适用于移动的物体。
大量植被实例会导致帧率显著下降。
项目设置(Project Settings)中的渲染优化:
- 阴影贴图方法(Shadow Map Method): 将 Virtual Shadow Maps(虚拟阴影贴图)切换为更传统的 Shadow Maps(普通阴影贴图),因为虚拟阴影贴图开销较大。
- 抗锯齿方法(Anti-aliasing Method): 将 Temporal Super Resolution(时间超分辨率,TSR,虚幻引擎 5 的高级抗锯齿技术)切换为 Temporal Anti-Aliasing(时间抗锯齿,TAA),因为 TSR 同样开销较大。
纹理流送池超预算(Texture Streaming Pool Over Budget): 当场景中纹理过多,超出分配的内存时,会显示此警告。这通常不会导致崩溃,但引擎会自动降低纹理分辨率以适应内存限制。
默认情况下,通过 Foliage 工具放置的实例植被是没有碰撞的,玩家可以直接穿过树木。
通过FoliageType设置Collision Presets,例如BlockAll,设置后玩家将无法穿过这些植被
后期处理体积(Post Process Volume):允许在场景渲染完成后,应用各种图像效果,改变场景的整体视觉风格。
Post Process Volume是一个Actor,默认效果只在摄像机进入体积内部时生效。
在细节面板中勾选 Infinite Extent Unbound,使后期处理体积的效果应用于整个关卡,无论摄像机在哪里。
Post Process Volume的常见参数如下:
温度(Temperature):调整场景的冷暖色调。
泛光(Bloom):控制场景中高光区域(如光源、高反射表面)周围的“光晕”效果。亮度
颜色调整等
Level Instance:允许将多个 Actor 组织在一个单独的子关卡中。这个子关卡可以在主关卡中被实例化(重复使用),并且只有在需要时才被流送(Stream)进出,从而提高性能。
Packed Level Actor (打包关卡 Actor) 的概念: 这是 Level Instance 的一种特殊形式,它将一个 Level Instance 打包成一个单独的 Actor。这意味着你可以像操作任何其他 Actor 一样,在场景中轻松复制、移动、旋转整个 Level Instance 的集合。
默认情况下,虚幻引擎中的每个纹理采样器会占用一个独立的采样器槽位,而这些槽位数量是有限的。当采样器数量超出限制时,地形可能会显示为灰色的默认材质。
解决需要在地形材质 (M_Landscape) 中,选中每个 Texture Sample 节点。在细节面板中,将 Sampler Source 从 From Texture Asset 改为 Shared Wrap
Level Instance:
向量,旋转与三角学(Vectors, Rotators, and Trigonometry)
The Actor Class
ProjectName_API宏:
- 当你在当前模块(即定义该宏的模块)中编写代码时,_API 宏会被替换为 dllexport,告诉编译器将这个类或函数导出到动态链接库(DLL)中。
- 当你在其他模块(即使用该宏的模块)中编写代码时,_API 宏会被替换为 dllimport,告诉编译器从 DLL 中导入这个类或函数。
它的作用就是让模块间的代码互相可见和调用。你创建一个新的C++类时,虚幻引擎会自动将这个宏添加到类声明中,以确保它能够被正确地导出和导入。
GENERATED_BODY() 是虚幻引擎(Unreal Engine)C++ 特有的一个宏,它的主要作用是在编译时被 Unreal Build Tool (UBT) 和 Unreal Header Tool (UHT) 替换为大量自动生成的代码。这些代码赋予了你的 C++ 类与虚幻引擎核心功能进行交互的能力,例如反射系统集成,序列化,蓝图集成,垃圾回收等。
设置一个 Actor 每帧都调用 Tick() 函数:
PrimaryActorTick.bCanEverTick = true;
PrimaryActorTick: 这是一个结构体变量,其类型为 FActorTickFunction,如果不需要 Actor 每帧都执行某些功能,可以将此值设置为 false,以提升性能。
可以通过C++类派生出相应的蓝图类
Actor类派生出的蓝图具有Construction Script (构建脚本)和Event Graph (事件图表)
Construction Script:在游戏开始前执行,每当你在编辑器中对蓝图的属性进行更改时,Construction Script 就会被触发。
Event Graph:其中的节点在游戏运行时执行。包含多种事件节点,这些节点会在特定的游戏事件发生时被触发。用于实现游戏中的动态逻辑,例如处理输入、进行物理模拟、响应碰撞等。
蓝图中的Event节点实际是通过C++中的“委托”Delegate实现的。在 C++ 中,委托是一种类型安全的函数指针,可以用来引用一个或多个函数,从而在某个事件发生时调用这些函数。
代码打印到Log:
UE_LOG:这是虚幻引擎特有的一个宏,用于将消息打印到输出日志窗口
UE_LOG(LogTemp, Warning, TEXT(“BeginPlay is called!”));
代码打印到屏幕上:
GEngine->AddOnScreenDebugMessage()
GEngine:一个全局指针变量,指向虚幻引擎实例。在使用前必须进行空指针检查,以避免崩溃。
参数有Key, TimeToDisplay(float), DisplayColor(FColor), Text(FString)
蓝图如下:
Print String: 将文本消息打印到屏幕左上角和输出日志。
Key (键):一个可选的命名参数。如果多个 Print String 节点使用相同的 Key,新消息会替换旧消息,防止屏幕消息堆积。这在 Event Tick 这种每帧调用的事件中尤其有用。
Duration (持续时间):消息在屏幕上显示的时间。
Text Color (文本颜色):消息的颜色
Log String: 将文本消息打印到输出日志。
Live Coding 是 UE5 提供的一套热重载(Hot Reload)机制,允许在编辑器或游戏运行时编译并加载修改后的 C++ 代码,更快地迭代调试逻辑,而无需完全停止、重新编译并重启整个项目。
Live Coding 通过动态打补丁的方式向正在运行的原生代码注入改动,这种做法本质上并不可靠;它只对函数体内部的修改能够相对稳定地生效,一旦涉及头文件(header)或经过反射(reflected)类型的改动,就会触发引擎对相关对象的“重实例化”(re-instancing),而这一过程经常会破坏 Blueprint 数据。
为避免此类问题,最好在关闭编辑器的情况下,通过 IDE 进行 C++ 类的创建与编译,并且无须每次都重新生成项目文件。 可在编辑器设置中关闭“Enable Re-instancing”以减少风险。
C++开发时,建议关闭 Unreal 编辑器,在 IDE 中编辑、编译 C++,整个过程更接近常规软件开发流程,更加稳定。
在 Visual Studio 安装器中启用 “Unreal Engine Support” 插件,可直接在 IDE 中创建 UE 类,无需打开编辑器 添加新类后无需手动“Regenerate Project Files”;只有项目文件损坏或缓存异常时才需要重建。
添加新类或普通编译不必每次都“Regenerate Project Files”。只有当项目缓存或文件损坏时,才需要删除中间文件并重新生成项目文件。
UE的log打印可以通过%f打印float类型的变量,实际上就是使用C++的格式化字符串参数
UE_LOG(LogTemp, Warning, TEXT(“Delta Time: %f”), DeltaTime);
FString::Printf 允许你将格式化的字符串与多个动态参数结合在一起,生成一个新的字符串。
例如:
float DeltaTime = 0.016f;
FString FormattedString = FString::Printf(TEXT(“Delta Time: %.2f”), DeltaTime);
GetName 方法是 UObject 类的成员函数,用于获取该对象的名称。每个 Unreal Engine 对象都有一个内部名称,用于在引擎中进行标识和管理,GetName 就是用来获取这个名称的。
同时注意使用%s的时候,我们要传递的实际上是C Style的字符串,所以实际传递类型为const TCHAR*,而FString类对“”进行了重载,使之功能为将FString转换为了一个C风格的字符串。即 const TCHAR 类型的字符数组指针。
Debug Shapes,调试形状
Sphere调试球体绘制:帮助开发者可视化不可见的对象。
一种通过蓝图方式,在BeginPlay中调用"Draw Debug Sphere",参数如下:
- 位置Center:确定球体的中心位置,通常是对象的世界位置(通过 Get Actor Location 获取)。
- 半径Radius:球体的大小。
- 段数Segments:球体的细分程度,决定其圆滑程度。
- 颜色Line Color:球体的颜色,可以通过线性颜色设置。
- 持续时间Duration:球体显示的时长,通常用秒为单位。
- 线条厚度Thickness:控制球体线条的粗细。
注意Center部分,在蓝图中可通过Get Actor Location传递
一种通过C++方式,引入头文件"DrawDebugHelpers.h"
然后调用方法DrawDebugSphere(const UWorld* InWorld, const FVector& Center, float Radius, int32 Segments, const FColor& Color, bool bPersistentLines, float LifeTime, uint8 DepthPriority, float Thickness)
UWorld* World:绘制调试球体的世界环境,通常通过 GetWorld() 获取。
const FVector& Center:球体的中心位置,通常是对象的位置坐标。
float Radius:球体的半径,决定球体的大小。
int32 Segments:球体的细分数,决定球体的平滑度。越高,球体越圆滑。
FColor Color:球体的颜色。你可以指定 FColor 类型的颜色,或者使用内置的颜色(如 FColor::Red)。
bool bPersistentLines:是否保持线条绘制。如果为 true,球体会持续存在,直到被手动移除或游戏结束。如果为 false,球体将在指定的持续时间后自动消失。
float LifeTime:球体的持续时间,单位是秒。如果 bPersistentLines 为 false,则球体会在 LifeTime 秒后消失。
uint8 DepthPriority:控制绘制优先级,较高的值会使得球体覆盖其他绘制的调试图形。
float Thickness:球体线条的厚度,控制调试球体的外边框线条的粗细。默认值是 0.0f,即线条较细。你可以通过增加这个值来改变线条的厚度。
调用 DrawDebugSphere 函数并传递必要的参数:
使用 GetWorld() 获取当前世界。
使用 GetActorLocation() 获取物体位置。
设置球体的半径、段数、颜色等参数。
需要检查世界指针是否为空,确保世界有效后才能绘制调试球体。
使用案例:
UWorld* World = GetWorld();
if (World)
{
FVector Location = GetActorLocation();
DrawDebugSphere(World, GetActorLocation(), 50.0f, 12, FColor::Red, true, 10.0f);
}
为了减少重复代码,可以定义宏来简化调试球体的绘制, 如替换上述使用案例
同理Line
蓝图为Draw Debug Line
通过蓝图中的 Draw Debug Line 节点,可以绘制一条从物体当前位置到另一个位置的线。需要提供:
起始位置Line Start:通常是物体的位置,可以通过 Get Actor Location 获取。
结束位置Line End:通常是物体的前进方向,可以通过 Get Actor Forward Vector 和 Get Actor Location 通过 Add 节点结合,可以根据需要缩放Get Actor Forward Vector。
其他参数包括:线条颜色Line Color、持续时间Duration、线条厚度Thickness等。
在 C++ 中,则使用 DrawDebugLine 函数绘制调试线。首先需要通过 GetWorld() 获取当前世界环境指针,确保能够正确绘制。
主要还是起始位置和结束位置,同样通过GetActorLocation获取物体位置,通过GetActorForwardVector获取前进向量。
使用案例:
UWorld* World = GetWorld();
if (World)
{
FVector Location = GetActorLocation();
FVector Forward = GetActorForwardVector();
DrawDebugLine(World, Location, Location + Forward * 100.0f, FColor::Red, true, -1.f, 0, 1.0f);
}
Color及之后的参数与Sphere相同
同样可以定义宏简化
同理Point
蓝图Draw Debug Point,调试点设置 位置、大小、颜色和持续时间
C++中,则使用DrawDebugPoint 函数绘制调试点,同样Color之后的参数相同,没有Thickness
UWorld* World = GetWorld();
if (World)
{
DrawDebugPoint(World, Location + Forward * 100.0f, 15.f, FColor::Red, true);
}
我们可以定义 DRAW_VECTOR 宏,通过传入起始和结束位置,轻松地同时绘制 调试线 和 调试点,从而可视化整个向量的起始和结束位置。
Moving Object With Code
蓝图调用会在C++调用之前发生。
我们通过SetActorLocation来设置物体位置。
在蓝图中,使用 Set Actor Location 节点,通过指定新的 X, Y, Z 坐标,可以将演员移动到指定位置。
在C++中 使用 SetActorLocation 方法,其参数:
除了目标位置,SetActorLocation 还包括一些可选参数:
bSweep:控制是否启用 “sweeping” 功能,防止演员穿透其他对象。如果启用,演员会停止在碰撞物体之前。
FHitResult 和 ETeleportType:与 “sweeping” 相关的参数,通常不需要手动传递,默认值即可
我们通过SetActorRotation来设置物体旋转。
在 蓝图 中,使用 Set Actor Rotation 节点来设置 Actor 的旋转。旋转值是通过 FRotator 类型指定的,FRotator 包含 Roll (X)、Pitch (Y) 和 Yaw (Z) 三个分量。正值为顺时针,负值为逆时针。
Teleport Physics 是一个布尔值选项,控制是否在旋转时保持物理效果。如果启用该选项,物理状态(如速度和碰撞)不会受到影响
当我们编写代码,在Tick让Actor移动时,如果仅仅使用AddActorWorldOffset,那么由于1s中每个机器的帧数不同,那么每个机器1s中移动的距离也不同,这不是我们希望看到的。解决方案为使用Delta Time,即移动距离为MoveRate速度*DeltaTime时间
在 C++ 中,我们可以使用 UProperty 宏暴露类的成员变量到 Blueprint,以便在编辑器中进行修改。
UProperty暴露变量方式有如下(UProperty(EditDefaultOnly)):
EditDefaultsOnly:允许在 默认蓝图 中修改变量,但不能在 实例 中修改。
EditInstanceOnly:允许在 实例 中修改变量,但不能在 默认蓝图 中修改。
EditAnywhere:允许在 默认蓝图 和 实例 中都修改变量,提供最大的灵活性。
VisibleDefaultsOnly:只在默认蓝图中可见,不可修改
VisibleInstanceOnly:只在实例中可见,不可修改
VisibleAnywhere:在 默认蓝图 和 实例 中都可见,不可修改
默认蓝图的属性设置影响所有基于该蓝图的实例。
通过在实例中修改属性,能够覆盖默认蓝图中的设置
Event Graph 是蓝图中用于定义事件响应和逻辑的可视化界面。它允许开发者通过将节点连接起来,来创建复杂的事件处理逻辑。
同样在事件图中暴露变量的方式如下,在UPROPERTY中进行添加:
BlueprintReadOnly:只能读取,不能修改
BlueprintReadWrite:既能读取也能修改
注意Read/Write在事件图中对应Get/Set,然后使用BlueprintReadOnly不能使用在private变量,除非在UProperty中使用meta = (AllowPrivateAccess=“true”)
UProperty中通过Category specifier将多个变量组织成不同的类别,例如UProperty(Category=“Movement”)
除了暴露变量外,还可以将函数暴露到Blueprint,通过使用UFunction宏,能将C++中的函数暴露到Blueprint中。其Specifier说明符如下:
BlueprintCallable:即在Blueprint中存在输入和输出引脚
BlueprintPure:仅在Blueprint中存在输出引脚,即只提供值
模板函数:允许函数接受不同类型的参数,增强函数的灵活性。通过template关键字声明,使用T作为类型参数(可根据需求使用其他名称)。
inline关键字:它告诉编译器在调用函数时直接将函数的代码插入到调用点,以减少函数调用的开销。
如果模板函数定义在源文件中,而多个源文件引用它,会导致链接错误。通过在头文件中定义模板并用inline修饰,可以避免编译器多次生成相同函数的定义,从而避免链接时的重复定义问题。
Component组件是赋予Actor功能的一种方式。一个Actor(如Weapon)可以拥有多个组件,如网格(Mesh)组件和碰撞检测组件(Box Collider)。
这些组件使得演员不仅在游戏中可视化,还能与其他物体进行互动
场景组件(Scene Component)是最基本的组件类型,它包含了位置、旋转和缩放等信息
蓝图中每个Actor都具有Default Scene Root,在C++中为RootComponent,类型为USceneComponent
Scene Component能被附加attach到其他Component中,
组件的附加:组件之间可以形成父子关系。一个组件可以被附加到另一个组件上,这样它将与父组件保持相对位置。
UStaticMeshComponent继承USceneComponent,额外有Mesh(UStaticMesh)
可以将默认的场景根替换为其他组件(如静态网格组件)。替换后,这个新的组件将作为根组件
当我们C++创建Class时,会自动创建Class Default Object(CDO),用于反射系统