🤸 Unreal 5 C++ 多人游戏入门资料
2025-06-05
注意事项
从 Rider 启动编辑器
Ctrl+S 并不是全部保存
使用版本管理工具
-
先关闭编辑器并不保存所有修改; -
回退版本; -
使用 Rider 打开编辑器;
UE 常见类
UObject
-
AActor -
UActorComponent
AActor
参考资料(官方文档): https://dev.epicgames.com/documentation/en-us/unreal-engine/unreal-engine-actor-lifecycle
-
BeginPlay (Level 开始时被调用) -
EndPlay (在 Destory 中被调用) -
Tick (每帧更新) -
BeginDestroy (与垃圾回收相关,通常用于释放内存或处理多线程资源) -
FinishDestory (与垃圾回收相关,这是 UObject 被释放前的最后一次调用)
APawn
https://dev.epicgames.com/documentation/en-us/unreal-engine/API/Runtime/Engine/GameFramework/APawn
UActorComponent
-
PostLoad 静态 Actor 加载时触发 -
InitializeComponent 组件首次初始化时触发 -
BeginPaly Actor 进入游戏时触发 -
TickComponent 函数每帧都会执行 -
EndPlay Actor 被移除时触发
MyComponent = CreateDefaultSubobject<UMyCustomComponent>(TEXT("MyComponentName"));
MyComponent->SetupAttachment(RootComponent); // RootComponent 是 Actor 的默认根组件
子类 | 功能说明 | 示例场景 |
USceneComponent | 提供空间变换能力,支持层级嵌套(如根组件、子组件) | 角色骨骼、摄像机弹簧臂 |
UPrimitiveComponent | 继承自 USceneComponent,支持物理碰撞与渲染(如静态网格、粒子系统) | 武器碰撞体积、可破坏物体 |
UAudioComponent | 直接继承自 UActorComponent,提供音频播放功能 | 背景音乐、角色脚步声 |
UMovementComponent | 实现物理移动逻辑(如角色移动、飞行载具) | 角色跳跃、载具加速 |
UE 垃圾回收机制
所有强引用(Hard Reference)
-
持有一个 UPROPETY 宏标记的 C++ 裸指针
UPROPERTY()
UMyObjectClass* Foo;
-
持有 UPROPERTY 宏标记的 C++ 裸指针的 UE 容器
UPROPERTY()
TArray<UMyObjectClass*> Foo;
-
根集对象(可以通过 MyObject->AddToRoot() 将对象添加到根集) -
被 TStrongObjectPtr 管理的对象(不需要 UPROPERTY )
某些弱引用(Soft Reference)
-
在 UFUNCTION 内被声明的新的 UObject 会存活到其生命周期结束(当前函数结束)。 -
在结构体或类中直接使用 UObject 的裸指针作为成员变量 -
使用 TWeakObjectPtr<T> 或 TSoftObjectPointer<T> 弱指针作为成员变量
智能指针与非
UObject
对象的内存管理
非 UObject 智能指针 | 机制 | 备注 | 可空性 | 标准库指针类比 |
TUniquePtr | 所有权 | - | 可空 | std::unique_ptr |
TSharedPtr | 引用计数 | - | 可空 | std::shared_ptr |
TSharedRef | 非空引用计数 | 可以转换为 TSharedPtr | 不可空 | - |
TWeakPtr | 弱引用不计数 | 用于解决引用计数的循环引用问题 | 可空 | std::weak_ptr |
UObject 智能指针 | 机制 | 备注 | - | |
TStrongObjectPtr | 引用计数 | 用于保护 UObject 不被垃圾回收 | 可空 | - |
TWeakObjectPtr | 弱引用不计数 | 上面的弱引用版本 | 可空 | - |
Tips:为什么 UE 有一套自己的智能指针? UE 源码起源早于 C++ 11。另一方面,自主的智能指针有利于多平台支持的开发。此外,独立的智能指针也提供了统一的命名风格等好处。笔者仅了解至此。
参考资料
多人游戏与网络同步机制
Actor 的网络属性
所有者:Role 和 Remote Role
-
Local Role 代表的是当前机器对该 Actor 的权限 -
Remote Role 代表的是对端机器对该 Actor 的权限
ROLE_None | 这个 Actor 不是可复制的 | Local Role | Remote Role |
ROLE_Authority | 当前机器是 Actor 的所有者,持有 Actor 真正的状态 | 服务器 | 客户端 |
ROLE_SimulatedProxy | 当前机器只是 Actor 的模拟,对于 Actor 的真正状态是只读的,同时不能调用远程函数 | 客户端 | 无 |
ROLE_AutonomousProxy | 当前机器虽然是 Actor 的模拟,但可以更改其真正状态和调用远程函数 | 客户端 | 服务器 |
优先级(Priority)
复制(Replication)
AMyActor::AMyActor()
{
bReplicates = true; // 启用 Actor 的复制
...
}
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "MyActor.generated.h"
UCLASS()
class HELLOUNREAL_API AMyActor : public APawn
{
GENERATED_BODY()
public:
AMyActor();
UPROPERTY(EditAnywhere)
AMyActor* Friend;
// ========== Replication ==========
/// 要想声明一个可以被复制的变量,首先需要使用 Replicated 或 ReplicatedUsing(见下)标签
/// 接着还必须在 GetLifetimeReplicatedProps 注册要复制的变量(见下)
UPROPERTY(Replicated)
bool IsSleeping;
/// @c ReplicatedUsing 为复制提供了一个声明回调的机会,
/// 这个功能被称作 "RepNotify"。
/// 在这个例子中,@c OnRep_OwnedCatsCount 会在客户端成功收到变量的复制值时运行。
/// 需要注意的是,这个在回调中并不需要执行赋值,赋值在注册后是自动的。
/// 回调函数会在被复制赋值之后被触发。
UPROPERTY(ReplicatedUsing=OnRep_OwnedCatsCount)
int OwnedCatsCount;
/// 所有有复制变量的 Actor 都需要重载该函数,其作用是注册需要网络复制的变量。
///
/// 其会被在 Actor 被创建并加入网络时被触发,用于构建网络同步的元数据;
/// 通常在服务端,是服务器生成 Actor 时调用此函数。
///
/// 使用该函数务必首先调用 @c Super::GetLifetimeReplicatedProps 以确保父类的内容被注册
void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
protected:
UFUNCTION()
void OnRep_OwnedCatsCount();
};
相关性(
Relevancy
)
-
强制相关条件 满足以下任一条件时,Actor 必定相关 (向客户端同步): -
依赖所有者的相关性 若满足以下条件,Actor 的相关性 由其 Owner 决定 : -
强制不相关条件 满足以下任一条件时,Actor 必定不相关 : -
基于骨骼附加的相关性 若 Actor 附加到其他 Actor 的骨骼 (如角色装备的武器): -
距离相关性(可选) 若启用了 距离相关性 (通过 AGameNetworkManager::bUseDistanceBasedRelevancy ):
-
强制相关 条件的优先级最高(如 Always Relevant )。 -
强制不相关 条件优先级次之(如隐藏或无根组件)。 -
其余条件按顺序判断,最终由 距离或附加目标相关性 决定。
-
使用控制台命令 VisualizeNetworkRelevancy 可视化相关性范围。 -
检查 Actor 属性: bAlwaysRelevant 、 bOnlyRelevantToOwner 、 NetCullDistanceSquared 。
网络休眠(
Network Dormancy
)
-
DORM_Never 永不休眠 -
DORM_Awake 苏醒 -
DORM_DormantPartial 对于部分连接是休眠的,通过 AActor::GetNetDormancy 来获得具体在哪些连接中是休眠的 -
DORM_DormantAll 对于所有连接都是休眠的 -
DORM_Intial 初始对所有连接都是休眠的
AActor::SetDormancy(ENetDormancy::DORM_Awake); // 唤醒
AActor::FlushNetDormancy(); // 唤醒
AActor::SetDormancy(ENetDormancy::DORM_DormantAll); // 休眠
RPC 远程函数调用
标签 | 运行位置 | 备注 |
Client | 客户端 | 所有在这个 Actor 上拥有连接的客户端 |
Server | 服务器 | 只能在拥有这个 Actor 的客户端上调用 |
Remote | 对端 | |
NetMulticast | 客户端与服务器 | 服务器与所有与这个 Actor 相关的客户端 |
/// RPC 远程函数调用,可以实现跨端调用函数的功能。
/// @c Reliable 标签声明了这此调用一定要被抵达,不管带宽情况如何;
/// 默认的可靠性是 Unreliable.
/// @c WithCalidation 提供了简单的程序验证,通过实现对应的 XXX_Validate 函数实现
UFUNCTION(Server, Reliable, WithValidation)
void ServerRequestPetCat();
UFUNCTION(Client)
void ClientPlayPetCat();
UFUNCTION(NetMulticast)
void NetMulticastRPC();
void AMyActor::ServerRequestPetCat_Implementation() { ... }
bool AMyActor::ServerRequestPetCat_Validate() { ... }
UPROPERTY(Replicated)
AMyActor* Friend;
void LetMyFriendPetMyCat() {
Friend->ServerRequestPetCat();
}
void ServerRequestPetCat_Implementatoin() {
ClientPlayPetCat()
}
void LetMyFriendPetMyCat() {
ServerRequestLetMyFriendPetCat();
}
void ServerRequestLetMyFriendPetCat_Implementation() {
if (IsValid(Friend) && TargetFriend->GetOwner() == GetOwner()) {
Friend->ClientPlayPetCat();
}
}
调试建议

场景切换
Profiling



参考资料
-
How to Optimize Performance in Unreal Engine 5 https://youtu.be/lfjG3z5VVIw
其它常用 API
-
类型转换
UBar* Parent;
...
UFoo* Child = Cast<UFoo>(Parent);
-
在子类中调用父类内容,使用 Super ,如 Super::Foo() -
检查指针的可用性(是否为空指针):
UPROPERTY()
UMyClass* ReflectedPtr;
UMyClass* NotRefletedPtr;
...
// For reflected pointer (UPROPERTY)
IsValid(ReflectedPtr);
// For not reflected pointer
NotReflectedPtr->IsValidLowLevel()