# 前言

前几天 Unity 公布了新的 收费政策,然后整个开发者社区都沸腾了,处处都是 讨论的声音,不管最后实际生效政策如何 (2023.9.18 官方似乎更新了公告,说会对『运行费』政策进行修改),这种杀鸡取卵的行为必然都会造成以后开发选择的考虑点。

之前本来还打算专一 Unity 的,比如前些日子都还一直在研究 HDRP,算是基本把 HDRP 物理光照、各种调参熟悉得七七八八了,结果就听到这么个消息... 简直是...
如果真的实施,人家独立开发只怕都会把这个作为一个很大比例权重考虑进去了,毕竟哪个游戏开发者没有想做个好玩 (爆款) 的大作的梦想呢?

之前一直都关注 UE,比如 UE 商城每月一次的各种免费资源都嫖了好久:

要是没有使用之地也太可惜了。

加上想到到学 UE 顺便还能深入下 C++ 和真正的游戏引擎代码 (Unity 作为一个黑盒引擎,有时候用起来确实感觉不是自己的)

之前本来还打算 C# 一条道走到黑的,现在考虑了良久,觉得至少 C/C++ 也要深入一下才行,UE 也可以算是个入口了
而且 UE 内置工具链比 Unity 完善很多,Unity 扩展几乎全靠插件... 怎么感觉 UE 才适合个人玩?毕竟很多东西给提供了,不用个人造轮子。
几年前使用 UE4 的时候,感觉很难搞下去还有一个重要原因是:UE 的 C++ 操作起来实在太慢了!最近重新试了下,现在代码跳转提示的速度算是可以接受了。

这两天对 UE 的基础知识进行了一轮学习,先记录一部分笔记,后续再做补充。

# 术语

# Gameplay

  • 玩法:游戏的规则和状态
  • 提供能力的是引擎,提供内容的是 Gameplay
  • Unreal Gameplay 框架:游戏规则、角色、控制、相机、用户界面和 HUD
    • GameMode:不同的游戏规则
    • GameState:存储当前的游戏状态
    • PlayerState:存储玩家状态信息
    • Character:角色
    • Camera:相机
    • Controller:控制器
    • 3C:Character、Control 和 Camera

GameFeatures:UE5 推出的一个支持动态改变游戏玩法的框架

  • 类似动态插件
  • 一种模块化的逻辑组织方式,GameFeatures 允许在 “游戏功能” 这个颗粒度上进行解耦

UGameInstance:游戏单例,全局唯一

  • 引擎的初始化加载
  • 还可以包含 UGameInstanceSubsystem

GEngine:全局变量

  • 也可以包含 UEngineSubsystem

坐标轴

  • X、Y、Z
  • Row、Pitch、Yaw

服务器

  • 异构服务器:客户端和服务器的执行逻辑完全不一样,服务器和客户端分别各自实现
  • 同构服务器:服务器和客户端用同一套框架和代码,通过在启动时指定自己作为服务器还是客户端即可

# 关系

  • Actor 组成世界
  • Actor 由 Component 组成

# Component

  • 功能的载体,表达的是『功能』的概念,一定程度的嵌套组装能力 (SceneComponent)
  • 正确理解『功能』和『游戏业务逻辑』的区分是理解 Component 的关键要点

是实现一个个『与特定游戏无关』的功能。理想情况下,已经实现完成的 Components 是可以直接在游戏之间迁移使用
——《InsideUE4》GamePlay 架构(四)Pawn

# Actor

  • 世界中的基础对象
  • Actor 可以组装其它 Actor 或 Component
  • 概念上也可以类比 Unity 中的 Prefab

# Pawn

  • 可以响应响应外部输入、移动能力的 Actor (如可以被玩家或 AI 控制)
  • 封装了一些列对 UPawnMovementComponent 组件操作 (因此也有封装碰撞)

