再谈网游同步技术

实时动作游戏在近年来得到迅猛的发展。而游戏同步问题,成为大家继续解决的核心问题之一。早在 2004 年,国内游戏开发还处于慢节奏 RPG 满天飞的情况下,我就开始实时动作游戏研究,分别在 2005-2006 期间写了一系列相关文章,被好多网站转载:

帧间同步模式:《帧锁定同步算法》(2007): http://www.skywind.me/blog/archives/131

玩法规避模式:《网络游戏同步法则》(2005): http://www.skywind.me/blog/archives/112

预测插值模式:《影子跟随算法》(2007): http://www.skywind.me/blog/archives/1145

如今十年过去,网上越来越多的人开始讨论游戏同步技术了,然而很多文章往往只针对某种特定的游戏情况,而观点又经常以偏概全。很多人并没有真正开发过实时动作游戏,更别说了解同步技术的前世今生了。转载别人的观点并加上自己理解的人很多,实际动过手的人很少。避免给更多人造成无谓的误导,我今天基于先前的实践和对欧美动作游戏,战网游戏,主机游戏(PSN,XBox Live等)网络技术的了解,来对这个问题做一个简单总结:

网速的变化

开发快速动作游戏,首先要对公网的网络质量数据有详细的了解。这里所说到的网速,是指 RTT,数据往返一周的毫秒时间,而非每秒传送多少 KB/s。我写这篇文章是基于我 2005-2006 年开发的东西来说的,当时国内公网质量比国外差很多:

上图为 2005-2006 年国内的网络环境,某三个省级 IDC的情况采样。当时公网 RTT平均值基本在 100ms,120ms 左右徘徊。所以我文中引用了很多 100ms。这个情况在2009 年以后已经好了很多(60ms 的 rtt)。到了 2012 年以后,公网平均 RTT已经降低到平均 40ms-50ms,省内平均 10ms 以内了:

上图为 2015 年某省级 IDC 的全国延迟情况,如若全国多布点以及区别电信联通的话,平均延迟能控制在 20ms 以内,延迟基本接近国外水平(当然带宽还差很多),比我当年文章中提到的网络情况好了不少。

帧间同步法

关于帧间同步的 “帧锁定算法” 系列的方法有很多类似实现(包括后面提到的帧间无等待改进,包括 LockStep 等),但是他们的核心都是一个:保证所有客户端每帧的输入都一样。这样的方式被格斗游戏,RTS 和足球(FIFA类)、篮球(NBA)等体育和动作游戏大量使用,比如我们熟悉的各大战网平台游戏(Xbox Live等),还有很多基于模拟器的街机对战平台。以及不少大型多人横版动作游戏。以开发便利,同步逻辑直观而受到大家欢迎。

帧锁定算法多用在 C/S 模型中(或者一人做主多人做从的P2P里),它和 LockStep(多用于P2P)共同存在的问题就是 “网速慢的玩家会卡到网速快的玩家”,老式游戏经常一个角色断网,所有人就在那里等待。为此出现了帧锁定的改良版本 “乐观帧锁定”(具体描述见帧锁定文章的下半部分)经过了不少游戏的实践检验。先前还有几款上线的横版格斗页游(如熟知的街机三国)用 Flash 的 TCP without NODELAY 来每秒 20 个关键帧的模式(特意找该游戏开发者确认了一下)跑该算法(由于近两年国内网速提高,Flash 的 Tcp without NODELAY 也能做很多事情了),效果还不错。

具体实施时用不着按照文所述每一个步奏都相同,可以有很多变通。比如不一定是有变化的时候才通知服务端,有线上某横版格斗页游就是也可以每秒 20 次向服务端直接发送数据(flash 时钟不准需要自己独立计时),服务端再每秒 40 次更新回所有客户端,看具体情况而定。

