随便聊聊网络游戏开发模式

2024-06-19 03:04

本文主要是介绍随便聊聊网络游戏开发模式,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

本文仅是闲聊罢了,并非开发教程,有意依此学习的同学注意一下.

就网络游戏开发而言,目前主流的同步方式大概是以下两种:

  • 帧同步

顾名思义,就是按"帧"(一般指逻辑帧)来进行网络同步,一般实现上,都是客户端按"帧"来发送自己的操作数据(无操作也是一种操作(也要发送)),服务器收取到所有客户端的操作数据之后统一进行分发,客户端收取到分发数据之后则进行"帧"模拟,由于只同步操作数据(客户端各自进行逻辑模拟),为了能让各个客户端保持同步,对于客户端逻辑的编写有比较高的确定性需求(随机、浮点等不确定因素都需要进行确定性处理,当然纯表现类的内容可以忽略),游戏项目中比较典型使用帧同步技术的应该就是"王者荣耀"(以下简称"王者")了.

  • 状态同步

相比于帧同步,状态同步则是按"状态"(动态单位的位置速度等数据)来进行网络同步,一般实现上,服务器都是在游戏一开始进行一次基准"状态"同步(给客户端),之后在游戏过程中按需进行增量"状态"同步,客户端同样按需对接收到的"状态"数据进行设置处理(也可做些插值外推等操作),一般数据量较大的游戏(尤其是涉及各类LOD处理时),逻辑是很难做到高确定性的,所以帧同步基本就不能使用了,其实"王者"最初选用帧同步应该也并非完全出自技术层面的考量,更多的可能还是当时项目周期的限制,如果现在回过头来重新评估的话,"王者"改用状态同步应该也是可行的.


有些游戏也可能同时使用 帧同步 和 状态同步,具体则根据游戏实际需求来定了,不过不论哪种同步方式,都有一个棘手的问题需要处理:主控端的(本地)操作响应问题:

在帧同步中,由于(本地)操作需要经历上传接收的网络流程才能进行逻辑模拟,所以操作延迟基本是不可避免的,虽然可以做一些本地(预测)表现来优化(譬如技能前摇动作等等),但是因为高确定性的要求,逻辑可优化的空间比较小.

而在状态同步中,如果逻辑允许,客户端本地可以直接执行本地操作(逻辑模拟)并将操作数据上传,服务器接收到客户端操作数据后执行逻辑模拟,然后将结果下发,客户端再依据接收到的下发结果对操作进行"纠错重放",如果一切顺利,主控端将感受不到延迟.

对于诸如云游戏之类将客户端直接作为终端显示的同步模式,更多的考量应该是流量控制方面的(而非具体的同步技术),同时因为同步模式的限制,这类游戏对主控端的(本地)操作响应基本没有优化空间,所以一般来讲这类游戏只适合对(本地)操作响应要求不高的游戏.


接着我们来聊下一个更细节的问题:

如何进行实际的网络同步呢 ?

一般来讲,主流的网络同步操作如下(省略加解密之类的操作):

  • 定义生成数据格式(使用 Protobuf 等工具)
  • 使用对应数据格式编解码数据
  • TCP/UDP 发送接收数据
    • (TCP 可靠但不灵活,游戏中使用 TCP 的话可能会出现更多的延迟问题;UDP 不可靠但灵活,如果游戏使用 UDP 的话可能需要去自行实现 TCP 的某些子集功能以达到一定的可靠性要求,总的来说各有利弊)

当然,上述的流程比较底层,一般游戏开发都会对其进行上层封装,最终的结果大概是以下两个接口(伪代码):

  void SendMessage(Proto);void ReceiveMessage(Proto);

再以 服务器开发 与 客户端开发 的角度来看下网络游戏的开发流程(仅涉及程序开发流程):

  • 服务器开发 与 客户端开发 对齐逻辑开发需求
  • 拆解 服务器程序内容 与 客户端程序内容
  • 定义相关同步协议
  • 服务器开发 与 客户端开发 各自编写代码并持续进行阶段联调

可以看到,该流程下 服务器端代码 与 客户端代码 是完全隔离的,双方仅仅通过协议进行协作交流(其他方面双方可以一无所知).