分类

  • DefaultPawn:默认带 UFloatingPawnMovement (移动)、USphereComponent (碰撞)、UStaticMeshComponent (显示) 三件套
  • SpectatorPawn (观众):相当于不带重力的 DefaultPawn
  • Character:人形角色使用 (Pawn 的加强特化版本)

# 结构

保管库:下载资源地址,每个引擎对应一个,可自定义路径

  • 不需要的引擎可删除

编译 (材质) 公共缓存:AppData/Local/UnrealEngine

  • 派生数据缓存 (DDC)
  • 不用引擎版本可删除
  • 还可以放服务器

# 视口

  • 快速隐藏各个 Editor 提示图标:选 GameView (快捷键 G)
  • ShowFPS:显示帧率
  • 全屏:F11
  • 移动捕捉:视口右上,网格图标设置
  • 相机自动跟随移动目标:按住 Shift + 移动
  • End 键:将对象投射到地面
  • 物体对齐当前视口:右键对象,选择 SnapObjectToView
  • 复制:Alt + 拖动
  • 以视口控制移动:Ctrl+Shift+P (或右键 actor 选择控制)
  • 运行模式退出鼠标:Shitf+F1

# 工具

BSP:做原型的,可以避免普通 cube 材质拉伸等,还支持打洞、组合等。

# 代码

UHT:反射与垃圾回收系统

  • #include "xxx.generated.h" 需要包含在最后,其它头文件在此之前

类型

  • 整型使用 int32 (UE 提供的跨平台类型)

# 类型关系

继承关系

  • UObject->
    • UActorComponent
    • AActor
      • APawn->ACharacter
      • AController->APlayerController
      • AInfo->APlayerState

命名关系

  • 派生自 Actor 带有 A 前缀,如 AController
  • 派生自 Object 带有 U 前缀,如 UComponent
  • Enums 的前缀是 E,如 EFortificationType
  • Interface 的前缀是 I,如 IAbilitySystemInterface
  • Template 的前缀是 T,如 TArray
  • 派生自 SWidget 的类 (Slate UI) 带有前缀 S,如 SButton
  • 其它类 (结构) 的前缀为字母 F,如 FVector

# 详情

# UObject

  • 存放数据、不能放置到场景

# UActorComponent

  • 可以挂载至场景 Actor 上,必须依附于 Actor 存在

# AActor

  • 能放置在场景,可以有视觉表现
  • 除了继承自 UObject 的序列化、反射、内存管理等能力之外,额外实现的是组件的组合能力,Tick 能力,网络复制能力和对生命周期的管控

# APawn

  • 可以被控制器持有

# ACharacter

  • 有角色移动组件 (CharacterMovementComponent)
  • 具有角色封装好的一系列功能

# AController

  • 可以控制 APawn 的控制器

# APlayerController

  • 角色控制器存在于整个游戏关卡中,而 Pawn 可以被破坏、重生
  • 可以控制不同的 Pawn,也可以在它们之间切换,或完全不控制任何 Pawn
  • 具有输入,不过一般用于处理与 Pawn 无关的事件 (如 UI)

# 注意

  • 如果想要一个 C++ 类可以被蓝图继承,需要填写
    • UCLASS(Blueprintable)
    • 注:继承应用了该宏的派生类可以不用加
  • 不建议 vs 生成代码,而是引擎中编译按钮进行重新生成,或按 Alt+Ctrl+F11 调用引擎 LiveCoding 编译 (编译信息可以在 Window->MessageLog 查看)
  • 若关闭了 LiveCoding 可使用 Ctrl+Alt+Shift+P 热编译代码修改

# 接口