也有使用 UDP 的端游,客户端每秒钟上传 50 次键盘信息到服务端,丢了就丢了,后面持续发送过来的键盘数据会覆盖前面的数据,所以丢了没关系,更快捷。当然,UDP 也不是必须的,近两年网速提高很快,省内都能做到 10ms 的 RTT 了,跨省也就 50ms 的 rtt,不少页游上用该方法上裸的 TCP 照样跑的很顺畅。

而近两年国外动作游戏领域也涌现出其他一些新的改良方法,比如 Time Warp,以客户端先行+逻辑不一致时回滚的方式,带来了更好的同步效果,俗称时间回退法。不果国内暂时没看到有游戏这么尝试,更多的是国外近两年的双人动作游戏比较多,要求游戏每帧状态都可以保存,逻辑上开发会复杂一些。国内大部分是超过两人出去副本的,在3-4 人出去 PK 的情况下,引入状态回退,会让整个效果大打折扣。不过 2 人的效果确实有所改进,有兴趣的同学可以搜索 Time Warp 相关的论文。

2009年,云游戏(游戏远程渲染)技术得到广泛应用,客户端上传操作,服务端远程渲染,并以低延迟视频编码流的方式传回给客户端,用的就是这样类似的技术。客户端不需要高额的硬件,也不存在盗版问题,其中 Gaikai 和 OnLive 两家公司做的比较好。

2012年,Sony 推出 Playstation Now 技术,可以在 PSV 和 PS3/PS4 上玩云游戏,玩家不需要购买游戏就可以免费体验一定时间。使得 PSV/PS3 等低端硬件也可以流畅的跑 PS4 游戏。

但是目前国外网络环境下跑的还比较流畅,国内的网络环境要低延迟传送 HD画质的视频流还比较困难,视频都是比较费带宽的。但是帧锁定等保证每帧输入一致的算法,在当今的网络质量下传递一下玩家操作,还是没有任何问题的。

状态同步法

对于逻辑不需要精确到帧的游戏类型而言(RPG/ARPG,FPS,赛车),允许每个客户端屏幕上显示的内容不同,只要将他们统一到一个逻辑中即可,这部分见:“网络游戏同步法则”(最好给策划看看这篇,从玩法上规避)。如果是 RPG 游戏,其实更多是使用障眼法从玩法和动画效果上减少 “一次性的”,“决定性”的事件即可:

RPG 游戏的移动很简单,只需要 “谁在哪里朝着哪里移动”,客户端再做一些简单的平滑处理即可,不需要额外的 “时间” 参数。比如《魔兽世界》移动时,就是差不多每秒发送一次(坐标,朝向,速度),别的客户端收到以后就会矫正一下,如果矫正错误,比如 A 本来往北走突然拐弯向东,这个数据包传到 B 上,B 屏幕上的A可能在拐弯前往北跑了更远,致使拐弯向东时被树卡住,那么 B 就会看到 A 被树卡了两秒无法移动,然后突然瞬间移动到新的坐标,继续朝着东跑。

通常 RPG 攻击分为 “有锁定攻击” 和 “无锁定攻击”,有锁定攻击意思是,我朝你发射火球,不管你怎么跑,火球都会追踪并射击到你,比如你在我面前横着跑过,我向你发射火球,可以发现火球并不是直线飞行,而是曲线追踪着你就过去了,这叫有锁定攻击。无锁定攻击一般是范围攻击,先播放个动画(比如挥刀),然后将攻击请求提交服务器,服务器结果回来时,动画刚好播放完毕,然后大家一起减血。

而 FPS和 赛车类游戏的同步性要求比 RPG高很多,每秒发包量也会多很多(10-30个),多半采用位置预测及坐标差值的 “导航推测算法(DR)”,具体实现见我的:“影子跟随算法”(DR算法的一个改进实现)。

这类算法由于位置判定更为精确,所以计算量大,很多没法服务端判断,而是客户端直接判断,比如 FPS 射击是否打到别人,客户端先判断,除了狙击这种一枪毙命的射击外基本都是客户端判断的。由于计算更为复杂,每秒同步发包差不多到 30 个以上,这样的模式下,每局游戏的人数也不可能很多,一般 16 人左右。而且很多才用 P2P 的方式运行,具体 FPS 游戏的实现,及 DR 算法的代码编写,见 “影子跟随算法” 这篇文章。