这本身其实是一个很好的开发范式,很多网络程序也是如此实践的,但对于网络游戏而言,有时候却捉襟见肘了:

考虑我们要开发网络游戏中的技能系统,首当其冲的一个问题是如何进行伤害判定,过去像 MMO 一类的游戏中,伤害判定一般都是在服务器进行的,判定方式往往也很简单,基本上就是检测一些距离方位的限制,同时为了增强手感,客户端还会配合进行一些攻击帧的表现处理,总的来说使用上面那种开发"隔离"的方式还能应付.

但是我们对新的技能系统有更高的需求,我们希望伤害判定能够严格按照动画中配置的攻击帧数进行,同时动画本身也可能带有 Root Motion 和各类动画姿态处理(IK等),此时我们便遇到难题了,摆在我们面前的选项似乎只剩两个:

  • 服务器实现相同一套动画系统(A 方案)
  • 伤害判定转移至客户端进行(B 方案)

两种方案都不尽如人意.

因为 路径依赖 的关系,很多项目可能会选择 B 方案,毕竟 A 方案基本没有可行性,尤其是当项目有之前的服务器继承代码时,而我们又需要坚持上述的开发"隔离"模式 …

但实际上,如果我们抛弃所谓的开发"隔离",我们还有更好的选择:

  • DedicatedServer

所谓的 DedicatedServer,即专用服务器,可以理解为游戏逻辑在开发上不再"隔离",而是同时支持服务器和客户端,由于代码基是相同的,基础功能自然也是相同的,上面提到的动画难题也便迎刃而解了.

说的有些抽象,我们拿 UE 的同步框架来举个例子:

先看下 ENetMode:

enum ENetMode
{/** Standalone: a game without networking, with one or more local players. Still considered a server because it has all server functionality. */NM_Standalone,/** Dedicated server: server with no local players. */NM_DedicatedServer,/** Listen server: a server that also has a local player who is hosting the game, available to other players on the network. */NM_ListenServer,/*** Network client: client connected to a remote server.* Note that every mode less than this value is a kind of server, so checking NetMode < NM_Client is always some variety of server.*/NM_Client,NM_MAX,
};

国情关系,我们一般开发时都不太关心 NM_Standalone 和 NM_ListenServer,但实际从实践角度来讲,一个游戏本身同时支持单机游玩和联机游玩其实是件很正常的事情,但在我们先前的开发"隔离"模式下,同时支持单机游玩和联机游玩基本是不可能实现的 …

再来看下 ENetRole:

/** The network role of an actor on a local/remote network context */
UENUM()
enum ENetRole : int
{/** No role at all. */ROLE_None,/** Locally simulated proxy of this actor. */ROLE_SimulatedProxy,/** Locally autonomous proxy of this actor. */ROLE_AutonomousProxy,/** Authoritative control over the actor. */ROLE_Authority,ROLE_MAX,
};

这个是对 Actor 的网络角色的定义,也是代码同时支持 服务器 和 客户端 的关键,譬如我们做以下判断(伪代码):

if (ActorNetRole == ROLE_Authority)
{// ...
}

基本可以认为是在 服务器 端执行代码(而不会在客户端执行)

接着就是 RPC(远程过程调用)了, UE 中的 RPC 大概可以分为下面两个大类:

  • 按可靠性分

    • Reliable
    • Unreliable
  • 按同步方式分

    • Client
    • Server
    • NetMulticast

另一个重要概念就是 Replicate 了,可以理解为一种服务器向客户端自动同步数据的方式.

简单来说, RPC 可以理解为事件通知(或者狭义上类似于之前的那种协议同步方式(只是方式更简单一些)),Replicate 则可以理解为状态同步,仅会由服务器同步给客户端,简单来讲也可以通过 RPC 来模拟,但是逻辑上使用 Replicate 会更简单,并且是自动化的.


总结来看, DedicatedServer 是更广义上的一种网络游戏开发框架,先前的那种协议同步方式可以认为是其框架下的子集,相互"隔离"的网络游戏开发方式有其适用的场景,但是随着游戏复杂度的提高(因为游戏需求提高等原因), DedicatedServer 在架构层面的优势会愈加明显.