# 常规

  • GetWorld():"Engine/World.h"

    • UActorComponent、AActor 均有该接口
  • GetActorTransform:transform

  • SetActorLocation:设置对象坐标,同 Unity position

  • AddActorLocalOffset:本地坐标增量移动 (注:sweep 只对根组件生效)

  • AddForce:启用物理模拟后施加力,可传入第三个参数指定忽略质量

  • SetupAttachment:设置父级组件

  • ContructorHelper:静态帮助类,可用于加载资源

  • "Kismet/GameplayStatics.h"

    • 各种帮助函数,如 GetAllActorsOfClass (GetWorld (),XXX::StaticClasd)
  • 碰撞

    • 设置为触发器:UBoxComponent->SetCollisionEnabled (ECollisionEnabled::QueryOnly)
    • 设置响应通道:->SetCollisionResponseToChannel ()
    • 绑定响应事件:->OnComponentBeginOverlap.AddDynamic ()
    • 注:或直接使用 TriggerBox 等

# 对象创建

# UWorld::SpawnActor

  • 在世界中创建 Actor

# UWorld::SpawnActorDeferred

  • 在世界中创建 Actor
  • 配套 xxx->FinishSpawn () 调用后才会调用 BeginPlay 等进行初始化的函数,即延迟调用 (在此之前可设置一些其它初始化参数)

# CreateDefaultSubobject

  • 为 Actor 创建组件 ( 只能在构造函数使用 )
  • T:类型
  • TEXT:一个标识,当前类中不能重复

# NewObject

  • 仅在运行时构建 UObject 使用 ( 构造函数不要用 )
  • 主要指 UObject 的派生类 (非 Actor、非 ActorComponent)
  • objects-in-unreal-engine

# AActor::AddComponentByClass

  • 动态为 Actor 创建一个组件
  • AActor 所实现,内部其实也是调用 NewObject 创建 (所以构造函数应当也不要用)

# 纯 C++ 类的创建

  • 纯 C++ 类 (指非继承自 UObject 的类,如 UE 中一般以 F 开头的)
  • 可以通过 new 来创建对象,并使用 TSharedPtr 和 TSharedRef 来管理对象
  • TSharedPtr<MyClass> MyClassPtr = MakeShareable(new MyClass())

# 资源加载

# UObject::StaticLoadObject

  • 加载资源文件 (静态对象、非蓝图资源)
  • 应该算是资源加载 API 中最底层的了?

# UObject::StaticLoadClass

  • 可用于加载蓝图类资源
  • 内部调用 LoadObject

# LoadObject< T >

  • 封装 StaticLoadObject ,可以当做其泛型版本
  • 可用来加载非蓝图资源,比如动画、贴图、音效等
  • 内部会先执行 FindObject (查找已加载对象)

# LoadClass< T >

  • 封装 StaticLoadClass ,用来加载 UClass,可以当做其泛型版本
  • 可以用来加载蓝图并获取蓝图类

# ConstructorHelpers

  • FClassFinder:可以用来加载蓝图类资源
  • FObjectFinder:对 LoadObject () 的封装,加载非蓝图普通资源
  • 只能在构造函数使用
  • 查找某个对象的对象和类

# FSoftObjectPath

  • 包含资源路径字符串的结构体
  • 根据给定的资源路径找到对应的资源
  • ResolveObject 内部封装 FindObject<UObject>(nullptr, PathString) 调用
  • TryLoad 内部封装 StaticLoadObjectLoadObject 调用
  • 可配合 StreamableManager 进行资源异步加载
  • TSoftObjectPtr :看起来可以管理 FSoftObjectPath

# FSoftClassPath

  • 继承自 FSoftObjectPath,可用于加载蓝图资源
  • 提供 TryLoadClass 实际调用 LoadClass

# FSoftObjectPtr

  • 封装对 FSoftObjectPath 操作
  • (也提供了 LoadSynchronous 同步加载方法,实际是调用 FSoftObjectPath.TryLoad)

# TSoftClassPtr

  • 与 TSoftObjectPtr 类似

