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

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

相关文章

Nginx location匹配模式与规则详解

《Nginxlocation匹配模式与规则详解》:本文主要介绍Nginxlocation匹配模式与规则,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、环境二、匹配模式1. 精准模式2. 前缀模式(不继续匹配正则)3. 前缀模式(继续匹配正则)4. 正则模式(大

使用Python开发一个带EPUB转换功能的Markdown编辑器

《使用Python开发一个带EPUB转换功能的Markdown编辑器》Markdown因其简单易用和强大的格式支持,成为了写作者、开发者及内容创作者的首选格式,本文将通过Python开发一个Markd... 目录应用概览代码结构与核心组件1. 初始化与布局 (__init__)2. 工具栏 (setup_t

Spring Shell 命令行实现交互式Shell应用开发

《SpringShell命令行实现交互式Shell应用开发》本文主要介绍了SpringShell命令行实现交互式Shell应用开发,能够帮助开发者快速构建功能丰富的命令行应用程序,具有一定的参考价... 目录引言一、Spring Shell概述二、创建命令类三、命令参数处理四、命令分组与帮助系统五、自定义S

Python通过模块化开发优化代码的技巧分享

《Python通过模块化开发优化代码的技巧分享》模块化开发就是把代码拆成一个个“零件”,该封装封装,该拆分拆分,下面小编就来和大家简单聊聊python如何用模块化开发进行代码优化吧... 目录什么是模块化开发如何拆分代码改进版:拆分成模块让模块更强大:使用 __init__.py你一定会遇到的问题模www.

Spring Security基于数据库的ABAC属性权限模型实战开发教程

《SpringSecurity基于数据库的ABAC属性权限模型实战开发教程》:本文主要介绍SpringSecurity基于数据库的ABAC属性权限模型实战开发教程,本文给大家介绍的非常详细,对大... 目录1. 前言2. 权限决策依据RBACABAC综合对比3. 数据库表结构说明4. 实战开始5. MyBA

使用Python开发一个简单的本地图片服务器

《使用Python开发一个简单的本地图片服务器》本文介绍了如何结合wxPython构建的图形用户界面GUI和Python内建的Web服务器功能,在本地网络中搭建一个私人的,即开即用的网页相册,文中的示... 目录项目目标核心技术栈代码深度解析完整代码工作流程主要功能与优势潜在改进与思考运行结果总结你是否曾经

Linux系统配置NAT网络模式的详细步骤(附图文)

《Linux系统配置NAT网络模式的详细步骤(附图文)》本文详细指导如何在VMware环境下配置NAT网络模式,包括设置主机和虚拟机的IP地址、网关,以及针对Linux和Windows系统的具体步骤,... 目录一、配置NAT网络模式二、设置虚拟机交换机网关2.1 打开虚拟机2.2 管理员授权2.3 设置子

Spring Boot + MyBatis Plus 高效开发实战从入门到进阶优化(推荐)

《SpringBoot+MyBatisPlus高效开发实战从入门到进阶优化(推荐)》本文将详细介绍SpringBoot+MyBatisPlus的完整开发流程,并深入剖析分页查询、批量操作、动... 目录Spring Boot + MyBATis Plus 高效开发实战:从入门到进阶优化1. MyBatis

Python基于wxPython和FFmpeg开发一个视频标签工具

《Python基于wxPython和FFmpeg开发一个视频标签工具》在当今数字媒体时代,视频内容的管理和标记变得越来越重要,无论是研究人员需要对实验视频进行时间点标记,还是个人用户希望对家庭视频进行... 目录引言1. 应用概述2. 技术栈分析2.1 核心库和模块2.2 wxpython作为GUI选择的优

利用Python开发Markdown表格结构转换为Excel工具

《利用Python开发Markdown表格结构转换为Excel工具》在数据管理和文档编写过程中,我们经常使用Markdown来记录表格数据,但它没有Excel使用方便,所以本文将使用Python编写一... 目录1.完整代码2. 项目概述3. 代码解析3.1 依赖库3.2 GUI 设计3.3 解析 Mark