UE4/5 C++网络服务器编程纪录零--准备篇
Posted blind_mokey
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了UE4/5 C++网络服务器编程纪录零--准备篇相关的知识,希望对你有一定的参考价值。
前言
之前利用业余时间重新复习UE4/5的C++开发,闲来无事做了个基于独立服务器的多人在线(目前限定客户数量是20人以内)DEMO,核心功能在我之前发的B站视频里面有,战斗、动作、交互以及场景演示都有了,有朋友看了视频之后要求我写文档复现,我也顺便纪录一下需要做的工作。方便以后自己遇到相关业务场景进行回顾知识点,顺便分享给各位朋友。
视频指路1-整点小活儿: 整点小活儿
视频指路2-研发中天气场景切换: 研发中天气场景切换
视频指路3-简单的敌人AI功能: 简单的敌人AI功能
准备工作:
游戏服务器:UE5游戏服务器应用 UE5.1
数据库服务器:mysql-8.0.32
服务器操作系统:Linux Ubuntu18.0LTS
WEB服务器:利用UE5进行集成,处理工作在游戏服务器内部完成
客户端:UE5开发完成的客户端
框架设计:
基于UE5的独立服务器功能开发的C/S框架,基本原理就是客户端只负责发起功能请求和渲染展示,游戏服务器负责进行运算数据同步、坐标信息同步、加密保护等功能。数据库服务器用于存储客户数据,客户行为模型,客户标签等功能。WEB服务器处理一部分WEB请求,并提供一切WEB能处理的功能。
重点!!!!
所有的操作和运算都在游戏服务器完成,不允许在客户端进行数值运算和用户数据更新!客户端只能渲染画面和进行数据同步。
服务器开发基础入门参考文档:UE5设置专用服务器
mysql数据库服务器开发基础安装参考文档:mysql官方安装包
一、下载UE5源码
GITHUB源码包:虚幻引擎官方GITHUB
小技巧:在下载完成的安装包Setup.bat里面修改如下配置,能够发动多线程下载,提升某些不可描述原因引起的1兆不到的网速。
set PROMPT_ARGUMENT=set PROMPT_ARGUMENT=--prompt --threads=30 --exclude=VS2012 --exclude=VS2013 --exclude=VS2017 --exclude=html5
然后再在hosts里添加unreal的CDN代理
13.226.17.97 cdn.unrealengine.com
通过一系列操作,可以提升在SetUp.bat完整拉取虚幻引擎的速度,但是因为某些不可描述的原因不会提升很高,总比28kb/s好
安装完成后利用VisualStudio打开UE5.sln看到如下图所示
二、安装MySql
基本需要的引擎已经安装好了,需要安装数据库服务器,我这里使用的是Mysql-8.0.32
安装步骤参考另一位大神的文档:MySQL安装配置教程(超级详细、保姆级)
这个过程中因为Windows10的部分管理员权限问题,导致mysqld初始化安装失败。
我们需要在C:\\Windows\\System32里面用管理员身份打开cmd命令行,然后使用如下命令进入mysql安装包,不能用简单的cd命令操作,要加上/d
cd /d H:\\mysql-8.0.32-winx64\\bin
最后结果如图
其他的参考大神的mysql安装步骤解决就行了。
三、安装DBeaver
开启了mysql服务之后,不习惯命令行操作的朋友可以利用DBeaver进行数据库内容管理操作,和同事讨论了用轻量级的navicat,总感觉差点意思,毕竟现在是做大数据的,要集成很多jdbc功能,用DBeaver更合适我,所以就安装了。
DBeaver下载地址:DBeaver官方下载链接
安装完成之后直接链接本地Mysql服务,如图所示
前期的准备工作就已经完成了,我们编译一下UE5的源码,弄个基于官方第三人称模板开发的DEMO,包含登录功能、登录服务器关卡等功能的DEMO看一下。
四、准备工作完成进行简单的DEMO
(1)设置UE5引擎编辑器为启动项目
选择engine->UE5,设为启动项目
选择debug editor,点击生成。根据机器配置不同,生成新的项目时间为几个小时到几天不等(实在太大了,狂吃我机器性能)
(2)生成项目的同时不能闲着,创建用户信息数据库,并进行数据插入
create database game_users;
create table if not exists game_users.user_base_info(
`id` INT NOT NULL AUTO_INCREMENT COMMENT '主键',
`user_name` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '用户名',
`pass_word` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '用户密码',
`user_state` int NOT NULL DEFAULT 0 COMMENT '用户状态,0:不可用,1:可用',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '记录创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '记录更新时间',
PRIMARY KEY (`id`)
)ENGINE = INNODB charset = utf8mb4 COMMENT '用户信息表';
建表完成插入测试语句,如图所示
insert into game_users.user_base_info (user_name,pass_word,user_state) values ('demo1','demo1',1);
到此为止,基础的测试数据执行完成,数据准备工作就绪。
UE4 C++网络编程基础知识总结
角色类型
每个Actor都存在两个属性来判断当前的Actor在 服务器 / 客户端上的身份类型:
Role: 判断当前角色在本地端的身份类型。
RemoteRole:判断当前角色在远端身份类型(当是客户端时,远端为服务端,当是服务端时,远端是客户端)。
网络角色类型:
Simulated : 由服务器进行数据发送,当前终端进行操控模拟。操控来源于服务器。
Autonomous: 由当前终端实例进行操控。操控来源于真人。
Authority:服务器端存在标记,表明当前Actor存在于服务器。
- Role 类型分布
Simulate | Autonomous | Authority | |
---|---|---|---|
服务器 | 不存在 | 不存在 | 服务器拥有复制权限的权威性 |
客户端 | 服务器在当前引擎实例中模拟的玩家角色 | 当前引擎实例中由真人操控的角色 | 不存在 |
Role 可以用来清晰的裁定当前角色是否在服务器,一般HasAuthority就是检查此变量。
我们也可以用来裁定当前对象是否有权发动RPC。Simulate对象无法执行RPC函数。
- RemoteRole类型分布
Simulate | Autonomous | Authority | |
---|---|---|---|
服务器 | 当前Actor在远端是模拟的身份 | 当前Actor自主权操控权在远端 | 不存在 |
客户端 | 不存在 | 不存在 | 拥有当前Actor复制权限的权威性 |
RemoteRole可以用来裁定当前Actor另一端的身份类别。如果由服务器构建的对象,则远端均是Simulate。
Remote Procedure Call (RPC)
RPC,远程调用,指在本机上调用函数,但在其他机器上远程执行函数。RPC函数可以允许客户端或服务器通过网络连接相互发送消息。
RPC执行分三种形式:
- 服务端执行(Server) : 在客户端上调用,在服务器端执行
- 客户端执行(Client): 在服务器端调用,在客户端执行
- 所有终端执行(Multicast):在服务器端调用,在所有终端执行
C++ 实现:
函数说明符 | 效果 |
---|---|
Server | 此函数仅在服务器上执行。用于声明名称与主函数相同的附加函数,但是末尾添加了_Implementation ,是写入代码的位置。必要时,此自动生成的代码将调用 _Implementation 方法。 |
Client | 此函数仅在拥有在其上调用此函数的对象的客户端上执行。用于声明名称与主函数相同的附加函数,但是末尾添加了Implementation 。必要时,此自动生成的代码将调用Implementation 方法。 |
NetMulticast | 此函数将在服务器上本地执行,也将复制到所有客户端上,无论该Actor的 NetOwner 为何。 |
Reliable | 此函数将通过网络复制,并且一定会到达,即使出现带宽或网络错误。仅在与Client 或Server 配合使用时才有效。 |
Unreliable | 此函数将通过网络复制,但是可能会因带宽限制或网络错误而失败。仅在与Client 或Server 配合使用时才有效。 |
WithValidation | 用于声明名称与主函数相同的附加函数,但是末尾需要添加_Validate 。此函数使用相同的参数,但是会返回bool ,以指示是否应继续调用主函数。 |
服务端执行:
- 函数声明
UFUNCTION(BlueprintCallable, Server, Reliable, WithValidation, Category = "ALS|Character States")
void Server_SetDesiredGait(EALSGait NewGait);
- 函数实现
bool AALSBaseCharacter::Server_SetDesiredGait_Validate(EALSGait NewGait)
// 这里写服务器进行检测的逻辑
return true;
void AALSBaseCharacter::Server_SetDesiredGait_Implementation(EALSGait NewGait)
// 这里写你的逻辑
SetDesiredGait(NewGait);
客户端执行:
- 函数声明
UFUNCTION(BlueprintCallable, Client, Reliable, Category = "ALS|Character States")
void Client_SetDesiredGait(EALSGait NewGait);
- 函数实现
oid AALSBaseCharacter::Client_SetDesiredGait_Implementation(EALSGait NewGait)
// 这里写你的逻辑
所有终端执行 :
- 函数声明
UFUNCTION(BlueprintCallable, NetMulticast, Reliable, Category = "ALS|Character States")
void NetMulticast_SetDesiredGait(EALSGait NewGait);
- 函数实现
void AALSBaseCharacter::NetMulticast_SetDesiredGait_Implementation(EALSGait NewGait)
// 这里写你的逻辑
RPC 调用注意事项 :
- 它们必须从Actor上调用
- Actor必须被复制。
- 如果RPC是从服务器调用并在客户端上执行,则只有实际拥有这个Actor的客户端才会执行函数。
- 如果RPC是从客户端调用并在服务器上执行,客户端就必须拥有调用RPC的Actor。
- 多播RPC则是个例外: ① 如果他们是从服务器调用,服务器将在本地和所有已连接的客户端上执行它们。 ②如果它们是从客户端调用,则只在本地而非服务器上执行。③ 多播事件限制机制:在特定的Actor的网络更新期内,多播函数将不会复制两次以上。
RPC调用情况分析
- 从服务器调用的 RPC
Actor 所有权 | 未复制 | NetMulticast | Server | Client |
---|---|---|---|---|
Client-owned actor | 在服务器上运行 | 在服务器和所有客户端上运行 | 在服务器上运行 | 在 actor 的所属客户端上运行 |
Server-owned actor | 在服务器上运行 | 在服务器和所有客户端上运行 | 在服务器上运行 | 在服务器上运行 |
Unowned actor | 在服务器上运行 | 在服务器和所有客户端上运行 | 在服务器上运行 | 在服务器上运行 |
- 从客户端调用的 RPC
Actor 所有权 | 未复制 | NetMulticast | Server | Client |
---|---|---|---|---|
Owned by invoking client | 在执行调用的客户端上运行 | 在执行调用的客户端上运行 | 在服务器上运行 | 在执行调用的客户端上运行 |
Owned by a different client | 在执行调用的客户端上运行 | 在执行调用的客户端上运行 | 丢弃 | 在执行调用的客户端上运行 |
Server-owned actor | 在执行调用的客户端上运行 | 在执行调用的客户端上运行 | 丢弃 | 在执行调用的客户端上运行 |
Unowned actor | 在执行调用的客户端上运行 | 在执行调用的客户端上运行 | 丢弃 | 在执行调用的客户端上运行 |
数据同步
Actor必须满足在网络上被复制,设置的参数需要开启复制,参数的修正必须在服务器端被修改,才可以在网络上进行同步。
蓝图参数同步有两种方式 : Replicated, RepNotify
- Replicated, 同步数据,但没有通知, 无法直接通过参数修改驱动逻辑
- RepNotify, 同步数据,并生成通知,进行更新通知(向所有终端通知,满足相关性)
属性标签 | 效果 |
---|---|
Replicated | 属性应随网络进行复制。 |
ReplicatedUsing=FunctionName | ReplicatedUsing 说明符指定一个回调函数,其在属性通过网络更新时执行。(向除去服务器外的所有终端进行通知。) |
属性复制条件
属性复制被注册后, 无法进行取消。所有属性默认的复制条件是:不发生变化不进行复制。我们可以使用UE提供的条件进行修正,通过条件的约束设定可以有效的节约网络带宽。
条件 | 说明 | 事例 |
---|---|---|
COND_InitialOnly | 该属性仅在初始数据组尝试发送 | 玩家的名字等 |
COND_OwnerOnly | 该属性仅发送至 actor 的所有者 | 手持武器子弹剩余数量等 |
COND_SkipOwner | 该属性将发送至除所有者之外的每个连接 | |
COND_SimulatedOnly | 该属性仅发送至模拟 actor | FPS自己只需要看到手臂,但其他玩家需要看到自己的整个身体等 |
COND_AutonomousOnly | 该属性仅发送给自主 actor | |
COND_SimulatedOrPhysics | 该属性将发送至模拟或 bRepPhysics actor | |
COND_InitialOrOwner | 该属性将发送初始数据包,或者发送至 actor 所有者 | |
COND_Custom | 该属性没有特定条件,但需要通过 SetCustomIsActiveOverride 得到开启/关闭能力 |
C++ :
UPROPERTY(BlueprintReadOnly, Replicated, Category = "ALS|Ragdoll System")
FVector TargetRagdollLocation = FVector::ZeroVector;
UPROPERTY(BlueprintReadOnly, Category = "ALS|State Values", ReplicatedUsing = OnRep_RotationMode)
EALSRotationMode RotationMode = EALSRotationMode::LookingDirection;
绑定参数到复制参数列表:
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
void AALSBaseCharacter::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(AALSBaseCharacter, TargetRagdollLocation);
DOREPLIFETIME_CONDITION(AALSBaseCharacter, RotationMode, COND_SkipOwner);
需要使用属性复制条件进行优化就使用:DOREPLIFETIME_CONDITION
,不需要就使用:DOREPLIFETIME
。
网络关联性
UE网络模块考虑到通信带宽问题,针对场景中的所有Actor数据更新制定了高效的同步方案,根据Actor的网络关联性,进行数据更新,这样可以有效的规避无用数据更新造成的通信带宽压力。
网络关联性约束了Replicated是否进行广播,约束关系受限于关联性规则。
关联性用途:用来解决同步过程中,无关数据的传递带宽压力,用来筛选有意义内容进行同步,表现同步信息是否同步给目标。
关联性规则:
- AlwaysRelevant, 相关性最大,勾选之后,所有终端永远会收到此Actor同步信息,例如烟雾弹。
- NetUseOwnerRelevancy, 如果有拥有者Owner,则使用所有者的相关性(包括剔除距离约定)。此检查在第一项之后完成。例如角色拾取枪支。
- OnlyRelevantToOwner, 只与当前所有者相关,同步数据只发给所有者,不发送任何人,没有所有者不同步。
- Actor 出现依 附关系,则关联性取决于基础的Actor相关性。
- 如果Actor不可见(bHidden = true )并且 Root Component 没有碰撞,则不具备相关性。
- 采集距离(Net Cull Distance Squared)在采集范围内具备关联性。
- 约束层级关系(如果RPC勾选了Reliable则关联性设置失效)
以上是关于UE4/5 C++网络服务器编程纪录零--准备篇的主要内容,如果未能解决你的问题,请参考以下文章