# FStreamableManager

  • 可用于资源异步加载
  • 异步加载:
    • RequestAsyncLoad,返回 FStreamableHandle
  • 同步加载:
    • RequestSyncLoad (多个或单个加载,返回 FStreamableHandle)
    • LoadSynchronous (单个加载,实际调用 RequestSyncLoad,直接返回 UObject)
  • 看起来主要是与 FSoftObjectPath 配合使用
  • 官方建议定义在类似单例之类对象中使用
  • 注:FStreamableHandle 是同步或者异步加载的句柄,只要句柄处于激活状态,资源就会一直存在于内存

# ObjectLibrary

  • 资源加载库:设定一个路径,然后让它自动扫描
  • 可用于在指定路径中加载所有资源数据,之后选择性加载实际资源
  • 其中存储的 FAssetData 提供 GetSoftObjectPath () 接口返回 FSoftClassPath

# 调用关系

  • LoadObject ->UObject::StaticLoadObject
  • UObject::StaticLoadClass ->LoadObject->UObject::StaticLoadObject
  • LoadClass ->UObject::StaticLoadClass->LoadObject->UObject::StaticLoadObject
  • ConstructorHelpers::FObjectFinder ->LoadObject->UObject::StaticLoadObject
  • ConstructorHelpers::FClassFinder ->UObject::StaticLoadClass->LoadObject->UObject::StaticLoadObject
  • FSoftObjectPath ->ResolveObject->UObject::FindObject
  • FSoftObjectPath ->TryLoad->UObject::StaticLoadObject
  • FSoftClassPath ->TryLoadClass->LoadClass->UObject::StaticLoadClass->LoadObject->UObject::StaticLoadObject

# 注意

资源加载中,同类 API 一般有 LoadObjectLoadClass 两种版本的,其中 LoadObject 最基础, LoadClass 属于在 LoadObject 之上的封装 (继承) 扩展。
相当于 LoadObject 可用于加载所有资源,而 LoadClass 可用于更方便地加载蓝图类资源 (不知道理解得对不对)。

FObjectFinder、FClassFinder、LoadClass、StaticLoadClass 等最终都会调用到 LoadObject

# 模板

TSubclassOf< T >:定义类类型属性时,使引擎面板过滤类型,只能选择 T 类本身或继承自它的类。

  • 注:直接定义类类型指针,将能选择场景中类型对象

TArray:动态容器
Cast< T >:类型转换

# 字符串

  • FString::FromInt
  • FString::SanitizeFloat
  • FString::Printf

# 调试

GEngine->AddOnScreenDebugMessage:屏幕调试显示 (-1 的 key 可以始终显示,否则会去重)

  • 蓝图中为 PrintString

# 材质

Actor:BaseMesh

  • BaseMesh->CreateAndSetMaterialInstanceDynamic
  • UMaterialInstanceDynamic->SetVectorParameterValue()

# 颜色

FLineColor

  • 有预定义静态颜色
  • 随机颜色:FLineColor::MakeRandomColor ()

# 定时器

TimeManager.h

  • GetWorldTimerManager().SetTimer(FTimeHandler,this,callback,timeRate,isLoop)
  • GetWorldTimerManager().ClearTimer(FTimeHandler)

# 组件

  • USceneComponent:空组件
  • UStaticMeshComponent:mesh 组件
  • USpringArmComponent:悬臂组件 (相机平滑跟随)
  • UCameraComponent:相机
  • UParticleSystem:粒子
  • UDecalComponent:贴花

