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

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

相关文章

Redis Cluster模式配置

《RedisCluster模式配置》:本文主要介绍RedisCluster模式配置,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录分片 一、分片的本质与核心价值二、分片实现方案对比 ‌三、分片算法详解1. ‌范围分片(顺序分片)‌2. ‌哈希分片3. ‌虚

Python实例题之pygame开发打飞机游戏实例代码

《Python实例题之pygame开发打飞机游戏实例代码》对于python的学习者,能够写出一个飞机大战的程序代码,是不是感觉到非常的开心,:本文主要介绍Python实例题之pygame开发打飞机... 目录题目pygame-aircraft-game使用 Pygame 开发的打飞机游戏脚本代码解释初始化部

使用Python开发一个现代化屏幕取色器

《使用Python开发一个现代化屏幕取色器》在UI设计、网页开发等场景中,颜色拾取是高频需求,:本文主要介绍如何使用Python开发一个现代化屏幕取色器,有需要的小伙伴可以参考一下... 目录一、项目概述二、核心功能解析2.1 实时颜色追踪2.2 智能颜色显示三、效果展示四、实现步骤详解4.1 环境配置4.

Python使用smtplib库开发一个邮件自动发送工具

《Python使用smtplib库开发一个邮件自动发送工具》在现代软件开发中,自动化邮件发送是一个非常实用的功能,无论是系统通知、营销邮件、还是日常工作报告,Python的smtplib库都能帮助我们... 目录代码实现与知识点解析1. 导入必要的库2. 配置邮件服务器参数3. 创建邮件发送类4. 实现邮件

基于Python开发一个有趣的工作时长计算器

《基于Python开发一个有趣的工作时长计算器》随着远程办公和弹性工作制的兴起,个人及团队对于工作时长的准确统计需求日益增长,本文将使用Python和PyQt5打造一个工作时长计算器,感兴趣的小伙伴可... 目录概述功能介绍界面展示php软件使用步骤说明代码详解1.窗口初始化与布局2.工作时长计算核心逻辑3

RabbitMQ工作模式中的RPC通信模式详解

《RabbitMQ工作模式中的RPC通信模式详解》在RabbitMQ中,RPC模式通过消息队列实现远程调用功能,这篇文章给大家介绍RabbitMQ工作模式之RPC通信模式,感兴趣的朋友一起看看吧... 目录RPC通信模式概述工作流程代码案例引入依赖常量类编写客户端代码编写服务端代码RPC通信模式概述在R

python web 开发之Flask中间件与请求处理钩子的最佳实践

《pythonweb开发之Flask中间件与请求处理钩子的最佳实践》Flask作为轻量级Web框架,提供了灵活的请求处理机制,中间件和请求钩子允许开发者在请求处理的不同阶段插入自定义逻辑,实现诸如... 目录Flask中间件与请求处理钩子完全指南1. 引言2. 请求处理生命周期概述3. 请求钩子详解3.1

如何基于Python开发一个微信自动化工具

《如何基于Python开发一个微信自动化工具》在当今数字化办公场景中,自动化工具已成为提升工作效率的利器,本文将深入剖析一个基于Python的微信自动化工具开发全过程,有需要的小伙伴可以了解下... 目录概述功能全景1. 核心功能模块2. 特色功能效果展示1. 主界面概览2. 定时任务配置3. 操作日志演示

JavaScript实战:智能密码生成器开发指南

本文通过JavaScript实战开发智能密码生成器,详解如何运用crypto.getRandomValues实现加密级随机密码生成,包含多字符组合、安全强度可视化、易混淆字符排除等企业级功能。学习密码强度检测算法与信息熵计算原理,获取可直接嵌入项目的完整代码,提升Web应用的安全开发能力 目录

SQL Server身份验证模式步骤和示例代码

《SQLServer身份验证模式步骤和示例代码》SQLServer是一个广泛使用的关系数据库管理系统,通常使用两种身份验证模式:Windows身份验证和SQLServer身份验证,本文将详细介绍身份... 目录身份验证方式的概念更改身份验证方式的步骤方法一:使用SQL Server Management S