“兼听则明,偏信则暗”

这篇关于随便聊聊网络游戏开发模式的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1073830

相关文章

使用Go语言开发一个命令行文件管理工具

《使用Go语言开发一个命令行文件管理工具》这篇文章主要为大家详细介绍了如何使用Go语言开发一款命令行文件管理工具,支持批量重命名,删除,创建,移动文件,需要的小伙伴可以了解下... 目录一、工具功能一览二、核心代码解析1. 主程序结构2. 批量重命名3. 批量删除4. 创建文件/目录5. 批量移动三、如何安

Android 悬浮窗开发示例((动态权限请求 | 前台服务和通知 | 悬浮窗创建 )

《Android悬浮窗开发示例((动态权限请求|前台服务和通知|悬浮窗创建)》本文介绍了Android悬浮窗的实现效果,包括动态权限请求、前台服务和通知的使用,悬浮窗权限需要动态申请并引导... 目录一、悬浮窗 动态权限请求1、动态请求权限2、悬浮窗权限说明3、检查动态权限4、申请动态权限5、权限设置完毕后

Java实现状态模式的示例代码

《Java实现状态模式的示例代码》状态模式是一种行为型设计模式,允许对象根据其内部状态改变行为,本文主要介绍了Java实现状态模式的示例代码,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来... 目录一、简介1、定义2、状态模式的结构二、Java实现案例1、电灯开关状态案例2、番茄工作法状态案例

基于Python开发PPTX压缩工具

《基于Python开发PPTX压缩工具》在日常办公中,PPT文件往往因为图片过大而导致文件体积过大,不便于传输和存储,所以本文将使用Python开发一个PPTX压缩工具,需要的可以了解下... 目录引言全部代码环境准备代码结构代码实现运行结果引言在日常办公中,PPT文件往往因为图片过大而导致文件体积过大,

使用DeepSeek API 结合VSCode提升开发效率

《使用DeepSeekAPI结合VSCode提升开发效率》:本文主要介绍DeepSeekAPI与VisualStudioCode(VSCode)结合使用,以提升软件开发效率,具有一定的参考价值... 目录引言准备工作安装必要的 VSCode 扩展配置 DeepSeek API1. 创建 API 请求文件2.

基于Python开发电脑定时关机工具

《基于Python开发电脑定时关机工具》这篇文章主要为大家详细介绍了如何基于Python开发一个电脑定时关机工具,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1. 简介2. 运行效果3. 相关源码1. 简介这个程序就像一个“忠实的管家”,帮你按时关掉电脑,而且全程不需要你多做

Java中的Opencv简介与开发环境部署方法

《Java中的Opencv简介与开发环境部署方法》OpenCV是一个开源的计算机视觉和图像处理库,提供了丰富的图像处理算法和工具,它支持多种图像处理和计算机视觉算法,可以用于物体识别与跟踪、图像分割与... 目录1.Opencv简介Opencv的应用2.Java使用OpenCV进行图像操作opencv安装j

基于Qt开发一个简单的OFD阅读器

《基于Qt开发一个简单的OFD阅读器》这篇文章主要为大家详细介绍了如何使用Qt框架开发一个功能强大且性能优异的OFD阅读器,文中的示例代码讲解详细,有需要的小伙伴可以参考一下... 目录摘要引言一、OFD文件格式解析二、文档结构解析三、页面渲染四、用户交互五、性能优化六、示例代码七、未来发展方向八、结论摘要

在 VSCode 中配置 C++ 开发环境的详细教程

《在VSCode中配置C++开发环境的详细教程》本文详细介绍了如何在VisualStudioCode(VSCode)中配置C++开发环境,包括安装必要的工具、配置编译器、设置调试环境等步骤,通... 目录如何在 VSCode 中配置 C++ 开发环境:详细教程1. 什么是 VSCode?2. 安装 VSCo

C#图表开发之Chart详解

《C#图表开发之Chart详解》C#中的Chart控件用于开发图表功能,具有Series和ChartArea两个重要属性,Series属性是SeriesCollection类型,包含多个Series对... 目录OverviChina编程ewSeries类总结OverviewC#中,开发图表功能的控件是Char