# 宏定义

  • UPROPERTY
    • BlueprintReadWrite
    • BlueprintReadOnly
    • VisibleAnywhere:可在任何地方可见
    • EditAnywhere:可在任何地方编辑 (常用,但指针类型避免使用)
    • Category="分类"
    • EditInstanceOnly:只允许在实例上编辑
    • VisibleInstanceOnly:只允许在实例上查看
    • EditDefaultsOnly:只允许在原型中编辑
    • VisibleDefaultsOnly:只允许在原型中编辑
    • meta=(ClampMin=-5.0f,ClampMax=5.0f,UIMin=-5.0f,UIMax=5.0f):数值限制等
    • meta=(AllowedClassses="Textures"):限制变量类型
    • 注:标记该属性的对象会被引擎自动计数回收
  • UFUNCTION
    • BlueprintCallable
    • BlueprintImplementableEvent:标记方法为蓝图实现的事件,代码仅定义
  • UE_LOG
    • LogTemp,Log/Warning/Error,TEXT("Hello World!")
  • FORCEINLINE:强制内联
  • DEFINE_LOG_CATEGORY_STATIC:自定义日志级别
  • UENUM:定义枚举
    • BlueprintType (蓝图可用)
  • USTRUCT:定义结构
    • BlueprintType (蓝图可用)
    • 主体需添加:GENERATED_USTRUCT_BODY
  • DECLARE_DELEGATE:定义委托
    • 代码中通过 AddUObject 绑定
  • DECLARE_MULTICAST_DELEGATE:定义多播委托
  • DECLARE_DYNAMIC_DELEGATE:定义委托 (蓝图可用)
    • 绑定的委托方法需要定义 UFUNCTION 宏
    • 代码中通过 AddDynamic 绑定
  • DECLARE_DYNAMIC_MULTICAST_DELEGATE:定义多播委托 (蓝图可用)

# 问题

  • 不允许指针指向不完整类型
    • 官方文档搜索类型,可查看需要引入的头文件
  • 类前添加 class 可规避一些编辑器不识别的麻烦
  • 默认创建类时,若添加了自己的目录,引用头文件也会添加,需要删除 (否则会找不到)

# 蓝图

蓝图对象根组件:(如果没有任何组件时会自动创建) 相当于一个空物体对象,承载 Transform 等属性,可被替换

  • 根层级有多个组件且未指定根组件时,会随机选取一个作为根组件
  • 指定方式很简单:(代码) 构造函数初始化时为 RootComponent 赋值即可

操作

  • Alt + 点击连接线:断开节点
  • 右键蓝图 -> 资产操作 -> 重新加载

其它

  • 关卡蓝图:每个关卡一张

节点

  • Construct Object from class:实例化一个继承自 UObject 的类
  • Break XXX:访问一个类型对象中的字段 (或直接右键节点 split)

# 物体

碰撞

  • 默认导入对象生成的是复杂碰撞,简单碰撞需要手动生成 (Detail 面板也需要设置,引擎似乎也可以全局设置)
  • 凸包碰撞:生成的凸包碰撞也是属于简单碰撞,外形与模型类似,可调整参数

# 控制台

~键打开

  • 最大帧率:t.MaxFPS

# 参考文档

  • 写给 Unity 开发者的 Unreal Engine 开发指南 (扫盲)
  • Unreal Engine 的启动流程
  • Unreal Engine 的 Gameplay 框架和重点
  • https://zhuanlan.zhihu.com/p/467236675
  • 【UE4/5】虚幻 4/5 入门 c++ 基础训练
  • 成功上岸,将自己进大厂前花 3w 买的 C++ 游戏开发 & 虚幻引擎 UE 全套教程,完整版 300 集,拿出来分享给大家!学会即可上岗就业
  • 《InsideUE4》GamePlay 架构(四)Pawn
  • 《InsideUE4》GamePlay 架构(五)Controller
  • Subsystems
  • Unreal Engine 4 C++ 创建对象的几种方法
  • objects-in-unreal-engine
  • 【UE4 基础】对象创建与资源获取
  • 使用 ConstructorHelpers 来加载
  • [UE4] C++ 实现动态加载的问题
  • UE4_C++_资源加载与优化
  • AsyncLoading
  • AssetManager 系列之 TAssetPtr 与 FStreamableManager
  • 关于资产引用的各种 Path 和 Ptr 介绍