UE 5.8 终于把 C++ 联网射击这块的编辑体验补齐了。这次我们来看一套 CodeX 出品的 UE5 C++ 第一人称多人射击游戏中文精翻教程,它不教你做单机 Demo,而是从零搭建一个可联网对战的 FPS 框架:角色移动、动画蓝图、武器系统、血条与伤害、击杀反馈、UI 播报、服务器同步、会话创建与加入,全流程用 C++ 写,不用蓝图绕路。
这套教程的重点不是“看一眼就会”,而是让你在跟着做的时候真正理解 UE 的 Gameplay 框架:Pawn、PlayerController、PlayerState、GameMode、GameState 各司其职,客户端的输入如何变成服务器上的合法行为。如果你在做联机功能时总遇到“本地能跑、联机不同步”的问题,这套内容会直接帮你把概念和代码对起来。
本文会把教程主线拆成可执行的开发路线:先讲这套精翻课程适合谁、看之前需要准备什么环境,再按“玩家角色 → 武器 → 伤害与 UI → 网络同步 → 大厅会话”的顺序梳理实现思路,最后给出常见编译错误、多人联机测试方法、以及把 Demo 扩展成完整项目的建议。不管是从蓝图转 C++,还是第一次接触 UE 多人开发,这套流程都值得先收藏再慢慢过。
1. 教程核心能力速览
| 能力项 | 说明 |
|---|---|
| 课程来源 | CodeX 出品的 UE5 C++ 第一人称多人射击游戏教程,提供中文精翻 |
| 引擎版本 | 以 UE5 系列为基础,标题对应 UE 5.8,主力模块为 C++ 与网络复制 |
| 核心内容 | 第一人称角色控制、武器开火、伤害计算、血条 UI、击杀公告、多人会话 |
| 开发语言 | C++ 为主,辅助少量蓝图用于 UI、动画通知和资产配置 |
| 网络架构 | Actor 复制、RPC 调用、PlayerState 同步、GameMode 会话控制 |
| 适合人群 | 有 C++ 基础、想从单机 Demo 切换到多人联机开发的 UE 开发者 |
| 硬件建议 | 推荐独立显卡、16GB 内存以上,视项目规模和烘焙需求而定 |
| 启动方式 | UE 编辑器直接打开工程,编译通过后运行 PIE 或打包测试 |
| 是否含素材 | 教程侧重代码实现,美术、音频素材通常需自行准备或用引擎自带资源 |
| 是否支持批量 | 不适用,期间建议以“理解机制 + 小步测试”为主,不需要资产批处理 |
标题中的 UE 5.8 可视为 UE 5.7 到 5.8 之间的阶段性版本代号,安装时以 Epic Launcher 可获取的正式版为准。硬性门槛不高,重点在于理解 C++ 工程组织方式和多人同步原理。
2. 适用场景与使用边界
这套教程适合谁?最适合的是已经在 UE 里做过几个单机小项目、蓝图逻辑已经能跑通、但是一碰到多人就懵的开发者。它非常适合解决以下几类问题:
- 玩家角色从“本地控制”变成“服务器控制”时,移动和开火怎么改。
- 客户端和服务端分别执行什么代码,什么时候走 RPC,什么时候走属性复制。
- 玩家血量显示在 HUD 上,其他客户端怎么看这个玩家的名字和血条。
- 点击“创建房间”之后,好友如何通过会话系统加入同一张地图。
它不适合完全没有编程基础的入门读者。虽然会有中文精翻,但 C++ 的语法、头文件包含、宏和 UE 反射机制本身就是门槛。如果看不懂 UCLASS、UPROPERTY、UFUNCTION,建议先补一轮 C++ 基础再回来。
还要提醒一个问题:多人射击项目涉及在线会话、Steam 或 Epic 联机插件,联机匹配和玩家数据会走平台服务。开发和测试时不要用未经授权的账号信息,也不要植入任何绕过认证、外挂和作弊相关的代码。教程里的联机内容只用于本地多窗口测试或已授权的在线大厅测试。
3. 环境准备与前置条件
3.1 引擎与开发工具
建议安装并核对以下环境:
| 工具 | 作用 | 建议 |
|---|---|---|
| Epic Games Launcher | 获取 UE 编辑器 | 安装 UE 5.7 或 UE 5.8 对应版本,保持源码工程和编辑器版本一致 |
| Visual Studio 2022 | 编译 C++ 工程 | 勾选“使用 C++ 的游戏开发”工作负载 |
| UE 编辑器 | 资源、地图、蓝图开发 | 建议放到非中文路径,避免字符编码问题 |
| Git / Perforce(可选) | 工程版本管理 | 多人项目迭代快,强烈建议初始化 Git |
注意:UE 5.x 的 C++ 工程对中文路径支持不算稳定。如果工程名或路径里带有中文,编译和打包时可能出现莫名其妙的“无法找到模块”错误。最稳妥的办法是路径中只使用英文字符。
3.2 硬件与磁盘空间
从教程需求看,开发阶段不需要顶配机器,但建议满足:
- CPU:6 核以上处理器,编译 C++ 时多核优势明显。
- 内存:16GB 起步,状态越多、打开编辑器越久,内存占用越高。
- 显卡:独立显卡即可,教程中的项目规模不大,也能在中等画质下稳定运行。
- 磁盘:工程本身加引擎缓存可能超过 50GB,建议预留 100GB 以上。
编译时间会随工程规模增加而增长。第一次编译或修改 Build.cs 后编译通常较慢,之后增量编译会快很多;如果每次编译都要几分钟,先检查是不是杀了UnrealBuildTool进程,或者是不是没开启增量编译。
3.3 素材和网络环境
教程不会大量提供美术素材。角色可以用 UE5 自带的第三人称模板资源,武器模型甚至可以使用Cube加发射口临时替代。声音、开火粒子、命中特效可以在后续开发时再单独替换,第一步先把逻辑跑通。
如果用到 Steam 联机,需要开发者拥有 Steam 账号并注册 Steamworks AppID;如果只是本机开两个客户端联机测试,无需真实 Steam 上线。本教程是精翻课程,重点在代码实现,所以补素材时以引擎自带内容和临时占位资产为主。
4. 安装部署与工程创建
4.1 创建 C++ 工程
打开 Epic Launcher,在 UE 版本下选择创建工程。建议选择“游戏 → 第三人称”或“第一人称”模板,模板类型选择C++,这样可以获得基础的角色类、输入绑定和地图,后续按教程改造成多人框架会快很多。
创建工程时,把工程名设为英文,例如:
FPSMultiplayerTutorial工程路径放在机械或固态磁盘根目录下,避免出现:
C:\Users\张三\Desktop\游戏项目这类中文路径。
4.2 生成 Visual Studio 项目文件
创建 C++ 工程后,在 UE 编辑器里进入:
文件 -> 生成 Visual Studio 项目文件等待 IDE 文件生成完成后,用 Visual Studio 打开.sln文件。第一次打开时,Visual Studio 可能需要加载 UE 的大量头文件索引,扫描时间较长,不要以为是卡死。
4.3 编译并运行
先编译工程:
# 如果使用命令行编译,可以在工程目录下执行 # 实际项目名替换为你的工程名 "<UE安装目录>\Engine\Build\BatchFiles\Build.bat" FPSMultiplayerTutorialEditor Win64 Development更常用的方式是在 Visual Studio 中选择启动项目为FPSMultiplayerTutorialEditor,按Ctrl + B编译。编译完成后按F5启动编辑器。启动后创建或打开一张空地图,放置玩家出生点,运行 PIE 验证单机逻辑。
4.4 验证基础 C++ 能力
教程中会反复修改几个核心类。你可以先新建一个测试类来确认 C++ 编译链路正常:
#pragma once #include "CoreMinimal.h" #include "GameFramework/Character.h" #include "FPSCharacter.generated.h" UCLASS() class FPSMULTIPLAYERTUTORIAL_API AFPSCharacter : public ACharacter { GENERATED_BODY() public: AFPSCharacter(); };在.cpp中实现构造函数:
#include "FPSCharacter.h" AFPSCharacter::AFPSCharacter() { PrimaryActorTick.bCanEverTick = true; }编译通过后,说明 C++ 工程基础没问题。之后把教程中的类按模块添加即可。
5. 核心机制拆解与实现路线
5.1 玩家角色:Pawn 与 Character 的选择
第一人称射击项目中,角色通常继承自ACharacter。建议第一时间在SetupPlayerInputComponent中绑定移动和转向方法,并开启“使用 Pawn 控制旋转”。教程主线也会强调:第一人称角色的 Yaw 来自 Controller 旋转,而不是网格体旋转。
关键代码结构:
void AFPSCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) { Super::SetupPlayerInputComponent(PlayerInputComponent); PlayerInputComponent->BindAxis("MoveForward", this, &AFPSCharacter::MoveForward); PlayerInputComponent->BindAxis("MoveRight", this, &AFPSCharacter::MoveRight); PlayerInputComponent->BindAxis("LookUp", this, &AFPSCharacter::LookUp); PlayerInputComponent->BindAxis("Turn", this, &AFPSCharacter::Turn); PlayerInputComponent->BindAction("Fire", IE_Pressed, this, &AFPSCharacter::StartFire); }这里的MoveForward、MoveRight输入映射名称要和项目的 Input 设置保持一致。UE5.1 以后更推荐使用增强输入系统,但教程为了讲解底层逻辑,仍会保留传统输入轴或直接演示增强输入的 Alpha 通道绑定方式。
注意:多人环境下,移动函数只应在本地控制角色上执行:
if (!IsLocallyControlled()) { return; }否则会发生“每个客户端都在移动同一个角色”的同步问题。
5.2 摄像机与第一人称视角
第一人称摄像机通常放在角色胶囊体上,并调整相对位置为(0, 0, 70)左右。这样可以看到手臂和武器。教程中一般会用UCameraComponent和USpringArmComponent完成:
CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom")); CameraBoom->SetupAttachment(RootComponent); CameraBoom->bUsePawnControlRotation = true; FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera")); FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName); FollowCamera->bUsePawnControlRotation = false;记住第一人称模板的核心:控制器旋转决定了摄像机朝向,手臂只负责缓冲物理碰撞,不能再次旋转视角。
5.3 武器系统:从开火到命中判定
武器类建议单独设计,不要把所有代码塞进角色类。教程通常会把武器做成AActor子类,挂接到角色网格体上的武器插槽,通过SetOwner建立所有权关系。开火流程分为:
- 客户端本地播放开火动画、音效和发射特效。
- 客户端调用服务器 RPC,请求服务器生成弹道数据。
- 服务器执行可追踪命中或射线检测,计算伤害。
- 服务器将伤害结果同步到相关客户端。
RPC 宏用法:
// 在武器类头文件中 UFUNCTION(Server, Reliable, WithValidation) void ServerFire(); // 实现中 void AWeaponBase::ServerFire_Implementation() { PerformLineTrace(); }这里必须先提供_Validate版本:
bool AWeaponBase::ServerFire_Validate() { return true; }如果_Validate返回 false,服务器会直接断开客户端调用。脚本权限校验是联机安全的基本要求,教程里至少会有一次验证 RPC 的演示。
5.4 伤害计算与 PlayerState 同步
多人射击中,玩家自己只更新本地动画和 UI,服务器统一管理血量。伤害模型推荐写成:
UFUNCTION() void OnTakeDamage(AActor* DamagedActor, float Damage, AController* InstigatedBy, FVector HitLocation, UPrimitiveComponent* FHitComponent, FName BoneName, FVector ShotFromDirection, const UDamageType* DamageType, AActor* DamageCauser);在BeginPlay里绑定事件:
OnTakeAnyDamage.AddDynamic(this, &AFPSCharacter::OnTakeDamage);服务端计算出新血量后,通过ReplicatedUsing或GetLifetimeReplicatedProps同步给客户端:
void AFPSCharacter::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const { Super::GetLifetimeReplicatedProps(OutLifetimeProps); DOREPLIFETIME_CONDITION(AFPSCharacter, Health, COND_None); }血条 UI 读取本地复制的Health值并刷新进度条。击杀信息则可以经由AGameState或直接通过APlayerState上的击杀计数变量广播。
5.5 动画蓝图与第一人称手臂
第一人称动画可以用基于速度的 BlendSpace 控制上下身移动,也可以使用惯性修正。多人项目需要额外考虑:角色网格体在第三人称观察者视角下要播放全身动画,但本地第一人称视角只渲染手臂和武器。
常见做法是双重网格体方案:
FirstPersonMesh:只本地渲染,设置SetOwnerNoSee(false)、SetOnlyOwnerSee(true)。ThirdPersonMesh:给其他客户端渲染,设置SetOnlyOwnerSee(false)。
这样同一个角色在两个客户端看起来不同。这个细节在联机 FPS 中非常关键,很多教程会专门讲。
5.6 动画通知与开火事件
开火事件可以放在动画通知蓝图中,也可以直接用UAnimNotifyState。C++ 里推荐写一个自定义通知类:
UCLASS() class UAnimNotify_FireEvent : public UAnimNotify { GENERATED_BODY() public: virtual void Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, const FAnimNotifyEventReference& EventReference) override; };在Notify中获取拥有者武器并触发开火逻辑:
void UAnimNotify_FireEvent::Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, const FAnimNotifyEventReference& EventReference) { if (AActor* OwningActor = MeshComp->GetOwner()) { if (AWeaponBase* Weapon = OwningActor->FindComponentByClass<AWeaponBase>()) { Weapon->ClientFire(); } } }动画通知的好处是开火时机和枪口动画吻合,后续加入换弹、上膛和连发节奏也容易扩展。
5.7 游戏模式与玩家控制器
AGameModeBase只在服务器上有意义。创建房间时,服务器选择默认 Pawn 和默认 PlayerController:
AGameModeBase::AGameModeBase() { DefaultPawnClass = AFPSCharacter::StaticClass(); PlayerControllerClass = AMyPlayerController::StaticClass(); }APlayerController是客户端输入进入服务器的“入口”。在多人 FPS 中,适合把玩家重生的时机、聊天指令、RPC 验证都放在 PlayerController 里,而不是 Pawn 里。教程中也会引导学生把“是否可以开火”这类权限检查放到 PlayerController 层。
6. 多人会话:创建房间与加入游戏
6.1 Open Level 与会话创建
最简联机测试可以用OpenLevel和ServerTravel:
// 服务器创建房间 GetWorld()->ServerTravel("/Game/Maps/FPSMap?listen", true);客户端加入时使用:
// 客户端连接服务器 APlayerController* PC = GetWorld()->GetFirstPlayerController(); PC->ClientTravel("192.168.1.100:7777", TRAVEL_Absolute, true);这只是最原始的联网方式,不包含大厅、匹配和好友列表。教程后面一般会接入 Online Subsystem 的 Sessions 接口:
UOnlineSubsystem* OnlineSubsystem = Online::GetSubsystem(GetWorld()); IOnlineSessionPtr Sessions = OnlineSubsystem->GetSessionInterface(); Sessions->CreateSession(PlayerId, SessionName, SessionSettings); Sessions->FindSessions(PlayerId, SearchSettings); Sessions->JoinSession(PlayerId, SessionName, SearchResult);6.2 会话回调与错误处理
创建会话和查找会话是异步操作,必须绑定回调:
OnCreateSessionCompleteDelegateHandle = Sessions->AddOnCreateSessionCompleteDelegate_Handle( FOnCreateSessionCompleteDelegate::CreateUObject(this, &AMyGameInstance::OnCreateSessionComplete) );在回调中判断bWasSuccessful。失败时常见原因包括:平台后端未初始化、SessionSettings 中bShouldAdvertise设置为 false、MaxPlayers小于 1、已有同名会话等。
从教程层面来看,建议先在本地双开 PIE 测试会话流程,再接 Steam 或 Epic 在线子系统。这样排查问题时能把“游戏逻辑问题”和“平台服务问题”分开。
7. 功能测试与效果验证
7.1 单机测试
先确认单机逻辑:
- 启动编辑器,打开
FPSMap。 - 点击
Play,控制角色移动、跳跃、开火。 - 检查角色是否朝准星方向开火,子弹是否正常命中。
- 检查受伤后血量条是否下降。
- 检查死亡后能否重生或回到观察者状态。
判断标准很简单:一切行为和本地单机预期一致。
7.2 多人 PIE 测试
UE 支持在编辑器中同时运行多个客户端:
- 将地图设置为
FPSMap?listen。 - 点击
Play,选择Number of Players为 2 或 3。 - 选择
Net Mode为Play As Client或让第一个玩家作为监听服务器。 - 让两个窗口互相射击。
重点观察:
- 第一个客户端能不能创建会话。
- 第二个客户端能不能加入会话。
- 伤害是否同步到被击中客户端。
- 客户端死亡后,服务器是否重新生成。
- UI 上的击杀播报是否在所有客户端一致。
7.3 使用第三人称观察者验证同步
本地第一人称看不到自己的全身模型。为了验证同步,可以在第二个客户端用一个第三人称控制器观察第一个客户端角色。如果看到角色出现“瞬移”“滑步”“开火无子弹”,说明动画或 RPC 逻辑没对齐。
常见的排查顺序:
- 先看
ServerFire有没有被调用。 - 再看射线检测结果是否只在服务器执行。
- 再看伤害是否只由服务器写入。
- 最后看 UI 是否监听到了复制的血量更新。
8. 常见问题与排查方法
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 编译时报 “无法解析的外部符号” | 没有实现_Implementation或_Validate方法 | 查看报错中的类名和函数名 | 在.cpp中补全实现 |
| 角色能移动但摄像机不跟随 | 相机没有绑定到 Pawn 控制旋转 | 检查bUsePawnControlRotation | 改为 true 或从控制器的旋转处更新相机 |
| 开火没有伤害 | 射线检测在客户端执行,但伤害计算需要服务器执行 | 打印HasAuthority() | 只有服务器才执行伤害写入 |
| 其他客户端看不到玩家 | 网格体可见性设置错误 | 检查SetOwnerNoSee / SetOnlyOwnerSee | 按第一人称和第三人称分别设置可见性 |
| 创建会话失败 | 平台子系统未初始化 | 打印OnlineSubsystem名称 | 在DefaultEngine.ini指定在线子系统 |
| 客户端加入后地图不切换 | ClientTravel地址或地图路径错误 | 检查客户端日志 | 用绝对地址并确认端口监听 |
| 血条不更新 | 属性复制未启用 | 查看变量是否带UPROPERTY(Replicated) | 添加复制宏并包含GetLifetimeReplicatedProps |
| PIE 运行时卡顿 | 编辑器同时启动多个客户端,资源占用高 | 观察任务管理器和显存占用 | 降低画面设置或使用无网络客户端进行逻辑调试 |
9. 资源占用与性能观察
9.1 CPU 与内存
C++ 工程的编辑器启动和 Shader 编译会占用大量 CPU。建议观察以下指标:
- 打开编辑器后内存占用通常在 4GB 到 8GB 之间。
- PIE 启动多个客户端后,内存占用会成倍增加。
- 运行 C++ 编译时,CPU 会短暂跑满,这是正常现象。
如果机器在 PIE 多开时明显卡顿,可以先把测试场景中的植被、动态阴影和后期体积关闭。
9.2 网络同步对性能的影响
每个同步的 Actor 都会占用网络带宽。教程项目规模不大,但在扩展时要注意:
- 不要把武器上的临时特效标记为
bReplicates = true。 - 开火特效优先用
Multicast RPC,而不是持续复制变量。 - 每一次
DOREPLIFETIME都会产生网络开销,过度复制会导致 UE 的 NetDriver 频繁更新。
9.3 显存占用
显存占用主要取决于场景资源和后期效果。教程中的第一人称场景多数使用暗色室内关卡,中低端显卡也能流畅运行。但如果使用 DefaultEngine 中的全局光追或大范围开放世界地形,显存占用会明显上升。实际占用需要以GPU-Z或编辑器Stat GPU统计为准,不要以单机 Demo 推测多人场景上限。
10. 批量处理与测试自动化建议
多人射击教程本身不涉及资产批处理,但做联机功能测试时可以借鉴“批量测试”的思路:
- 在 PIE 一键启动 3 个客户端。
- 用控制台命令执行开火测试。
- 通过自定义
Automation测试用例自动创建会话并加入会话。
可执行的自动化 C++ 测试方法:
IMPLEMENT_SIMPLE_AUTOMATION_TEST(FMultiplayerSessionTest, "FPS.Session.CreateAndJoin", EAutomationTestFlags::ApplicationContextMask | EAutomationTestFlags::ProductFilter) bool FMultiplayerSessionTest::RunTest(const FString& Parameters) { // 测试创建会话逻辑 return true; }使用自动化测试的价值在于:每次改完网络逻辑后,可以快速回归验证会话创建、加入、离开的流程,不用反复手动开多个窗口。后续开发 UI、武器装备等内容时,这些基础测试能防住大量低级回归问题。
11. 工程实践与安全建议
11.1 工程管理规范
开发多人 FPS 项目时,建议从一开始就按以下结构组织目录:
Source/ FPSMultiplayerTutorial/ Characters/ Weapons/ PlayerControllers/ GameModes/ UI/ Online/ Content/ Characters/ Weapons/ Maps/ UI/C++ 类按职责拆分目录。后续增加 AI 机器人、拾取物、不同武器类型时,不要把所有文件堆放在Source/FPSMultiplayerTutorial根目录下。否则 Build.cs 会自动编译新增文件,但文件过多时管理和排查会变得困难。
11.2 网络同步的安全边界
多人射击项目最容易出现的问题是“客户端信任服务器不够”:
- 客户端发送的坐标只作为参考,服务器要有最终的移动捕捉。
- 开火前要做权限校验,不能单纯信任客户端的
Fire请求。 - RPC 的
_Validate要检查角色是否存活、是否拥有对应武器。 - 伤害数值调整要放在服务器执行,避免客户端篡改数据。
教程中会模拟合法开发下的标准流程,这部分一定要做笔记。
11.3 版权与素材合规
教程使用 C++ 和临时资源教学,但如果你要把项目和角色发布到正式平台,必须注意:
- 角色模型、动画、音效来源的许可证。
- Epic 商城免费素材的授权范围和是否允许商用。
- 使用 Steam 联机服务时的平台政策和用户协议。
涉及玩家名称、头像、好友关系时,还要遵守隐私和数据安全规范。不要在工程里把玩家账号明文写进日志,也不要收集无关信息。
12. 最佳实践:从教程到完整项目
12.1 小步验证
不要一口气把教程所有内容抄完再编译。建议每完成一个章节就编译、运行、测试一次:
- 创建角色类后:验证移动。
- 添加相机后:验证视角。
- 添加武器后:验证单机开火。
- 添加伤害后:验证血量下降。
- 添加复制后:验证双客户端同步。
每一步都留一个可回退的 Git 提交记录。这样出现网络同步的 Bug 时,可以通过git diff快速定位问题。
12.2 建立最小复现场景
遇到同步问题时,新建一个空场景,只放置一个角色和一面墙,关闭无关 UI,用两个 PIE 客户端最小化复现。把问题压缩到唯一的变量上,比在大场景里继续加日志效率高得多。
12.3 保留网络调试工具
在编辑器控制台输入:
stat net stat rhi stat fps可以快速查看每秒同步的 Actor 数量、带宽占用和帧率。net debug的控制台指令也可以进一步查看某个 Actor 的复制状态,调试阶段非常有用。
12.4 后续扩展方向
做完教程主线后,可以从以下方向继续:
- 添加多种武器和换弹逻辑。
- 对接 Epic Online Subsystem 大厅和邀请。
- 加入机器人 PlayerBot。
- 加入子弹反弹、投掷物物理。
- 使用 GameplayAbilitySystem 重构技能和状态。
- 写自动化测试保证网络回归稳定。
多人 FPS 是最适合深入 UE Gameplay 框架的项目类型,因为几乎每个模块都在网络约束下重新设计一遍。把 CodeX 这套精翻教程完整跑通,相当于替自己省下了四处翻文档和调试同步问题的大把时间。第一遍建议跟着节奏做完整实现,第二遍就按自己的项目需求进行拆分和重构。最值得现在就开始验证的代码段,就是“从客户端发起到服务器执行命中检测”的那一小段,跑通了整个框架就活了。