以下内容都是基于Unreal Engine版本:4.18.2-0+++UE4+Release-4.18。
操作系统是:Window10 x64专业版。
编译工具:VS2017 企业版。
在上一篇文章UE4 ShooterGame Standalone Dedicated Server(Windows & Linux),主要讲述如何编译、部署一台ShooterGame服务器。这篇文章里会讲述一些UE4 Server相关的基础知识,以及如何修改ShooterGame Dedicated Server的内容为我们所用。
阅读过一点ShooterGame代码再读此文效果更加,后面Server若无特指,均表示Dedicated Server。
参考文献:https://docs.unrealengine.com/latest/CHN/Gameplay/Networking/Server/index.html
UE4关于ENetRole的解释
UE4的Client与Server公用一套代码,从编译结果的角度来看就是他们依赖同一个.pak文件。所以在UE4内部,对于如何区分Client/Server有一套完整的机制,以Dedicated Server模式为例
enum ENetRole:
ROLE_None – 这个对象没有网络连接,并不需要在C/S同步;
ROLE_SimulatedProxy – 模拟对象,仅仅用于Client本地模拟,它的状态不能在本地主动被改变(只能被动改变);
ROLE_AutonomousProxy – 自治对象,在Client接受各种输入,并且主动与Server通信;
ROLE_Authority – Server权威,它表示一个真正的对象(为什么这么说呢?UE4官方建议:只有Server(包含Listen Server后文会提及)能产生需要在C/S间复制对象。如果Client自主产生了一个对象,那么它仅仅只会在创建它的Client上存在);
概念有点绕,举个实际的例子,比如我有二个客户端ClientA与ClinetB,他们通过Dedicated Server进行游戏,对应的ENetRole如下:
Server侧 – ClientA.Role = ROLE_Authority;ClientB.Role = ROLE_Authority;
ClientA侧 – ClientA.Role = ROLE_AutonomousProxy;ClientB.Role = ROLE_SimulatedProxy;
ClientB侧 – ClientA.Role = ROLE_SimulatedProxy;ClientB.Role = ROLE_AutonomousProxy;
简单的一句话概括就是:服务器拥有对象实体是ROLE_Authority,客户端拥有的本地对象是ROLE_AutonomousProxy,客户端展现的其他客户端对象是ROLE_SimulatedProxy。
UE4关于ENetMode的解释
ENetMode:
NM_Standalone – 单机服务器模式,可以有1-n个本地客户端,无网络连接;
NM_DedicatedServer – 专用服务器模式,没有本地客户端;
NM_ListenServer – 监听服务器模式,一个本地客户端做主机;
NM_Client – 客户端,连接远程服务器;
那么问题来了,ENetRole与ENetMode是不是有部分定义重复了?
不是。一些个人理解:ENetRole主要是用在和玩家角色直接相关内容上,往往会通过if (Role < ROLE_Authority)来判断代码是否需要对服务端进行RPC;ENetMode更宏观,提供了区分Listen Server与Dedicated Server的方法。例如,一些粒子效果不需要在Dedicated Server展现,但是需要在Listen Server展现时,可以依靠ENetMode区分。反过来看,ENetRole提供了区分本地操控客户端(ROLE_AutonomousProxy )与本地模拟客户端(ROLE_SimulatedProxy )的方法。
UE4 ShooterGame Server启动流程
ShooterGame Server的启动流程依附与UE4自身一套流程,UE4的一些基础类比如UGameInstance,AGameMode会暴露一些虚函数,ShooterGame继承这些类,并按需实现虚函数。实现中通过super关键字来调用基类的实现,达到嵌入ShooterGame自身逻辑的目的,附图:
图很简单,里面省略了很多资源的初始化与加载。实际我们需要关心的,GEngine是全局唯一的,然后创建GameInstance,确定游戏地图后才会创建GameMode,在GameMode里面初始化GameState与GameSession。可以看到核心还是Main()->Init()->Loop()的套路。
修改ShooterGame Server
先分析一下,如果目标是:
- 将ShooterGame Server作为一场战斗的核心逻辑;
- 战前匹配,战后结算放置到自定义(非UE4)的服务器;
需要解决的问题(只列了与ShooterGame 相关的):
- 需要一个控制服务器(ControlServer),来管理ShooterGame Server,这里包含了Socket通信,状态控制等问题;
- 搞清楚ShooterGame Client与Server的握手原理,因为需要加入自定义的参数,目的是为了验证客户端合法性;
- ShooterGame的状态扭转流程;
问题1限于篇幅后续有时间会再出一篇博文详细说明。先分析问题2、3:
UE4 Client连接Server步骤
- Client发送连接请求;
- Server如果接受请求,发送当前地图;
- Server等待Client加载地图;
- 加载完成后,Server会调用AGameMode::PreLogin;
- ShooterGame对其进行了重载AShooterGameMode::PreLogin,你可以在这里加入自己的自定义参数进行验证;
- 然后服务器会调用 AGameMode::Login;
- ShooterGame并没有重载这个函数;
- 该函数的作用是创建一个 PlayerController,可用于在今后复制到新连接的客户端。成功接收后,这个 PlayerController 将替代客户端的临时 PlayerController (之前被用作连接过程中的占位符);
- 此时将调用 APlayerController::BeginPlay。应当注意的是,此时调用 RPC 函数尚存在安全风险。您应当等待 AGameMode::PostLogin 被调用完成;
- 如果一切顺利,AGameMode::PostLogin 将被调用;
- ShooterGame对其进行了重载AShooterGameMode::PostLogin;
- 在这里处理一些成功Login之后的逻辑,也可以放心的让服务器在此 PlayerController 上开始调用 RPC 函数;
ShooterGame游戏状态流程
核心的处理函数是AShooterGameMode::DefaultTimer,它是一个定时器函数,每1秒会执行一次。整个流程参考下图
需要做的:
- 在WaitToStart加入自己等待开始的逻辑;
- 在进入WaitingPostMatch前,进行战斗结算与空闲通知,断开与玩家的socket连接;
写的比较杂,在年前看没有时间把控制服务器相关的东西整理一下总结出来。UE4总体来说是一个“坑”比较多的引擎,需要时间去积累经验,社区资源比较匮乏,有些问题官方文档说的不详细,经常导致一个问题需要解决很久的情况。不过天道酬勤,新的一年加油。
(全文结束)
转载文章请注明出处:漫漫路 - lanindex.com
thanks