精华内容
下载资源
问答
  • 状态同步与帧同步

    2019-09-06 19:46:21
    帧同步 状态同步 状态同步简单来说就是同步游戏中的各种状态,当客户端发送游戏动作到服务器,服务器接收到之后,通过计算游戏行为的结果,然后广播下发给客户端游戏中的各种状态数据。客户端接收到状态数据后显示...

    现代多人游戏中,多个客户端之间的通讯大多以同步多方状态为主要目标,为了实现这一目标,主要有两个技术方向:

    状态同步
    帧同步
    状态同步

    状态同步简单来说就是同步游戏中的各种状态,当客户端发送游戏动作到服务器,服务器接收到之后,通过计算游戏行为的结果,然后广播下发给客户端游戏中的各种状态数据。客户端接收到状态数据后显示内容。这种做法类似于各个客户端都在远程操作服务器上的软件。例如最高的mud,以及日后大量的国产网游,特别是回合制游戏,大多采用这种方式。

    状态同步的流程:

    客户端上传操作到服务器
    服务器收到后计算游戏行为的结果(如技能逻辑、战斗计算等)
    服务器以广播的方式下发游戏中各种状态
    客户端收到状态后更新本地状态(如动作状态、Buff状态、位置等)
    为了给游戏玩家更好的体验,减少同步的数据量,客户端也会做很多的本地运算,减少服务器同步的频率以及数据量。

    状态同步其实是一种不严谨的同步,它的思想中不同玩家屏幕上的一致性的表现并不是重要指标,只要每次操作的结果相同即可。所以状态同步对网络延迟的要求并不高。例如:RPG游戏中200~300ms的延迟对用户来说是可以接受的,但在RTS(即时战略)游戏中50ms的延迟却会很受伤。

    帧同步

    帧同步是RTS游戏经常采用的一种同步技术,状态同步中数据量会随着需要同步的单位数量增长,而对于RTS来讲动不动就是几百个单位可以被操作,如果这些都需要同步的话,数据量是不能被接受的,所以帧同步不同步状态,之同步操作。例如游戏中同步玩家的操作指令,操作指令包含当前的帧索引。

    简单来说,客户端发送游戏动作到服务器,服务器接收并汇总,然后直接转发给所有客户端,或者客户端直接通过P2P技术发送。客户端根据收到的游戏动作来做运算和显示。这种做法等于客户端之间相互远程控制其他客户端上的游戏软件。早期的ipx网络游戏,例如红色警戒、帝国时代、星际争霸,以及大量支持网络连线双打游戏机模拟机,都是采用这种方式。

    那些游戏需要使用帧同步呢?

    多人实时对战游戏
    游戏中需要战斗回放功能
    游戏中需要加速功能
    需要服务器同步逻辑校验防止作弊
    帧同步的流程

    同步随机种子:游戏中设计随机数的使用,通过同步随机数种子可以保持随机数的一致性。
    客户端上传操作指令给服务器,操作指令包含游戏操作和当前帧索引。
    游戏广播所有客户端的操作,如果没有操作也要广播空指令来驱动游戏帧前进。

    在这里插入图片描述

    帧同步
    帧同步主要依赖客户端的能力,服务器仅仅是做一个转发,甚至客户端可以无需服务器,通过P2P方式来转发数据。由于只是转发游戏的行为,所以广播的数据量比状态同步要小很多。非常适合游戏行为非常频繁的动作游戏,诸如飞行射击、FPS、RTS(即时战略)。

    状态同步由于要把整个游戏的状态都广播下去,如果游戏中的对象特别多,比如满屏的子弹、怪物,那么要广播的数据量就会很大,这个时候帧同步的优势就比较明显,因为不管有多少”机器控制的角色“,仅仅需要广播玩家角色有关的操作即可。反过来,如果游戏中有大量玩家同时聚集,那么帧同步和状态同步的差异就不太明显。反而状态同步能得到更多安全性,因为游戏运算在服务器上,比较容易防止外挂。

    简单来说,帧同步技术最要的概念是”相同的输入 + 相同的时机 = 相同的显示“。也就是说,游戏接收来自网络的多个客户端的操作,如果这些操作在各个客户端上都是一样的,那么多个客户端的显示也就是一样的,进而带来了”同步“的效果。在这种情况下,各个客户端的运算要绝对一致,不能依赖诸如本地时间、本地随机等”输入“,而要一切以网络来的数据为主。

    帧同步
    因为帧同步的特性,因此很容易做出战斗回放,即服务器记录所有操作,客户端请求到操作文件再执行一次。帧同步的特性导致客户端的逻辑实现和表现实现必须完全分离。

    帧同步的目的在于消除网络波动性带给玩家的卡顿以及忽快忽慢的不良体验。

    在这里插入图片描述
    网络波动的平滑处理
    状态同步和帧同步的比较和选择

    对于单位比较多的即时策略游戏,帧同步是很好的选择。相反的,如果玩家比较多,状态同步则更加合适,因为安全性更高。一般大型MMOARPG都采用状态同步,由于状态同步采用C/S架构,所有状态由服务器来控制,安全性比较高,但流量比较大。帧同步采用的是囚徒模式,所有C端强制采用一个逻辑帧率,从而保证输出一致,其特点是流量小,安全性较差。

    囚徒模式又叫锁步模式,就是把所有参与对战的客户端看成排成一列的囚犯,这些囚犯们的左脚都被锁链给连起来,如果要往前走,就只能同时迈步,如果其中某个人走快了或走慢了,都回让整队人停下来。
    在这里插入图片描述
    状态同步和帧同步的比较
    帧同步的基本原理
    帧同步是一种对同步源进行像素级同步显示的处理技术,对于网络上的多个接入者,一个信号将会通过主机同步发送给其他人,并同步显示在各个终端上。同步信号可以是每帧的像素数据,也可以是影响数据变化的关键事件信息。

    帧同步在网络游戏应用中的设计有别于传统的MMORPG游戏,因为可以承载大量的后台计算,实现类单机的效果,所以可以在射击类、飞行类游戏中实现弹幕计算或格斗类的高精度打击效果。

    什么叫做帧同步呢?服务器收集客户端手机发送过来的操作,然后在特定的时间(收集完成之后),再广播发送给每个客户端。客户端根据接收到的输入,进行同样的逻辑处理,最终得到同样的结果的过程。在实现上,一般都是以服务器按固定的帧率,来搜集每个客户端的输入,然后把这些输入广播给所有客户端。由于每个操作指令到达所有客户端的时间(帧)都是一样的,所以每个客户端运算的结果也是一样的,也就是同样的输入就会得到同样的结果。

    这就好像是玩家通过网络将操作手柄连接到你的手机,这种同步方案是传统单机局域网游戏中最常见的。

    帧同步
    帧同步模型最大的优点在于强一致性,每个客户端的表现是完全一样的,非常适合高度要求操作技巧的游戏。由于广播的仅仅是玩家的操作,所以数据量很少。不管游戏中的角色数量、状态数量有多大多复杂,都不会影响广播的数据量。

    帧同步模型最大的缺点是对所有玩家的延迟都有要求,一般来说要求在50毫秒以内,如果有一个客户端网络卡住了,所有客户端都要停下来等待。

    另外在帧同步模式中,数据同步的频率较高,网络延迟越小越好。由于TCP的滑动窗口机制和重传机制,导致延时机制,导致延时无法控制。因此帧同步一般采用UDP进行网络传输,但UDP又会衍生出可靠性问题,对于客户端,如果某些UDP包没有收到,就会出现丢帧的情况。

    帧同步技术原理
    客户端A的操作A1与客户端B的操作B1,共同封装成OperateCmd数据发送给PVP服务器,PVP服务器每66毫秒产生一个逻辑祯,在该桢所在时间段内,收到A1和B1后,生成一个Frame数据块,在该帧时间结束时,将Frame发送给客户端A和B。Frame数据块内有该帧的帧号,客户端A和B接收到Frame数据后,便知道该帧内,客户端A和客户端B都做了什么操作。然后根据接收到的消息A1和B1进行游戏表现,最终呈现给玩家A和B的结果是一致性的,从而实现客户端A和B的数据同步。
    在这里插入图片描述

    帧同步
    帧同步既然是在特定时间发送,也就是说每隔一段时间收集用户操作指令,那么要间隔多久内。例如每隔一段时间搜索用户的操作。此时,如果时间太快则网络速率达不到要求,如果时间太长则用户操作不流程。哪里多少才比较合适呢?根据统计玩家至少要在50ms100ms可以完成一次,一般维持到1520次左右会比较安全。

    展开全文
  • 帧同步 相关

    千次阅读 2017-06-05 19:20:26
    帧同步游戏开发基础分析:结合产品策划,让游戏流畅运行 什么游戏适合帧同步这种技术?  在现代多人游戏中,多个客户端之间的通讯,多以同步多方状态为主要目标。为了实现这个目标,主要有两个方向的技术: ...

    帧同步游戏开发基础分析:结合产品策划,让游戏流畅运行

    什么游戏适合帧同步这种技术?

      在现代多人游戏中,多个客户端之间的通讯,多以同步多方状态为主要目标。为了实现这个目标,主要有两个方向的技术:

      一种叫状态同步:客户端发送游戏动作到服务器,服务器收到后,计算游戏行为的结果,然后通过广播下发游戏中各种状态,客户端收到状态后显示内容。这种做法类似于各个客户端都远程操作服务器上的软件。最早的mud,以及后来大量的国产网游,特别是回合制游戏,都是这种方式;

      另外一种叫帧同步:客户端发送游戏动作到服务器,服务器广播转发所有客户端的动作(或者客户端直接通过P2P技术发送),客户端根据收到的所有游戏动作来做游戏运算和显示。这种做法等于客户端之间互相远程控制其他客户端上的游戏软件。早期的IPX网络游戏,如红色警戒、帝国时代、星际争霸,甚至大量的支持网络连线双打的游戏机模拟器,都是这种方式。

      帧同步这种同步方式,主要依靠客户端的能力,服务器仅仅是做一个转发,甚至客户端可以无需服务器,仅仅通过P2P方式来转发数据。由于只转发游戏行为,所以广播的数据量比状态同步要小很多,非常时候游戏行为非常频繁的动作游戏,比如飞行射击、FPS、RTS这类游戏。由于状态同步要把整个游戏的状态都广播下去,如果游戏中的对象特别多,比如满屏幕的子弹,很多怪物,那么要广播的数据量就很大了,这个时候帧同步的优势就比较明显,因为不管有多少“机器控制的角色”,仅仅需要广播玩家角色有关的操作即可。反过来说,如果游戏里是大量玩家聚集起来进行游戏的,那么帧同步和状态同步的差异就不明显了。反而状态同步能得到更多安全性上的好处,因为游戏运算在服务器上,比较容易防止外挂。

      帧同步技术的基础概念

      帧同步技术最重要的基础概念是:

      相同的输入+相同的时机=相同的显示

      意思是如果我们的游戏,接受了来自网络的多个客户端的操作,如果这些操作在各个客户端是一样的,那么多个客户端的显示也就一样了,这就带来了“同步”的效果。所以在这种情况下,各个客户端的运算要绝对一致,不能依赖诸如本地时间、本地随机数等等“输入”,而要一切以网络来的操作数据为主。

    在一般的帧同步系统中,会有一个Relay Server负责广播(转发)所有客户端的数据。为了让各个客户端能持续的运行,而不是卡住,所以需要定时的下发一个个“网络帧”数据来驱动各个客户端。因为客户端已经放弃了本地的时间,本地的循环驱动,所以这些“网络帧”就必不可少了。这些网络帧大部分实际上是“空”的,只有当玩家有输入的时候,才会把玩家的游戏操作的数据,填入到网络帧数据包中。对于客户端来说,就好像有很多键盘、鼠标、游戏手柄在通过网络操作自己一样。

    一般来说,大多数的游戏客户端引擎,都会定时调用一个接口函数,这个函数由用户填写内容,用来修改和控制游戏中各种需要显示的内容。比如在Flash里面叫OnEnterFrame(),在Unity里面叫Update()。这类函数通常会在每帧画面渲染前调用,当用户修改了游戏中的各个角色的位置、大小后,就在下一帧画面中显示出来。而在帧同步的游戏中,这个Update()函数依然是存在,只不过里面大部分的内容,需要挪到另外一个类似的函数中,我们可以称之为UpdateByNet()函数——由网络层不断的接收服务器发来的“网络帧”数据包,每收到一个这样的数据包,就调用一次这个UpdateByNet()函数,这样游戏就从通过本地CPU的Update()函数的驱动,改为根据网络来的UpdateByNet()函数驱动了。显然,网络发过来的同步帧速度会明显比本地CPU要慢的多,这里就对我们的游戏逻辑开发提出了更高的要求——如何同步的同时,还能保证流畅?

    帧同步的技术要点

      帧同步游戏中,由于需要“每一帧”都要广播数据,所以广播的频率非常高,这就要求每次广播的数据要足够的小。最好每一个网络帧,能在一个MTU以下,这样才能有效降低底层网络的延迟。同样的理由,我们为了提高实时性,一般也倾向于使用UDP而不是TCP协议,这样底层的处理会更高效。但是,这样也会带来了丢包、乱序的可能性。因此我们常常会以冗余的方式——比如每个帧数据包,实际上是包含了过去2帧的数据,也就是每次发3帧的数据,来对抗丢包。也就是说三个包里面只要有一个包没丢,就不影响游戏。另外我们还会在RelayServer上保存大量的客户端上传的数据,如果客户端发现丢了包(如果乱序了也认为是丢包),那么就发起一次“下载”请求,从服务器上重新下载丢失了的帧数据包(这个可能会使用TCP)。这一切,都依赖于每个帧数据要足够的小。所以我们一般要求,每次客户端发送的数据,应该小于128字节。你可以大概计算一下,如果我们的游戏有4个玩家,我们的冗余是3帧,那么一个下行的网络帧数据包大小会到128x4x3=1536字节,而每秒我们发15个网络帧,那么占用的带宽会到1536x15=23,040字节/秒,加上一些底层协议包头也就是24kB/s,这个速度看起来已经要求手机是3G网络才能支持了(实测中GPRS一般很难稳定到这个速度)。

      我们使用的游戏引擎,特别是3D游戏引擎,里面使用的位置数据,大多数是浮点数,大家知道,一个浮点数需要占用8个字节,这可比简单的整数4个字节大了足足一倍。而我们需要广播的游戏操作,往往不需要那么高的精确度,所以我们应该把这些浮点数,想办法变成整数来广播。有时候我们甚至有可能只用1~2个字节(0-256-65535)来表达一个操作所需要的数字(比如按键值、鼠标坐标)。这样就能大大降低广播的数据长度。最简单的方法,就是把浮点数乘以1000或100然后取整。

      另外一个降低广播数据量的做法就是自己编写序列化函数:一般现代编程语言,特别是面向对象的语言,都带有把对象序列化和反序列化的功能。我们要广播游戏操作的时候,这些操作往往也是一个个的“对象”,因此最简单的方法就是使用编程语言自带的序列化库来把对象转换成字节数组去广播。但是这些编程语言的默认序列化功能,为了实现诸如反射等高级功能,会把很多游戏逻辑所“不必要”的数据也序列化了,比如对象的类名、属性名什么的。如果我们自己去针对特定的数据对象来编写序列化函数,就没有这个问题了,我们可以仅仅提取我们想要的数据,甚至能合并和裁剪一些数据项,达到最小化数据长度的目的。

    在网络游戏中,各个客户端的运行条件和环境往往千差万别,有的硬件好一些,有的差一些,各方的网络情况也不一致;时不时玩家的网络还会在游戏过程中,发生临时的拥堵,我们称之为“网络抖动”。网络游戏有时候还会需要有中途加入游戏的需求(乱入),有游戏录像和观看、快进录像的功能。这些功能,都可能导致客户端收到“过去时间”里的一堆网络帧,因此,客户端必须要有处理这些堆积起来的网络数据的能力。最简单的做法就是加速播放(快进)——如果收到网络数据处理完游戏逻辑后,然后在同一个渲染帧(同一次Update()函数里)内,马上继续收下一个网络数据,然后又立刻处理。这样往往能在一个渲染帧的时间内,加速赶上服务器广播的最新游戏进度。但是这样做也会有副作用,如果客户端积累的包太多(比如游戏已经开始玩了10分钟,新的用户中途加入),会导致这个用户长时间卡住,因为程序正在疯狂的下载积累的帧同步包和运算快进。为了解决这个问题,有些程序员会限制每一个渲染帧中所快进的操作次数,这样用户还是能看到画面有活动。如果实在要快进的进度太多,就要采用“快照”技术,通过定时保存的游戏状态数据,来减少快进的进度了。这个快照功能这里就不展开了。


    一般来说,我们的客户端的渲染帧率都会大大高于网络帧的接收频率。如果我们每个渲染帧都去发送一次玩家操作(比如触摸屏上的手指位置),那么可能会导致发送的游戏操作远远大于收到的操作,这样做要么会让游戏操作堆积在服务器上,导致操作的严重延迟,要么导致下行的网络包非常大(服务器每次都把收到的所有操作一次下发),这样会让网络带宽占满,同样是会感觉延迟。不管怎么处理,都是不太好的结果。正确的做法应该是控制发包频率,最好是至少收到一个网络下行帧,才发送一个上行的游戏操作,避免堆积。另外,刚刚讲到的“快进”,如果我们在快速播放游戏逻辑的时候,每次播放同时也采集玩家输入去发送,那么同样会导致短时间内发送一大堆上行数据给服务器,而这些数据很可能客户端接收时产生大量的延迟。所以最好是在快进的时候不采集玩家的输入,因为玩家在看到快进过程中,实际上也很难有效的做出合理的反应,一个常见的做法,就是快进的时候,给游戏覆盖一个“等待”或“Loading”的蒙皮层,让玩家不可以输入操作。


    关于流畅度的优化

      实时同步游戏最重要的是流畅,然而影响游戏流畅的因素很多,网络带宽的限制,CPU运算和渲染效率的限制,都是很大的问题。所幸游戏本身还是有很多可以取舍的因素,这让我们可以牺牲一些游戏不太重要的特性,去提高流畅度。

      第一个可以用来交换流畅度的是“一致性”特性。我们做帧同步的目标是各个客户端都能看到一致的显示。但是游戏内容有很多,有一部分内容是可以容忍“不一致”的,比如我们做飞行射击弹幕游戏,满屏幕有很多子弹,而每一颗子弹本身的存在的时间很短,如果我们不是做对打的游戏(而是一起打电脑),那么这些子弹是可以不一致的。又比如我们做一个横版过关的配合游戏,几个玩家一起打电脑控制的怪物,大家关心的是怪物是怎么被打死的,而玩法本身又比较容忍不一致(横版动作游戏的攻击范围往往比较大),所以就算有些不一致问题也不大。在以上的条件下,我们就可以尝试,把更多的游戏逻辑,从网络帧的UpdateByNet()函数里面拿出去,放回到单机游戏中的Update()函数里去。这样就算网络有点卡,起码整个画面里还是有很多东西是不会被“卡住”的。但是必须注意的是,一般玩家控制的角色的动作,包括当前客户端控制的角色,还是应该从网络帧里面获得行为数据,因为如果玩家爱控制角色不一致的太多,整个游戏场面就会差更多。很多游戏中的怪物AI都是根据玩家角色来设定的,所以一旦玩家角色的行为是同步的,那么大多数的怪物的表现还是一致的。
    第二个可以用来交换流畅度的特性是实时性。一般来说,我们都希望游戏中的角色控制是灵敏的,实时的。我们的游戏角色往往在会玩家输入操作后的几十分之一秒内,就开始显示变化。在帧同步游戏中,我们可以让玩家一输入完操作,就立刻发包,然后尽快在下一个收到的网络帧中收到这个操作,从而尽快的完成显示。然而,网络并不是那么稳定,我们常常会发现一会快一会慢,这样玩家的操作体验就非常奇怪,无法预测输入动作后,角色会在什么时候起反应。这对于一些讲求操作实时性的游戏是很麻烦的。比如球类游戏,控制的角色跑的一会儿快一会儿慢,很难玩好“微操”。要解决这个问题,我们一般可以学习传输语音业务的做法,就是接收网络数据时,不立刻处理,而是给所有的操作增加一个固定的延迟,后在延迟的时间内,搜集多几个网络包,然后按固定的时间去播放(运算)。这样相当于做了一个网络帧的缓冲区,用来平滑那些一会儿快一会儿慢的数据包,改成匀速的运算。这种做法会让玩家感觉到一个固定延迟:输入操作后,最少要隔一段时间,才会起反应。但是起码这个延迟是固定的,可预计的,这对于游戏操作就便捷很多了,只要掌握了提前量,这个操作的感觉就好像角色有一定的“惯性”一样:按下跑并不立刻跑,松开跑不会立刻停,但这个惯性的时间是固定的。

    第三个用来交换流畅性的特性是公平性,这个特性其实和一致性有所类似。我们和其他玩家一起游戏的时候,有时候不希望对方因为电脑速度比较快,网络比较好,而能比我们更早的看到游戏的运行结果,从而提早作出操作。这一点在格斗对打游戏(如《街霸》)里面非常关键,在一些RTS(《星际争霸》)里面,提早看到游戏运行结果也是很有竞争优势的。因此我们为了让网络、硬件不一样的玩家能公平游戏,往往会使用一种叫“锁步”的策略:就好像一串绑着脚镣的囚犯,他们只能一起抬起左脚,然后再一起抬起右脚的走路,谁也不能走的更快。技术上的实现,就是每个客户端都定时(每N个渲染帧)发送一个网络帧到服务器上,就算玩家没操作,也类似心跳的这样发送空数据帧,所有客户端都要完整的收到所有的其他客户端的“心跳帧”才能开始运算一次游戏逻辑。这就是让所有的客户端,都互相等待,如果任何一个客户端卡了,其他的客户端都立刻就能知道,然后弹出界面让玩家停止输入来等待。因此在很多场合,帧同步的技术也被成为“锁步”技术,事实上,在没有统一的Relay Server服务器的时代(IPX局域网连机对战的时代),帧同步的网络帧其实就是上面所说的某个客户端的“心跳帧”,是由某个客户端产生并广播的(比如以前的局域网游戏,都会由一个客户端充当Host主机)。在《星际争霸》连机游戏中,如果有一个玩家掉线了,所有其他玩家就会发现有一个界面弹出来挡住画面,表示在等某某某。这种做法实际上是牺牲了流畅度的,因为你会发现一旦有网络、硬件卡的玩家加入游戏,所有其他玩家都受他的影响。为了减少这种对流畅度的影响,我们可以在需要“锁步”的时候,尽量少锁一点,比如不是发现缺了一帧就停下来,而是缺了若干帧,还是可以以“不公平”的方式继续玩一会儿(比如几秒),如果这段时间内还是没有补齐所缺的帧,才宣布锁住游戏等待。当然这个“容忍”的帧数我们可以调节到“最大”——就是没有。那么一个完全不锁步的游戏,肯定不是一个公平的游戏,但是也会在流畅性产生最大的好处,就是完全不受其他玩家影响。在那些不是PVP(玩家对战)的帧同步游戏中,不公平这个往往问题不大。我们完全可以在游戏的不同玩法里,打开、调整、甚至关闭这个“锁步”的机制,从而让游戏最大程度的平衡公平性和流畅性。

    总结

      帧同步游戏技术,并不存在一种可以让游戏流畅的通用做法,而是需要和游戏具体做很多结合,在减少数据包,优化游戏快进体验,控制发包速度上尽量调优。同时还需要和游戏产品策划一起,平衡一致性、实时性、公平性的策略,才能真正达到流畅游戏的目的。



    在帧同步模型中,每个客户端都会对整个游戏世界进行模拟。这种方法的好处在于减少了需要发送的信息。帧同步只需要发送用户的输入信息,而对于反过来的中心服务器模型来说,单位的信息则发送越频繁越好。

      比如说你在游戏世界中移动角色。在中心服务器模型中,物理模拟只会在服务器执行。客户端告诉服务器,角色要往哪个方向移动。服务器会执行寻路而且开始移动角色。服务器紧接着就会尽可能频繁地告知每个客户端该角色的位置。对于游戏世界中的每个角色都要运行这样的过程。对于实时策略游戏来说,同步成千上万的单位在中心服务器模型中几乎是不可能的任务。

      在帧同步模型中,在用户决定移动角色之后,就会告诉所有客户端。每个客户端都会执行寻路以及更新角色位置。只有用户输入的时候才需要通知每个客户端,然后每个客户端都会自己更新物理以及位置。

      这个模型带来了一些问题。每个客户端的模拟都必须执行得一模一样。这意味着,物理模拟必须执行同样的更新次数而且每个动作都需要同样的顺序执行。如果不这么做,其中一个客户端就会跑在其他客户端之前或者之后,然后在新的命令发出之后,跑得太快或者太慢的客户端走出的路径就会不同。这些不同会根据不同的游戏玩法而不同。

      另一个问题就是跨不同的机器和平台的确定性问题。计算上很小的不同都会对游戏造成蝴蝶效应。这个问题会在后续的文章中讲到。

      这里的实现方案灵感来自于这篇文章:《1500个弓箭手》。每个玩家命令都会在后续的两个回合中执行。在发送动作与处理动作之间存在延迟有助于对抗网络延迟。这个实现还给我们留下了根据延迟以及机器性能动态调整每回合时长的空间。这部分在这里先不讨论,会在后续文章再说。

      对于这个实现,我们有如下定义:

      帧同步回合:帧同步回合可以由多个游戏回合组成。玩家在一个帧同步回合执行一个动作。帧同步回合长度会根据性能调整。目前硬编码为200ms。

      游戏回合:游戏回合就是游戏逻辑和物理模拟的更新。每个帧同步回合拥有的游戏回合次数是由性能控制的。目前硬编码为50ms,也就是每次帧同步回合有4次游戏回合。也就是每秒有20次游戏回合。

      动作:一个动作就是玩家发起的一个命令。比如说在某个区域内选中单位,或者移动选中单位到目的地。

      注意:我们将不使用unity3d的物理引擎。而是使用一个确定性的自定义引擎。在后续文章中会有实现。

      游戏主循环

      Unity3d的循环是运行在单线程下的。可以通过在这两个函数插入自定义代码:

    1. Update()
    2. FixedUpdate()
    复制代码

      Unity3d的主循环每次遍历更新都会调用Update()。主循环会以最快速度运行,除非设置了固定的帧率。
      FixedUpdate()会根据设置每秒执行固定次数。在主循环遍历中,它会被调用零次或多次,取决于上次遍历所花费的时间。FixedUpdate()有着我们想要的行为,就是每次帧同步回合都执行固定时长。但是,FixedUpdate()的频率只能在运行之前设置好。而我们希望可以根据性能调节我们的游戏帧率。

      游戏帧回合

      这个实现有着与FixedUpdate()在Update()函数中执行所类似的逻辑。主要不同的地方在于,我们可以调整频率。这是通过增加”累计时间”来完成的。每次调用Update()函数,上次遍历所花费的时间会添加到其中。这就是Time.deltaTime。如果累计时间大于我们的固定游戏回合帧率(50ms),那么我们就会调用gameframe()。我们每次调用gameframe()都会在累计时间上减去50ms,所以我们一直调用,知道累计时间小于50ms。

    1. private float AccumilatedTime = 0f;

    2. private float FrameLength = 0.05f; //50 miliseconds

    3. //called once per unity frame
    4. public void Update() {
    5.     //Basically same logic as FixedUpdate, but we can scale it by adjusting FrameLength
    6.     AccumilatedTime = AccumilatedTime + Time.deltaTime;

    7.     //in case the FPS is too slow, we may need to update the game multiple times a frame
    8.     while(AccumilatedTime > FrameLength) {
    9.         GameFrameTurn ();
    10.         AccumilatedTime = AccumilatedTime - FrameLength;
    11.     }
    12. }
    复制代码

      我们跟踪当前帧同步回合中游戏帧的数量。每当我们在帧同步回合中达到我们想要的游戏回合次数,我们就会更新帧同步回合到下一轮。如果帧同步还不能到下一轮,我们就不能增加游戏帧,而且我们会在下一次同样执行帧同步检查。

    1. private void GameFrameTurn() {
    2.     //first frame is used to process actions
    3.     if(GameFrame == 0) {
    4.         if(LockStepTurn()) {
    5.             GameFrame++;
    6.         }
    7.     } else {
    8.         //update game

    9.         //...
    10.          
    11.         GameFrame++;
    12.         if(GameFrame == GameFramesPerLocksetpTurn) {
    13.             GameFrame = 0;
    14.         }
    15.     }
    16. }
    复制代码

      在游戏回合中,物理模拟会更新而且我们的游戏逻辑也会更新。游戏逻辑是通过接口(IHasGameFrame)来实现的,而且添加这个对象到集合中,然后我们就可以进行遍历。

    1. private void GameFrameTurn() {
    2.     //first frame is used to process actions
    3.     if(GameFrame == 0) {
    4.         if(LockStepTurn()) {
    5.             GameFrame++;
    6.         }
    7.     } else {
    8.         //update game
    9.         SceneManager.Manager.TwoDPhysics.Update (GameFramesPerSecond);
    10.          
    11.         List<IHasGameFrame> finished = new List<IHasGameFrame>();
    12.         foreach(IHasGameFrame obj in SceneManager.Manager.GameFrameObjects) {
    13.             obj.GameFrameTurn(GameFramesPerSecond);
    14.             if(obj.Finished) {
    15.                 finished.Add (obj);
    16.             }
    17.         }
    18.          
    19.         foreach(IHasGameFrame obj in finished) {
    20.             SceneManager.Manager.GameFrameObjects.Remove (obj);
    21.         }
    22.          
    23.         GameFrame++;
    24.         if(GameFrame == GameFramesPerLocksetpTurn) {
    25.             GameFrame = 0;
    26.         }
    27.     }
    28. }
    复制代码

      IHasGameFrame接口有一个方法叫做GameFrameTurn,它以当前每秒游戏帧的个数为参数。一个具体的带游戏逻辑的对象应该基于GameFramesPerSecond来计算。比如说,如果一个单位正在攻击另一个单位,而且他攻击频率为每秒钟10点伤害,你可能会通过将它除以GameFramesPerSecond来添加伤害。而GameFramesPerSecond会根据性能进行调整。

      IHasGameFrame接口也有属性标记着结束。这使得实现IHasGameFrame的对象可以通知游戏帧循环自己已经结束。一个例子就是一个对象跟着路径行走,而在到达目的地之后,这个对象就不再需要了。

      帧同步回合

      为了与其他客户端保持同步,每次帧同步回合我们都要问以下问题:

      我们已经收到了所有客户端的下一轮动作了吗?

      每个客户端都确认得到我们的动作了吗?

      我们有两个对象,ConfirmedActions和PendingActions。这两个都有各自可能收到消息的集合。在我们进入下一个回合之前,我们会检查这两个对象。

    1. private bool NextTurn() {       
    2.     if(confirmedActions.ReadyForNextTurn() && pendingActions.ReadyForNextTurn()) {
    3.         //increment the turn ID
    4.         LockStepTurnID++;
    5.         //move the confirmed actions to next turn
    6.         confirmedActions.NextTurn();
    7.         //move the pending actions to this turn
    8.         pendingActions.NextTurn();
    9.          
    10.         return true;
    11.     }
    12.      
    13.     return false;
    14. }
    复制代码

      动作

      动作,也就是命令,都通过实现IAction接口来通信。有着一个无参数函数叫做ProcessAction()。这个类必须为Serializable。这意味着这个对象的所有字段也是Serializable的。当用户与UI交互,动作的实例就会创建,然后发送到我们的帧同步管理器的队列中。队列通常在游戏太慢而用户在一个帧同步回合中发送多于一个命令的时候用到。虽然每次只能发送一个命令,但没有一个会忽略。

      当发送动作到其他玩家的时候,动作实例会序列化为字节数组,然后被其他玩家反序列化。一个默认的”非动作”对象会在用户没有执行任何操作的时候发送。而其他则会根据特定游戏逻辑而定。这里是一个创建新单位的动作:

    1. using System;
    2. using UnityEngine;

    3. [Serializable]
    4. public class CreateUnit : IAction
    5. {
    6.     int owningPlayer;
    7.     int buildingID;
    8.      
    9.     public CreateUnit (int owningPlayer, int buildingID) {
    10.         this.owningPlayer = owningPlayer;
    11.         this.buildingID = buildingID;
    12.     }
    13.      
    14.     public void ProcessAction() {
    15.         Building b = SceneManager.Manager.GamePieceManager.GetBuilding(owningPlayer, buildingID);
    16.         b.SpawnUnit();
    17.     }
    18. }
    复制代码

      这个动作会依赖于SceneManager的静态引用。如果你不喜欢这个实现,可以修改IAction接口,使得ProcessAction接收一个SceneManager实例。

      实例代码可以在这里找到:Bitbucket – Sample Lockstep


    展开全文
  • 为了能在GPS接收端获取正确导航电文,...使用Xilinx开发软件,通过Verilog代码完成对位同步、帧同步的设计并在硬件平台上进行调试。通过Chip Scope和逻辑分析仪进行验证,结果表明该设计方案正确可靠,满足设计要求。
  •  在Xilinx的FPGA器件XC3S200-4FT200上对方案中设计的帧同步系统进行了实现,利用Modelsim 6.0软件进行了仿真测试。仿真结果表明,本方案设计的同步系统工作稳定,满足性能要求。  0 引言  在数字通信系统中,...
  • 游戏帧同步的流程与实现 大纲 帧同步的基本原理 帧事件数据采集 帧同步的事件处理与动画 帧同步的逻辑数据同步 帧同步之跳帧处理 现代多人游戏中,多个客户端之间的通讯大多以同步多方状态为主要目标,为了实现这...

    游戏帧同步的流程与实现

    大纲

    • 帧同步的基本原理
    • 帧事件数据采集
    • 帧同步的事件处理与动画
    • 帧同步的逻辑数据同步
    • 帧同步之跳帧处理

    现代多人游戏中,多个客户端之间的通讯大多以同步多方状态为主要目标,为了实现这一目标,主要有两个技术方向:

    • 状态同步
    • 帧同步

    状态同步

    状态同步简单来说就是同步游戏中的各种状态,当客户端发送游戏动作到服务器,服务器接收到之后,通过计算游戏行为的结果,然后广播下发给客户端游戏中的各种状态数据。客户端接收到状态数据后显示内容。这种做法类似于各个客户端都在远程操作服务器上的软件。例如最高的mud,以及日后大量的国产网游,特别是回合制游戏,大多采用这种方式。

    状态同步的流程:

    1. 客户端上传操作到服务器
    2. 服务器收到后计算游戏行为的结果(如技能逻辑、战斗计算等)
    3. 服务器以广播的方式下发游戏中各种状态
    4. 客户端收到状态后更新本地状态(如动作状态、Buff状态、位置等)

    为了给游戏玩家更好的体验,减少同步的数据量,客户端也会做很多的本地运算,减少服务器同步的频率以及数据量。

    状态同步其实是一种不严谨的同步,它的思想中不同玩家屏幕上的一致性的表现并不是重要指标,只要每次操作的结果相同即可。所以状态同步对网络延迟的要求并不高。例如:RPG游戏中200~300ms的延迟对用户来说是可以接受的,但在RTS(即时战略)游戏中50ms的延迟却会很受伤。

    帧同步

    帧同步是RTS游戏经常采用的一种同步技术,状态同步中数据量会随着需要同步的单位数量增长,而对于RTS来讲动不动就是几百个单位可以被操作,如果这些都需要同步的话,数据量是不能被接受的,所以帧同步不同步状态,之同步操作。例如游戏中同步玩家的操作指令,操作指令包含当前的帧索引。

    简单来说,客户端发送游戏动作到服务器,服务器接收并汇总,然后直接转发给所有客户端,或者客户端直接通过P2P技术发送。客户端根据收到的游戏动作来做运算和显示。这种做法等于客户端之间相互远程控制其他客户端上的游戏软件。早期的ipx网络游戏,例如红色警戒、帝国时代、星际争霸,以及大量支持网络连线双打游戏机模拟机,都是采用这种方式。

    那些游戏需要使用帧同步呢?

    • 多人实时对战游戏
    • 游戏中需要战斗回放功能
    • 游戏中需要加速功能
    • 需要服务器同步逻辑校验防止作弊

    帧同步的流程

    1. 同步随机种子:游戏中设计随机数的使用,通过同步随机数种子可以保持随机数的一致性。
    2. 客户端上传操作指令给服务器,操作指令包含游戏操作和当前帧索引。
    3. 游戏广播所有客户端的操作,如果没有操作也要广播空指令来驱动游戏帧前进。

    帧同步

    帧同步主要依赖客户端的能力,服务器仅仅是做一个转发,甚至客户端可以无需服务器,通过P2P方式来转发数据。由于只是转发游戏的行为,所以广播的数据量比状态同步要小很多。非常适合游戏行为非常频繁的动作游戏,诸如飞行射击、FPS、RTS(即时战略)。

    状态同步由于要把整个游戏的状态都广播下去,如果游戏中的对象特别多,比如满屏的子弹、怪物,那么要广播的数据量就会很大,这个时候帧同步的优势就比较明显,因为不管有多少”机器控制的角色“,仅仅需要广播玩家角色有关的操作即可。反过来,如果游戏中有大量玩家同时聚集,那么帧同步和状态同步的差异就不太明显。反而状态同步能得到更多安全性,因为游戏运算在服务器上,比较容易防止外挂。

    简单来说,帧同步技术最要的概念是”相同的输入 + 相同的时机 = 相同的显示“。也就是说,游戏接收来自网络的多个客户端的操作,如果这些操作在各个客户端上都是一样的,那么多个客户端的显示也就是一样的,进而带来了”同步“的效果。在这种情况下,各个客户端的运算要绝对一致,不能依赖诸如本地时间、本地随机等”输入“,而要一切以网络来的数据为主。

    帧同步
    因为帧同步的特性,因此很容易做出战斗回放,即服务器记录所有操作,客户端请求到操作文件再执行一次。帧同步的特性导致客户端的逻辑实现和表现实现必须完全分离。

    帧同步的目的在于消除网络波动性带给玩家的卡顿以及忽快忽慢的不良体验。

    网络波动的平滑处理
    状态同步和帧同步的比较和选择

    对于单位比较多的即时策略游戏,帧同步是很好的选择。相反的,如果玩家比较多,状态同步则更加合适,因为安全性更高。一般大型MMOARPG都采用状态同步,由于状态同步采用C/S架构,所有状态由服务器来控制,安全性比较高,但流量比较大。帧同步采用的是囚徒模式,所有C端强制采用一个逻辑帧率,从而保证输出一致,其特点是流量小,安全性较差。

    囚徒模式又叫锁步模式,就是把所有参与对战的客户端看成排成一列的囚犯,这些囚犯们的左脚都被锁链给连起来,如果要往前走,就只能同时迈步,如果其中某个人走快了或走慢了,都回让整队人停下来。
    状态同步和帧同步的比较

    帧同步的基本原理

    帧同步是一种对同步源进行像素级同步显示的处理技术,对于网络上的多个接入者,一个信号将会通过主机同步发送给其他人,并同步显示在各个终端上。同步信号可以是每帧的像素数据,也可以是影响数据变化的关键事件信息。

    帧同步在网络游戏应用中的设计有别于传统的MMORPG游戏,因为可以承载大量的后台计算,实现类单机的效果,所以可以在射击类、飞行类游戏中实现弹幕计算或格斗类的高精度打击效果。

    什么叫做帧同步呢?服务器收集客户端手机发送过来的操作,然后在特定的时间(收集完成之后),再广播发送给每个客户端。客户端根据接收到的输入,进行同样的逻辑处理,最终得到同样的结果的过程。在实现上,一般都是以服务器按固定的帧率,来搜集每个客户端的输入,然后把这些输入广播给所有客户端。由于每个操作指令到达所有客户端的时间(帧)都是一样的,所以每个客户端运算的结果也是一样的,也就是同样的输入就会得到同样的结果。

    这就好像是玩家通过网络将操作手柄连接到你的手机,这种同步方案是传统单机局域网游戏中最常见的。

    帧同步
    帧同步模型最大的优点在于强一致性,每个客户端的表现是完全一样的,非常适合高度要求操作技巧的游戏。由于广播的仅仅是玩家的操作,所以数据量很少。不管游戏中的角色数量、状态数量有多大多复杂,都不会影响广播的数据量。

    帧同步模型最大的缺点是对所有玩家的延迟都有要求,一般来说要求在50毫秒以内,如果有一个客户端网络卡住了,所有客户端都要停下来等待。

    另外在帧同步模式中,数据同步的频率较高,网络延迟越小越好。由于TCP的滑动窗口机制和重传机制,导致延时机制,导致延时无法控制。因此帧同步一般采用UDP进行网络传输,但UDP又会衍生出可靠性问题,对于客户端,如果某些UDP包没有收到,就会出现丢帧的情况。

    帧同步技术原理
    客户端A的操作A1与客户端B的操作B1,共同封装成OperateCmd数据发送给PVP服务器,PVP服务器每66毫秒产生一个逻辑祯,在该桢所在时间段内,收到A1和B1后,生成一个Frame数据块,在该帧时间结束时,将Frame发送给客户端A和B。Frame数据块内有该帧的帧号,客户端A和B接收到Frame数据后,便知道该帧内,客户端A和客户端B都做了什么操作。然后根据接收到的消息A1和B1进行游戏表现,最终呈现给玩家A和B的结果是一致性的,从而实现客户端A和B的数据同步。
    帧同步

    帧同步既然是在特定时间发送,也就是说每隔一段时间收集用户操作指令,那么要间隔多久内。例如每隔一段时间搜索用户的操作。此时,如果时间太快则网络速率达不到要求,如果时间太长则用户操作不流程。哪里多少才比较合适呢?根据统计玩家至少要在50ms100ms可以完成一次,一般维持到1520次左右会比较安全。

    30人点赞
    game

    作者:JunChow520
    链接:https://www.jianshu.com/p/8cca5458c45b
    来源:简书
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

    展开全文
  • 帧同步游戏开发基础

    千次阅读 2016-09-28 18:12:55
    帧同步游戏开发基础 什么游戏适合帧同步这种技术?  在现代多人游戏中,多个客户端之间的通讯,多以同步多方状态为主要目标。为了实现这个目标,主要有两个方向的技术:  一种叫状态同步:客户端发送游戏动作到...

    帧同步游戏开发基础
    什么游戏适合帧同步这种技术?
      在现代多人游戏中,多个客户端之间的通讯,多以同步多方状态为主要目标。为了实现这个目标,主要有两个方向的技术:
      一种叫状态同步:客户端发送游戏动作到服务器,服务器收到后,计算游戏行为的结果,然后通过广播下发游戏中各种状态,客户端收到状态后显示内容。这种做法类似于各个客户端都远程操作服务器上的软件。最早的mud,以及后来大量的国产网游,特别是回合制游戏,都是这种方式;
      另外一种叫帧同步:客户端发送游戏动作到服务器,服务器广播转发所有客户端的动作(或者客户端直接通过P2P技术发送),客户端根据收到的所有游戏动作来做游戏运算和显示。这种做法等于客户端之间互相远程控制其他客户端上的游戏软件。早期的IPX网络游戏,如红色警戒、帝国时代、星际争霸,甚至大量的支持网络连线双打的游戏机模拟器,都是这种方式。
      帧同步这种同步方式,主要依靠客户端的能力,服务器仅仅是做一个转发,甚至客户端可以无需服务器,仅仅通过P2P方式来转发数据。由于只转发游戏行为,所以广播的数据量比状态同步要小很多,非常时候游戏行为非常频繁的动作游戏,比如飞行射击、FPS、RTS这类游戏。由于状态同步要把整个游戏的状态都广播下去,如果游戏中的对象特别多,比如满屏幕的子弹,很多怪物,那么要广播的数据量就很大了,这个时候帧同步的优势就比较明显,因为不管有多少“机器控制的角色”,仅仅需要广播玩家角色有关的操作即可。反过来说,如果游戏里是大量玩家聚集起来进行游戏的,那么帧同步和状态同步的差异就不明显了。反而状态同步能得到更多安全性上的好处,因为游戏运算在服务器上,比较容易防止外挂。


    帧同步技术的基础概念
      帧同步技术最重要的基础概念是:
      相同的输入+相同的时机=相同的显示
      意思是如果我们的游戏,接受了来自网络的多个客户端的操作,如果这些操作在各个客户端是一样的,那么多个客户端的显示也就一样了,这就带来了“同步”的效果。所以在这种情况下,各个客户端的运算要绝对一致,不能依赖诸如本地时间、本地随机数等等“输入”,而要一切以网络来的操作数据为主。
    在一般的帧同步系统中,会有一个Relay Server负责广播(转发)所有客户端的数据。为了让各个客户端能持续的运行,而不是卡住,所以需要定时的下发一个个“网络帧”数据来驱动各个客户端。因为客户端已经放弃了本地的时间,本地的循环驱动,所以这些“网络帧”就必不可少了。这些网络帧大部分实际上是“空”的,只有当玩家有输入的时候,才会把玩家的游戏操作的数据,填入到网络帧数据包中。对于客户端来说,就好像有很多键盘、鼠标、游戏手柄在通过网络操作自己一样。
    一般来说,大多数的游戏客户端引擎,都会定时调用一个接口函数,这个函数由用户填写内容,用来修改和控制游戏中各种需要显示的内容。比如在Flash里面叫OnEnterFrame(),在Unity里面叫Update()。这类函数通常会在每帧画面渲染前调用,当用户修改了游戏中的各个角色的位置、大小后,就在下一帧画面中显示出来。而在帧同步的游戏中,这个Update()函数依然是存在,只不过里面大部分的内容,需要挪到另外一个类似的函数中,我们可以称之为UpdateByNet()函数——由网络层不断的接收服务器发来的“网络帧”数据包,每收到一个这样的数据包,就调用一次这个UpdateByNet()函数,这样游戏就从通过本地CPU的Update()函数的驱动,改为根据网络来的UpdateByNet()函数驱动了。显然,网络发过来的同步帧速度会明显比本地CPU要慢的多,这里就对我们的游戏逻辑开发提出了更高的要求——如何同步的同时,还能保证流畅?
    帧同步的技术要点
      帧同步游戏中,由于需要“每一帧”都要广播数据,所以广播的频率非常高,这就要求每次广播的数据要足够的小。最好每一个网络帧,能在一个MTU以下,这样才能有效降低底层网络的延迟。同样的理由,我们为了提高实时性,一般也倾向于使用UDP而不是TCP协议,这样底层的处理会更高效。但是,这样也会带来了丢包、乱序的可能性。因此我们常常会以冗余的方式——比如每个帧数据包,实际上是包含了过去2帧的数据,也就是每次发3帧的数据,来对抗丢包。也就是说三个包里面只要有一个包没丢,就不影响游戏。另外我们还会在RelayServer上保存大量的客户端上传的数据,如果客户端发现丢了包(如果乱序了也认为是丢包),那么就发起一次“下载”请求,从服务器上重新下载丢失了的帧数据包(这个可能会使用TCP)。这一切,都依赖于每个帧数据要足够的小。所以我们一般要求,每次客户端发送的数据,应该小于128字节。你可以大概计算一下,如果我们的游戏有4个玩家,我们的冗余是3帧,那么一个下行的网络帧数据包大小会到128x4x3=1536字节,而每秒我们发15个网络帧,那么占用的带宽会到1536x15=23,040字节/秒,加上一些底层协议包头也就是24kB/s,这个速度看起来已经要求手机是3G网络才能支持了(实测中GPRS一般很难稳定到这个速度)。
    我们使用的游戏引擎,特别是3D游戏引擎,里面使用的位置数据,大多数是浮点数,大家知道,一个浮点数需要占用8个字节,这可比简单的整数4个字节大了足足一倍。而我们需要广播的游戏操作,往往不需要那么高的精确度,所以我们应该把这些浮点数,想办法变成整数来广播。有时候我们甚至有可能只用1~2个字节(0-256-65535)来表达一个操作所需要的数字(比如按键值、鼠标坐标)。这样就能大大降低广播的数据长度。最简单的方法,就是把浮点数乘以1000或100然后取整。
    另外一个降低广播数据量的做法就是自己编写序列化函数:一般现代编程语言,特别是面向对象的语言,都带有把对象序列化和反序列化的功能。我们要广播游戏操作的时候,这些操作往往也是一个个的“对象”,因此最简单的方法就是使用编程语言自带的序列化库来把对象转换成字节数组去广播。但是这些编程语言的默认序列化功能,为了实现诸如反射等高级功能,会把很多游戏逻辑所“不必要”的数据也序列化了,比如对象的类名、属性名什么的。如果我们自己去针对特定的数据对象来编写序列化函数,就没有这个问题了,我们可以仅仅提取我们想要的数据,甚至能合并和裁剪一些数据项,达到最小化数据长度的目的。
    在网络游戏中,各个客户端的运行条件和环境往往千差万别,有的硬件好一些,有的差一些,各方的网络情况也不一致;时不时玩家的网络还会在游戏过程中,发生临时的拥堵,我们称之为“网络抖动”。网络游戏有时候还会需要有中途加入游戏的需求(乱入),有游戏录像和观看、快进录像的功能。这些功能,都可能导致客户端收到“过去时间”里的一堆网络帧,因此,客户端必须要有处理这些堆积起来的网络数据的能力。最简单的做法就是加速播放(快进)——如果收到网络数据处理完游戏逻辑后,然后在同一个渲染帧(同一次Update()函数里)内,马上继续收下一个网络数据,然后又立刻处理。这样往往能在一个渲染帧的时间内,加速赶上服务器广播的最新游戏进度。但是这样做也会有副作用,如果客户端积累的包太多(比如游戏已经开始玩了10分钟,新的用户中途加入),会导致这个用户长时间卡住,因为程序正在疯狂的下载积累的帧同步包和运算快进。为了解决这个问题,有些程序员会限制每一个渲染帧中所快进的操作次数,这样用户还是能看到画面有活动。如果实在要快进的进度太多,就要采用“快照”技术,通过定时保存的游戏状态数据,来减少快进的进度了。这个快照功能这里就不展开了。
    一般来说,我们的客户端的渲染帧率都会大大高于网络帧的接收频率。如果我们每个渲染帧都去发送一次玩家操作(比如触摸屏上的手指位置),那么可能会导致发送的游戏操作远远大于收到的操作,这样做要么会让游戏操作堆积在服务器上,导致操作的严重延迟,要么导致下行的网络包非常大(服务器每次都把收到的所有操作一次下发),这样会让网络带宽占满,同样是会感觉延迟。不管怎么处理,都是不太好的结果。正确的做法应该是控制发包频率,最好是至少收到一个网络下行帧,才发送一个上行的游戏操作,避免堆积。另外,刚刚讲到的“快进”,如果我们在快速播放游戏逻辑的时候,每次播放同时也采集玩家输入去发送,那么同样会导致短时间内发送一大堆上行数据给服务器,而这些数据很可能客户端接收时产生大量的延迟。所以最好是在快进的时候不采集玩家的输入,因为玩家在看到快进过程中,实际上也很难有效的做出合理的反应,一个常见的做法,就是快进的时候,给游戏覆盖一个“等待”或“Loading”的蒙皮层,让玩家不可以输入操作。
    关于流畅度的优化
      实时同步游戏最重要的是流畅,然而影响游戏流畅的因素很多,网络带宽的限制,CPU运算和渲染效率的限制,都是很大的问题。所幸游戏本身还是有很多可以取舍的因素,这让我们可以牺牲一些游戏不太重要的特性,去提高流畅度。
      第一个可以用来交换流畅度的是“一致性”特性。我们做帧同步的目标是各个客户端都能看到一致的显示。但是游戏内容有很多,有一部分内容是可以容忍“不一致”的,比如我们做飞行射击弹幕游戏,满屏幕有很多子弹,而每一颗子弹本身的存在的时间很短,如果我们不是做对打的游戏(而是一起打电脑),那么这些子弹是可以不一致的。又比如我们做一个横版过关的配合游戏,几个玩家一起打电脑控制的怪物,大家关心的是怪物是怎么被打死的,而玩法本身又比较容忍不一致(横版动作游戏的攻击范围往往比较大),所以就算有些不一致问题也不大。在以上的条件下,我们就可以尝试,把更多的游戏逻辑,从网络帧的UpdateByNet()函数里面拿出去,放回到单机游戏中的Update()函数里去。这样就算网络有点卡,起码整个画面里还是有很多东西是不会被“卡住”的。但是必须注意的是,一般玩家控制的角色的动作,包括当前客户端控制的角色,还是应该从网络帧里面获得行为数据,因为如果玩家爱控制角色不一致的太多,整个游戏场面就会差更多。很多游戏中的怪物AI都是根据玩家角色来设定的,所以一旦玩家角色的行为是同步的,那么大多数的怪物的表现还是一致的。
    第二个可以用来交换流畅度的特性是实时性。一般来说,我们都希望游戏中的角色控制是灵敏的,实时的。我们的游戏角色往往在会玩家输入操作后的几十分之一秒内,就开始显示变化。在帧同步游戏中,我们可以让玩家一输入完操作,就立刻发包,然后尽快在下一个收到的网络帧中收到这个操作,从而尽快的完成显示。然而,网络并不是那么稳定,我们常常会发现一会快一会慢,这样玩家的操作体验就非常奇怪,无法预测输入动作后,角色会在什么时候起反应。这对于一些讲求操作实时性的游戏是很麻烦的。比如球类游戏,控制的角色跑的一会儿快一会儿慢,很难玩好“微操”。要解决这个问题,我们一般可以学习传输语音业务的做法,就是接收网络数据时,不立刻处理,而是给所有的操作增加一个固定的延迟,后在延迟的时间内,搜集多几个网络包,然后按固定的时间去播放(运算)。这样相当于做了一个网络帧的缓冲区,用来平滑那些一会儿快一会儿慢的数据包,改成匀速的运算。这种做法会让玩家感觉到一个固定延迟:输入操作后,最少要隔一段时间,才会起反应。但是起码这个延迟是固定的,可预计的,这对于游戏操作就便捷很多了,只要掌握了提前量,这个操作的感觉就好像角色有一定的“惯性”一样:按下跑并不立刻跑,松开跑不会立刻停,但这个惯性的时间是固定的。
    第三个用来交换流畅性的特性是公平性,这个特性其实和一致性有所类似。我们和其他玩家一起游戏的时候,有时候不希望对方因为电脑速度比较快,网络比较好,而能比我们更早的看到游戏的运行结果,从而提早作出操作。这一点在格斗对打游戏(如《街霸》)里面非常关键,在一些RTS(《星际争霸》)里面,提早看到游戏运行结果也是很有竞争优势的。因此我们为了让网络、硬件不一样的玩家能公平游戏,往往会使用一种叫“锁步”的策略:就好像一串绑着脚镣的囚犯,他们只能一起抬起左脚,然后再一起抬起右脚的走路,谁也不能走的更快。技术上的实现,就是每个客户端都定时(每N个渲染帧)发送一个网络帧到服务器上,就算玩家没操作,也类似心跳的这样发送空数据帧,所有客户端都要完整的收到所有的其他客户端的“心跳帧”才能开始运算一次游戏逻辑。这就是让所有的客户端,都互相等待,如果任何一个客户端卡了,其他的客户端都立刻就能知道,然后弹出界面让玩家停止输入来等待。因此在很多场合,帧同步的技术也被成为“锁步”技术,事实上,在没有统一的Relay Server服务器的时代(IPX局域网连机对战的时代),帧同步的网络帧其实就是上面所说的某个客户端的“心跳帧”,是由某个客户端产生并广播的(比如以前的局域网游戏,都会由一个客户端充当Host主机)。在《星际争霸》连机游戏中,如果有一个玩家掉线了,所有其他玩家就会发现有一个界面弹出来挡住画面,表示在等某某某。这种做法实际上是牺牲了流畅度的,因为你会发现一旦有网络、硬件卡的玩家加入游戏,所有其他玩家都受他的影响。为了减少这种对流畅度的影响,我们可以在需要“锁步”的时候,尽量少锁一点,比如不是发现缺了一帧就停下来,而是缺了若干帧,还是可以以“不公平”的方式继续玩一会儿(比如几秒),如果这段时间内还是没有补齐所缺的帧,才宣布锁住游戏等待。当然这个“容忍”的帧数我们可以调节到“最大”——就是没有。那么一个完全不锁步的游戏,肯定不是一个公平的游戏,但是也会在流畅性产生最大的好处,就是完全不受其他玩家影响。在那些不是PVP(玩家对战)的帧同步游戏中,不公平这个往往问题不大。我们完全可以在游戏的不同玩法里,打开、调整、甚至关闭这个“锁步”的机制,从而让游戏最大程度的平衡公平性和流畅性。
    总结
      帧同步游戏技术,并不存在一种可以让游戏流畅的通用做法,而是需要和游戏具体做很多结合,在减少数据包,优化游戏快进体验,控制发包速度上尽量调优。同时还需要和游戏产品策划一起,平衡一致性、实时性、公平性的策略,才能真正达到流畅游戏的目的。
      转载自公众号:“韩大”(handa1740168)。

    展开全文
  •  在Xilinx的FPGA器件XC3S200-4FT200上对方案中设计的帧同步系统进行了实现,利用Modelsim 6.0软件进行了仿真测试。仿真结果表明,本方案设计的同步系统工作稳定,满足性能要求。  0 引言  在数字通信系统中,...
  • 帧同步 1. 状态同步 状态同步简单来说就是同步游戏中的各种状态,当客户端发送游戏动作到服务器,服务器接收到之后,通过计算游戏行为的结果,然后广播下发给客户端游戏中的各种状态数据。客户端接收到状态数据后...
  • ​有没有遇到过这样的问题:视频在播放器播放时没问题,但是导入剪辑软件之后就会出现声画不同步的情况?其实导致声画不同步的原因有很多,如视频和音频的采样率不同、声音编码器的延迟等。但是,90%以上的声画不...
  • 在现代多人游戏中,多个客户端之间的通讯,多以同步多方状态为主要目标。为了实现这个目标,主要有两个方向的技术:  一种叫状态同步:客户端发送...这种做法类似于各个客户端都远程操作服务器上的软件。最早的mu...
  • 在计算机视觉和计算机图形应用中使用消费类相机的主要障碍是缺乏同步硬件。 我们提出了一种基于频闪仪的同步方法,用于电荷耦合器件(CCD... 结果表明,与传统软件方法的同步相比,我们的方法可以达到更高的精度。
  • PC端软件控制器RDM模块、芯片配合标准 RDM 协议设备可支持灯具状态的实时监测;◆ 支持远程读写(搜索发现)RDM 设备唯一 UID◆ 支持 UID 修改 DMX 地址◆ 支持回读 RDM 设备固件版本号◆ 支持温度实时监测◆ 支持电流...
  • 引言:本文我们继续介绍Xilinx FFT IP核,主要对IP核的接口进行详细说明,并对IP核信号各个通道接口数据格式进行详细介绍,方便我们进行FPGA软件设计。本文介绍以下接口信号:时钟信号复位信号事件信号AXI4-Stream...
  • 本文主要说明用软件实现同步时代码设计的思想,其涉及到两个层次:DataLock、SwapLock。DataLock用于保证各渲染计算机在每个渲染周期使用相同的场景数据进行更新,以避免各计算机之间场景内容的不一致。SwapLock用于...
  • [1] 根据上一 lastvp 的播放时长 duration,校正等到 delay 值,duration 是上一理想播放时长,delay 是上一实际播放时长,根据delay 值可以计算得到当前的播放时刻 [2] 如果当前 vp 播放时刻未到,则继续...
  • EmbeddedDevelop标签:CAN、 CANOpen、 CanFestival一、写在前面继续为大家更新CAN总线协议相关内容,这些内容其实也比较重要,会直接影响到你后期CAN总线的编程,夸张一点的说会影响到你的软件架构。本文内容相对来...
  • FFmpeg 工程自带的一个简单播放器,尽管称为简单播放器,其代码实现仍显得过为复杂,本实验对 ffplay.c 进行删减,删掉复杂的命令选项、滤镜操作、SEEK 操作、逐插放等功能,仅保留最核心的音视频同步部分。...
  • 四种以太格式

    2020-08-11 17:38:52
    用过NetXray之类的抓包软件的人,可能经常会被一些不同的FrameHeader搞糊涂,为何用的Frame的Header是这样的,而另外的又不一样。这是因为在Ethernet中存在几种不同的格式,下面我就简单介绍一下几种不同的格式...
  • 影音同步修正

    2017-06-11 15:13:00
    影音同步由播放器自动修正,本套软件改进思路如下: 1. 产生误差的原因 1.1 非同源时钟:当两个定时器非同一个时钟源时,彼此产生积累误差。由于音频和视频数据性质不同,分音频和视频两套播放系统,需要两个...
  • 当一个不同步现象、 强噪声或一个断开符号被检测到的时候, 这个位有硬件置 1。 由软件向 USART_ICR 寄存器的 FECF 位写 1, 可以清除这个标志。 在智能卡模式 中发送数据时, 当重发尝试的次数达到上限, 由没有...
  • 有线同步--ASP007

    2020-07-16 14:34:39
    有线同步中的软件特性 第二章 有线同步的系统架构 ** 2.1 说明 ** 一个RTLS系统由移动的标签和固定的基站组网构成的。其中一个较为高效技术,就是标签会周期性的发送“blink”消息,该消息会被许多附近的基站...
  • 同步传输 与 异步传输

    千次阅读 2017-07-07 15:48:47
    如一个字符发送为1秒,接受也需要为1秒(小于一秒的话,利用软件延时) 2、同步异步最大的不同是:异步传输时,间隙可以不同步。比如,1传输完后 隔5秒传输2.间隙为5秒。再接受方,接受完1后,可以隔10秒...
  • 通信原理软件实验(6)

    2020-12-27 20:13:59
    文章目录实验六 位同步与帧同步1.位同步框图搭建2.帧同步框图搭建总结 实验六 位同步与帧同步        本实验的目的是用Simulink实现位同步和帧同步,其中位同步采用微分整流法,帧同步采用7位...
  • 一、Ethereal抓包 打开“命令提示符”窗口,使用“Ping”命令测试...因此,这8个报文将被网络协议分析软件捕获。 单击Capture-按默认过滤器-Start-抓包-Stop。 二、以太网结构解析 8个字节的前同步码分为7字...
  • 同步与异步串行通信

    千次阅读 2010-01-19 14:58:00
    同步通信是按照软件识别同步字符来实现数据的发送和接收,异步通信是一种利用字符的再同步技术的通信方式。 2.1同步通信 同步通信是一种连续串行传送数据的通信方式,一次通信只传送一信息。这里的信息与异步...
  • 同步通信是按照软件识别同步字符来实现数据的发送和接收,异步通信是一种利用字符的再同步技术的通信方式。  同步通信  同步通信是一种连续串行传送数据的通信方式,一次通信只传送一信息。这里的信息与异步...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 419
精华内容 167
关键字:

帧同步软件