网络同步的解决方案有三种:状态同步、帧同步、实时广播同步,实际使用中可以混合使用。
网络同步概览
知名游戏中的同步方案
类型 |
游戏名 |
同步方案 |
MMORPG |
魔兽世界 |
状态同步+实时广播同步 |
战地竞技 |
和平精英 |
状态同步+实时广播同步 |
传奇类游戏 |
传奇世界、热血传奇 |
状态同步 |
MOBA |
王者荣耀 |
帧同步 |
网络同步的本质
网络同步的任务是在多人游戏中用最少的信息同步量来逼真地模拟其他玩家的位置、动作以及状态。
同步不仅涉及信息同步,还涉及到同步的范围。
主流的同步方案
状态同步
核心思想:将玩家的行为抽象成若干个状态,每个状态相当于一个具有固定逻辑的行为模式,例如战斗状态、寻路状态等。这些状态都是只要给相同的数据,就会展现出相同画面的个体效果。
在状态同步中,状态的指令由服务器发送,服务端操纵客户端的人物进行状态变化。但是为了防止出现延迟,也会允许用户直接在客户端对于角色进行操控,在服务器校验的时候再对玩家进行矫正。
总结起来就是,状态同步是指使角色的每个状态在某一段时间下的行为可以根据数据进行预测,除非角色的状态被改变,否则状态数据得到的就是相同的个体状态结果。
实时广播同步法
在一些对于实时性要求比较高的游戏来说,由于移动的速度和旋转变化太多、频次太高,无法做到拆分成状态来进行模拟。不过实际的游戏中往往是有些时候可以划分状态,有些时候进入战斗了就不能划分状态,所以状态同步往往也和实时广播同步同时存在。
实时广播的特点是,位置和旋转信息由客户端决定。这种方式能更加逼真的模拟变化频率很高的人物移动和旋转动作,但是会牺牲一些数据的安全性。
实际的游戏开发中最好传输速度,其他玩家接收到实时数据的时候,对于速度、预测速度、加速度等信息进行计算,这样会使得角色的行走更加的流畅。但是这种计算会有偏差,所以实际中还要需要定时进行矫正,就会造成游戏中我们看到的对方角色在不停的闪动。
帧同步
最大的特点是:不再由客户端本身的逻辑帧Update来决定,转而由从网络收到帧数据包来驱动执行逻辑的更新。
帧同步的服务器需要向每个客户端发送每秒15~30个帧数据包,帧数据主要存储的就是指令和指令相关的参数。客户端不断收到服务端广播的帧数据,每帧都执行一次更新逻辑,执行到某一帧带有指令数据时就执行该帧内的所有指令,同时也更新逻辑。
逻辑计算和渲染之间的速率是存在差距的,渲染会在10~60帧的范围内进行变化,帧数据的频率是每秒15帧。
如果网络出现重大延迟,可以从最近收到的帧数据离现在的时间来判断。
由服务器端发送的帧数据来完成客户端的同步执行操作,在每个客户端设备中的演算方式一致,最终会导致所有设备中的执行结果也一致。
三种同步方式的总结
状态同步 |
实时广播同步 |
帧同步 |
|
主要特点 |
指使角色的每个状态在某一段时间下的行为可以根据数据进行预测,除非角色的状态被改变,否则状态数据得到的就是相同的个体状态结果 |
位置和旋转信息由客户端决定 |
由从网络收到帧数据包来驱动执行逻辑的更新 |
优势 |
数据安全性好 |
在移动、旋转等上较合适 |
同步性高 |
不足 |
无法做到频次高的精确同步 |
不太能作为一个完整的解决方案 |
逻辑主要在客户端 |
网络同步的优化
同步快进
现实中的网络情况往往是波动的,网络时而延迟,时而有一堆数据涌过来。
当数据大量涌过来的时候,往往会执行堆积在队列里的全部帧数据;但是为了防止堆积的数据量太大导致游戏卡住,我们可以指定每次执行N帧的方式来快速同步帧数据。
断线重连的问题中,往往需要从头开始执行全部的逻辑帧,庞大的运算量会对CPU造成较大的压力。实际游戏中我们可以采用内存快照的方式,将跟战斗相关的数据备份一份,这样断线重连的时候只需要从快照执行到最后的逻辑帧,可以节省不少CPU的消耗。
如果非常频繁的发送指令,可能会造成帧数据的混乱,解决方案是将需要发送的指令存储起来,等收到服务器来的网络帧数据时再进行发送,如果有多个指令,则不断对于未发送的指令进行替换。
浮点数的精度问题
帧同步把计算过程交给了不同的设备,所以浮点数的精确计算也成为一个重要问题。
定点数是一个重要的解决方案,将整数部分和小数部分分开进行存储,两部分都当作整数来进行计算。C#中自带一个decimal类型就是定点数,但是无法与浮点数随意互相转换,计算前也需要封装,所以在游戏中使用的并不顺手。
另一个更加简单的方式是直接乘以10的倍数,这样能大大降低研发成本。
同步锁机制
如果有玩家的网络不好,希望等待这个玩家的进度,就会使用同步锁机制。同步锁机制中每个客户端每隔一段时间发送一个锁帧数据,告诉其他玩家这个玩家还是在线的,在游戏中,如果超过50帧没有收到某个玩家的锁帧数据,就停止播放网络帧数据,等这个玩家跟上大部队之后,所有的客户端从最后一次锁帧数据点开始,继续各自的演算。