组件网络编程概述
Flexi引擎提供了基于组件对象的网络编程框架,可以简化多人游戏编程的复杂度,使用一份代码就可以编译出客户端和服务器。本文档对多人游戏编程中用到的概念进行概述说明。
客户端-服务器网络模型
Flexi引擎使用客户端-服务器模型来实现网络游戏的开发。
客户端-服务器模型简称为C/S模式。C/S模式是一种网络架构,而不是指具体的计算机设备,它把客户端(Client)与服务器(Server)区分开来。对于网络游戏来说,每个游戏客户端都可以向位于网络上的游戏服务器发送请求,服务器收到请求后,执行某项功能或其他任务,游戏客户端和服务端通过网络来共享游戏世界内的状态信息。 游戏服务器在网络游戏中具有权威性,它保持了一个最正确且真实的游戏状态。服务器是多人游戏实际发生的地方。客户端会远程控制服务器上的角色,通过网络请求使其执行游戏操作。服务器会将游戏状态信息同步到各个客户端,通知对应GameObject,GameObject行为,以及不同变量应拥有的值。使用这些信息使客户端能模拟游戏正在发生的情况。
注意:网络游戏中,客户端和服务器通过网络设备连接起来,形成互相交互的系统。服务器处理游戏逻辑,客户端渲染画面显示给玩家。
网络编程基础概念
下面将详细介绍Flexi引擎内驱动网络Gameplay的概念,为理解多人游戏开发和构建游戏提供帮助。
网络模式和服务器类型
Flexi引擎有多种网络模式和服务器类型,适用多种情况下的网络游戏开发和运行。
网络模式 | 说明 |
---|---|
客户端(Client) | 作为游戏客户端运行,不会运行服务端游戏逻辑。 |
监听服务器(Listen Server) | 作为网络多人游戏的服务器运行。其接受远程客户端连接,且直接在服务器上拥有本地玩家。此模式通常用于临时合作和竞技多人游戏。 |
专属服务器(Member Server) | 作为网络多人游戏的服务器运行。其接受远程客户端连接,但无本地玩家,它废弃了图形、音效、输入和其他面向玩家的功能。此模式通常用于需要更持久、安全和大型多人在线游戏。 |
带转发服务的专属服务器(Entry Server-Member Server) | 包含专属服务器功能,另外还携带转发服务器,为大型多人在线游戏提供安全的接入方案。 |
单机独立端(Standalone) | 单机模式运行,可以认为它既运行服务器逻辑又运行客户端逻辑。不接受远程客户端的连接。 |
GameObject网络同步
GameObject网络同步(Network Synchronization)是指在不同机器间GameObject状态信息的复制处理。多数GameObject默认不会启用复制,且将本地执行所有功能。调用GameObject类的 SetAddNetScene
函数,可启用GameObject网络同步。
同步功能 | 说明 |
---|---|
创建和销毁 | 服务端创建带有网络同步的GameObject时,会在所有连接客户端上自动生成相同的GameObject网络代理。游戏运行中,还会将信息同步到这些远程代理。若销毁服务器上的GameObject,则将自动销毁所有连接客户端上的网络代理。 |
移动同步 | 若GameObject启用了移动同步,或在C++中调用 SetNetMovement 函数,将自动同步位置、旋转和速度。 |
属性同步 | 带有NetProperty标记的属性变量的值变更时,其将自动从对应GameObject同步这些值到网络代理。 |
远程过程调用 | RPC是传输到网络游戏中特定机器的特殊函数。无论初始调用RPC的是哪台机器,其实现仅在目标机器上运行。此类RPC可指定为服务器(仅在服务器上运行)、客户端(仅在GameObject拥有的客户端上运行),或在连接会话的所有机器上运行。 |
注意:角色的部分常用功能通常不会被网络同步。
- 骨架网格体和静态网格体组件
- 材质
- 动画树
- 粒子系统
- 音效
- 物理对象
此类项目均在所有客户端上单独运行。但是,若同步此类视觉元素的变量,则可确保所有客户端都具有相同信息,从而以大致相同的方式进行模拟网络角色和控制类型。
属性变量网络同步
在C++中使用对应 FX_PROPERTY
宏内的 NetProperty
说明符,将它们指定为网络同步。GameObject上变量的值变更时,其信息将自动从服务端GameObject发送到客户端网络代理。
网络同步回调函数
在C++中使用变量的 FX_PROPERTY
宏的 NetPropertyNotify
说明符可指定同步回调函数。指定在GameObject接收属性变量的网络同步变化时要调用的函数。NetPropertyNotify
在变量更新时本地触发。使用 NetPropertyNotify
来实现变量更新时的回调,相较于使用RPCs来实现,可减少网络开销。因为属性同步是一个固定开销,使用 NetPropertyNotify
不会增加任何额外的网络开销。
网络同步代码说明
多人游戏demo使用属性变量的网络同步来处理玩家生命值。
class LFPSDemoPawn : public LRole
{
DECLARE_LCLASS(LFPSDemoPawn, LRole, COMPILED_IN_FLAGS(0))
public:
LFPSDemoPawn();
virtual ~LFPSDemoPawn();
// 当前生命值
FX_PROPERTY(Type = float, GetFunc = GetHp, SetFunc = SetHp, Name = CurrentHp,
NetProperty, NetPropertyNotify = OnHpChangedNotify)
float m_fCurrentHp;
}
void LFPSDemoPawn::OnHpChangedNotify()
{
g_pCore->ExecAsyncProc("share\\ui\\form_state_bar.lua", "refresh_hp",
CVarList() << m_fCurrentHp << m_fMaxHp);
}
相关性-视野管理
GameObject之间的相关性决定是否需要网络同步。某个时刻特定客户端中,不会存在所有服务端上的GameObject。服务器视野管理会剔除被认为不相关的GameObject,此操作可节约带宽,提高效率。若GameObject未被玩家拥有,且不在玩家附近,将其被视为不相关,而不会进行网络同步。但是,不相关GameObject会存在于服务器上,且会影响游戏状态,但在玩家靠近前不会向客户端发送信息。可使用 SetServerVisualRange
函数设置GameObject的视野距离,来控制相关性。
注意:是否网络同步和服务器视野距离可通过GameObject属性面板设置。
网络控制方式
网络控制方式(NET_CONTROL_TYPE) 将决定网络游戏期间控制GameObject的是服务器还是客户端。NET_CONTROL_TYPE::SERVER_CONTROLLED 被认为是服务器控制GameObject,并可将其信息复制到其他客户端机器上。其由LocalControlType变量进行追踪,可取以下值:
网络控制方式 | 说明 |
---|---|
无(NONE) | GameObject在网络游戏中无角色,不会复制。 |
服务器控制(SERVER_CONTROLLED) | GameObject为服务器控制,会将其信息复制到其他机器上的网络代理。 |
非玩家控制(CLIENT_NON_PLAYER_CONTROLLED) | GameObject为网络代理,由服务器上的GameObject完全控制。网络游戏中的交互对象等多数GameObject将在客户端上显示为非玩家控制。 |
玩家控制(CLIENT_PLAYER_CONTROLLED) | GameObject为网络代理,能够本地执行部分功能,但会接收服务端的矫正。它通常为玩家直接控制的GameObject所保留,如Pawn。 |
远程过程调用
远程过程调用(Remote Procedure Call),简称RPC,类似于网络消息处理过程。在基于网络对象和对象反射的基础上,我们可以对GameObject和Component对象发送网络消息并可以在另一端处理它。在游戏逻辑的具体调用上,会根据RPC类型,在网络连接的特定端上发生。三种RPC类型如下所示。
RPC类型 | 说明 |
---|---|
Server | 客户端向服务端发RPC请求。使用函数LScene::ServerRemoteFunction。 |
Client | 服务端向客户端发RPC请求。使用函数LScene::ClientRemoteFunction。若GameObject无拥有连接,将不会执行此逻辑。 |
Client-Broadcast | 服务端向客户端发RPC广播请求。向GameObject所有连接的客户端发RPC请求。使用函数LScene::ClientRemoteFunction。 |
C++代码示例
使用FX_METHOD说明符,将函数指定为实体方法;
使用LScene::ServerRemoteFunction向服务器发起RPC,它会在服务器的GameObject上调用指定实体方法;
使用LScene::ClientRemoteFunction向客户端发起RPC,它会在客户端的GameObject上调用指定实体方法。
// LMyGameObject
class LMyGameObject : public LGameObject
{
public:
void CallServerFunction();
void CallClientFunction();
FX_METHOD()
void MyFunction_Server(int n);
FX_METHOD()
void MyFunction_Client(int n);
}
void LMyGameObject::CallServerFunction()
{
auto* pScene = GetScene();
CVarList var;
var.AddInt(10);
pScene->ServerRemoteFunction(this, "MyFunction_Server", var);
}
void LMyGameObject::CallClientFunction()
{
auto* pScene = GetScene();
CVarList var;
var.AddInt(10);
pScene->ClientRemoteFunction(this, "MyFunction_Client", var);
}
void LMyGameObject::MyFunction_Server(int n)
{
// 服务器玩法逻辑
}
void LMyGameObject::MyFunction_Client(int n)
{
// 客户端玩法逻辑
}