其实状态同步是一种乐观的同步方法,认为大家屏幕上的东西不同没关系,只要每次操作的结果相同即可,不需要象 “帧间同步” 那样保证每帧都一样,因此,对网速的要求也没有 “帧间同步” 系列算法那么苛刻,一般 100ms-200ms 都是能够接受的(DiabloIII 里面 300ms 的延迟照样打),偶尔网络抖一下,出现 1 秒的延迟,也能掩盖过去。然而比起 “帧间同步”,状态同步方式对玩法有不少要求,诸如 “一次性”,“决定性” 的事件要少很多,而且代码编写会复杂一些,不果由于能容忍更坏的网络情况,以及容纳更多同时游戏的人数,在一些玩法确定的游戏中(RPG,FPS,赛车),被广泛使用。

而状态同步又分为 “DR同步” 和 “非DR同步”,前者针对 FPS,赛车或者更激烈点的 ARPG,后者针对 RPG 和普通 ARPG。他们对网速的要求和错误的容忍度也是不一样,当然,带来的游戏即时感也是不同的。

总得来说,你希望游戏体验更爽快,即时感更强,那么你每秒发包数就越多,每局(副本)支持的人数越少;而你如果追求对网络的容忍,想降低发包数,并且增加同时游戏的人数,那么相应的就需要以降低即时感为代价,其二者不可得兼。然而聪明的策划和程序们总能想出很多好主意,利用障眼法和玩法规避,动作掩盖等方法,在相同的情况下来掩盖延迟,让玩家 “看起来” 更加 “即时” 和 “爽快”,而这个方法具体该怎么做,并没有统一的做法,就得大家结合自己的游戏和玩法,发挥自己的聪明才智了。

结果同步法

结果同步往往比较简单,位置即使全部错乱或者延迟很久都没有关系,因为游戏过程完全不在乎位置,只在乎最后的结果,比如《梦幻西游》这样的“回合制 RPG” 游戏,屏幕上的人走到哪里确实无所谓,所有操作都是要点击或者选择菜单来下命令,象这样的游戏背后其实是文字游戏,只是加了一个图形的壳。

游戏表面上看起来是动作/RTS 游戏,但是没有玩家直接协作和对抗,都是单机游戏,并不需要同步什么东西,服务端只要监测下结果不离谱即可,延迟检测都没关系。基本是 PVE,而且无协作。即使是 PVP也就是打一下别人的离线数据,和无同步回合制游戏并无本质上的区别。

传输协议选择

老话题 TCP 还是 UDP,答案是大部分时候,TCP 打开 NODELAY 即可,现在网络情况好了很多,没必要引入新的复杂度。即便是“帧锁定算法”上线的多人实时格斗游戏,也有在用 TCP 跑着的。帧间同步如果能够做到更好的架设机房,那么延迟基本能控制在 10ms 以内,将游戏玩家按照区域分服务器,让他们选择更快的服务器。

即便是带 DR 的状态同步,很多也都是 TCP 的,《魔兽世界》和《暗黑破坏神3》都是基于 TCP 来实现的,所以我的建议是,先上 TCP,把你的游戏发布出去。

