# 前言
前几天 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 内部封装
StaticLoadObject
或LoadObject
调用 - 可配合 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::StaticLoadObjectUObject::StaticLoadClass
->LoadObject->UObject::StaticLoadObjectLoadClass
->UObject::StaticLoadClass->LoadObject->UObject::StaticLoadObjectConstructorHelpers::FObjectFinder
->LoadObject->UObject::StaticLoadObjectConstructorHelpers::FClassFinder
->UObject::StaticLoadClass->LoadObject->UObject::StaticLoadObjectFSoftObjectPath
->ResolveObject->UObject::FindObjectFSoftObjectPath
->TryLoad->UObject::StaticLoadObjectFSoftClassPath
->TryLoadClass->LoadClass->UObject::StaticLoadClass->LoadObject->UObject::StaticLoadObject
# 注意
资源加载中,同类 API 一般有 LoadObject
和 LoadClass
两种版本的,其中 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 介绍