当然,等到你的游戏发布出去了,开始挣钱了,你想改进你的游戏效果,特别是高峰期的卡顿比例(需要收集客户端统计),那么你可以使用 UDP 来改进,《街霸4》和《英雄联盟》都是使用 UDP 的,比如你可以使用 libenet(英雄联盟用的)。不过 libenet 所采用的传输技术,是上世纪的标准 ARQ做法了,《街霸4》所采用的传输技术远远高过 libenet,如果你想采用更为现代的传输技术,赢得更低延迟的话,可以使用我的 “快速传输协议-KCP”(http://www.skywind.me/blog/archives/1048),被再若干上线项目和开源项目使用的协议,效果远远 PK libenet。

在使用 KCP 时,你可以用在你 TCP 的基础上,再登陆时服务端返回 UDP端口和密钥,客户端通过 TCP 收到以后,向服务端的 UDP 端口每隔一秒重复发送包含握手信息,直到服务端返回成功或者失败。服务端通过 UDP 传上来的密钥得知该客户端 sockaddr 对应的 TCP 连接,这样就建立 TCP 连接到 UDP 连接的映射关系。为了保持连接和 NAT 出口映射,客户端一般需要每 60 秒就发送一个 UDP 心跳,服务端收到后回复客户端,再在这个 UDP 连接的基础上增加调用 KCP 的逻辑,实现快速可靠传输,这样一套 TCP/UDP 两用的传输系统就建立了。

中国的网络情况比较特殊,会存在有些网络 UDP 连接不上的情况,因此都是先连接 TCP,然后试图 UDP,UDP 不通的情况下,退回 TCP 也能正常游戏,一旦 TCP 断开,则认为 UDP 也断开了。

不果归根结底,还是先上 TCP,再根据自己游戏的特点和是否出现传输问题,选择 UDP。

话题总结

根据游戏类型,选择恰当的同步方式和传输协议是最基础的问题,很多讲述网络同步的文章一般就是只会强调上述那么多种算法的其中一种方式,好像使用该方式就可以 hold 住所有游戏一样的,其实并非如此。技术需要多和策划沟通,别策划一个需求下来,技术就来一句:无法实现,这样的游戏永远没有竞争力。就像国内当时都是慢节奏 RPG,偶尔有点 ARPG 的时候,大家觉得《DNF》这样的游戏无法实现,于是韩国实现了,在市场上取得了先机,国内才慢慢跟进,再一看,哇塞,好多坑呢。

然后开发者开始在网上寻找各种同步算法,东一榔头西一棒子,明明是一款需要帧间同步的格斗游戏,结果却上了导航推测,最后发现问题永远解决不了,一堆 BUG 这就叫误导。正因为我 2004 年就开始弄同步相关的问题,期间也指导过不少游戏设计他们的同步方案,所以这次相当于将以前的观点做一个总结和点评,根据自己的游戏类型选择最适合的同步算法,为玩家提供更好的体验才是关键。

Loading

About skywind

Putty 本无树,MinGW 亦非台
This entry was posted in 游戏开发, 网络编程 and tagged , . Bookmark the permalink.

21 Responses to 再谈网游同步技术

  1. egmkang says:

    街机三国其实是帧同步的,服务器只负责同步数据,战斗都是在客户端完成的.

  2. skywind says:

    @egmkang
    街机三国,键盘操作,服务端基本只记录吧,人为阻塞网络一段时间了照样能打,这不叫同步吧?

  3. dictator says:

    想咨询一下:
    1、kcp现在有用在已经上线的网游/手游项目么
    2、另外在udp包高速分发的情境下,你们有进行加密么,这个貌似会成为一个热点

  4. skywind says:

    @dictator
    不想公开谈论自己的工作,开源项目的话,dog-tunnel用了,加密的话是 KCP上一层(应用层),和下一层(传输层),可以看 github kcp里面的 issue,有人问过,关闭了单子。

  5. dictator says:

    @skywind
    好的,看了issue,thx

  6. andylee says:

    “无锁定攻击一般是范围攻击,先播放个动画(比如挥刀),然后将攻击请求提交服务器,服务器结果回来时,动画刚好播放完毕,然后大家一起减血。”

    韦法王你好, 请教个问题:
    挥刀的时候提交请求到服务器,服务器判定攻击有效,在server端是马上减血呢,还是配合客户端的动画,延时一段时间才减血(可以先返回减血的数量给客户端)。

    如果服务端马上减血的话,比如有个被攻击者刚好因为这次伤害死亡了,但是客户端的动画还没播完,被攻击者还没判定为死亡,他马上释放一个技能,这个请求提交到服务器自然是被驳回的,因为服务器上的他已经死亡了。

    如果服务端在批准攻击之后延时减血的话,挥刀动画播完之前,被攻击者还可以进行最后的反击。

    请问一般是怎样处理的呢?

  7. neb says:

    @andylee
    在RPG游戏中将技能释放流程分解为Action, Attack两段(Action是挥刀阶段, Attack是刀砍伤害的阶段), 先后分两个包发到服务端, 在Action阶段可以检查各种条件是否满足, 在Attack阶段额外要做伤害计算.
    如果A,B两个攻击者同时发起攻击, Action阶段都通过, 接下来就看哪个客户端先将Attack包发送到服务器, 先到的先造成伤害, 如果目标死亡则后面的Attack失败.

  8. weipinglan says:

    感谢无私分享!可以发你的qq给我邮箱吗?希望能请教你.
    国内真正研究同步的真的不多,08年跟我师傅一起制作的雷电ol,同步算法就是帧同步.(我师傅也是网易系的)
    去年我做的手游也是使用帧同步,玩家充当主机.这种效果非常不好(3g,4g,wifi下的混杂).现在纠结在选择tcp还是udp.
    今年的产品,以游戏服务器充当同步主机,但在承载数上怕没保证(我游戏是跑60fps,服务器如果是按50fps下发同步数据,那当连接数是很大时,服务器不知道还能不能稳定给客户端当主频)

  9. afsdfg says:

    @skywind
    街机三国用的是帧同步

  10. skywind says:

    @afsdfg
    了解啊,不好意思,写错,已经修正。

  11. wuyuntao says:

    请教一下,Time Warp 或 Trailing State Synchronization 底层是比较好实现的,保存上次同步的快照即可。但表现层要怎么实现呢,比如说回退的时候,正好一个角色的动画播到一半,或者粒子的特效播到一半,这种情况一般是怎么处理的呢?

  12. kaka says:

    目前为止玩过的网游中实时无锁定战斗同步体验最好的是龙之谷,自己也尝试模仿过,效果还是差一些 感觉混合用到了博主说到的几种方案 能分析下它的整体机制吗?

  13. 燃烧吧,小宇宙! says:

    请教一下,现在的移动网络RTT大概是多少?自己搜了下搜不到..
    然后. 文中有个笔误吧,Flash 的 “TCP without NODELAY “应该是”with”吧

  14. orzsxc says:

    想请教下,皇室战争这种两人即时对战游戏是用帧锁定同步实现的吗?

  15. Soar says:

    街霸4 的传输技术远远高于 lol
    那么 博主 能否介绍一下 街霸4 使用的传输技术呢
    还有 街霸4 的技术和KCP 的相同和异同及 比较

  16. chiron says:

    @orzsxc
    皇室战争每次出怪都有1秒缓冲,同步太好弄了。

  17. AA says:

      “以客户端先行+逻辑不一致时回滚的方式,带来了更好的同步效果,俗称时间回退法。不果国内暂时没看到有游戏这么尝试”,博主知道Arclive(Suparc)和游聚平台吗(Arc刚关闭)?你去游聚格斗游戏街霸2区,找一个延迟大于150ms的对手测试一下,这两个平台应该都采用了这种回退方式,延迟大的时候,你看到“自己出招打了对方”,但如果对手实际出招比你早,会重新纠正并回退到“你被对手打”的画面,然后继续进行

  18. 黯然 says:

    @kaka
    现在有研究出个结果了吗?我也想知道龙之谷那一套是怎么做的,体验很棒。

  19. Pingback: フレーム同期の最適化における難点及びソリューション

  20. Pingback: 帧同步与状态同步 – 奇遇的博客

  21. Pingback: 帧同步游戏中使用 Run-Ahead 隐藏输入延迟 - Skywind Inside

Leave a Reply

Your email address will not be published. Required fields are marked *