守望先锋全场最佳判定如何打全场最佳

守望先锋开发者承诺改进全场最佳的系统守望先锋开发者承诺改进全场最佳的系统布拉卡百家号昨晚,有玩家在 reddit上发布了一个全场最佳镜头,查莉娅大中 6人,猎空脉冲炸弹全部击杀,但最佳却给到了只是挥了挥锤头的莱因哈特。《守望先锋》首席工程师 Bill Warnecke看到了这个帖子,他回复道:“我们很想改进全场最佳系统。一旦我们有时间,就会把很多积压许久的想法付诸实践。”另外一名玩家也参与到了讨论之中,他表示刚刚那个全场最佳镜头并没有什么毛病,因为莱因哈特打了最多的伤害。不过话说回来,全场最佳系统确实有点小问题,比如天使只是复活了某人,或者托比昂和秩序之光只是逛逛街就能上最佳这种。最后该玩家用小字写道“拜托给查莉娅和安娜制作能够双人同镜的最佳吧”。没想到得到了 Bill Warnecke的又一次回复:“对!我主要想表达的是,你们对于该系统的任何反馈都是有用的,并且我们以后绝对会改进它。这是《守望先锋》中我们最喜欢的系统之一,我们能做的还有很多。”看来《守望先锋》开发团队对于全场最佳系统早就有了很多想法,只是因为它一直不是优先开发的项目,所以被一拖再拖,毕竟玩家迫切需要的内容还有很多,比如新英雄、新动画、新的限时活动等等。未来,也许全场最佳系统真的会制作成双人同镜的模式,让一向默默无闻付出的团队型英雄得到更多的支持与肯定,只不过我们似乎还要等待一段时间。本文由百家号作者上传并发布,百家号仅提供信息发布平台。文章仅代表作者个人观点,不代表百度立场。未经作者许可,不得转载。布拉卡百家号最近更新:简介:希望我们的内容能让您开心,天天快乐哦作者最新文章相关文章>守望先锋全场最佳的判定机制与四种方法
守望先锋全场最佳的判定机制与四种方法
10:36:39 条 来源:努巴尼守望先锋 作者:摇滚肉肉 ]
  玩守望先锋的时候,能不能上最后的装逼镜头乃是游戏最大的乐趣之一,感觉所有人都在看自己的精彩表现那种装逼感简直不要太溜。
  最近很多玩家在猜测全场最佳的判定方法,到底怎么样才能上最佳。肉肉在此给各位萌新们科普一下,暴雪在此前的蓝贴里面已经介绍过全场最佳的判定机制,总共有4种方法上最佳。分别是:高评分,救世主,神枪手,终结者。下面给大家详细介绍分析一下,教大家实力装逼。
  守望先锋会捕捉游戏中4种类型的精彩镜头,然后对比这些瞬间,找出那一轮中最佳的精彩镜头成为最后的全场最佳。如果你的镜头也足够精彩,但是最终没有上全场最佳,那么就会被纪录到你自己的亮眼表现中。
  一、高评分全场最佳
  最常见的全场最佳的镜头,这个分类趋向于纪录一次多杀以及快速的连续击杀。更高的多杀与更快的连杀,越接近目标点,都会得到更高的评分。此外,重生技能原理相同,一次性复活多个人,越接近目标点,评分越高。
  这个最好理解,因为大家见得最多的就是这样的镜头,一旦被对手连杀了,就知道卧槽了,要出现在对手的全场最佳镜头里当演员了。
  除了多杀与连杀之外,要注意的越靠近目标点的多杀连杀,分数也会更高。像一些控制地图上,在点上大招连杀几乎是必上最佳镜头的。天使在点上一次复活3个人以上,那也基本上可以拿到镜头。
  如果本场最佳的分类是高分,那就不会显示镜头分类。其他3个分类的最佳镜头在底部中间会显示出镜头的分类。
  在更早之前的版本,这个分类的比重最高,因此也导致了其他类型英雄几乎不太可能上镜头,全都是各种有击杀能力的英雄上镜头。所以暴雪下调了这个分类的比重,并且有了另外3个分类。即使玩的是不会杀人的辅助英雄,也有机会上全场最佳。
  所以要打出这个最佳镜头的方法基本上最简单了,在尽量靠近点上的地方连杀或全体复活。(如果可以的话谁都这么想啊!)
  二、救世主最佳镜头
  这个分类趋向于记录一个玩家在另外一个玩家马上就要死了的情况下救了他。例如:一个冲锋撞上了一个敌人,而敌人的队友把莱因哈特眩晕或者击杀,那么敌人的队友就会获得一个很高的&救世主&评分。
  注意这个分类底下中间会有分类的名称表示这是一个救世主类型的最佳镜头。神枪手跟终结者同理。
  这个镜头显而易见是记录一些极限操作的,无论是队友还是对手都会印象深刻。虽然救世主的分类听起来有点类似辅助的镜头,但是目前来看,跟的大招,天使的加血这些瞬间拯救的队友并不会上救世主的镜头。所以这里的救,指的是阻止了敌人来挽救,而不是给自己治疗来挽救。
  想要拿到这个全场最佳装逼镜头的话,就需要多多关注你队友的动向,发现你的队友被打的时候,不要第一时间救他,等他快被打死只剩一点点血的时候再出手把敌人消灭。(本句删除)
  三、神枪手最佳镜头
  这个分类趋向于记录非常有技巧的高难度的击杀镜头。
  对于这个分类,游戏会充分考虑各种因素,包括移动速度,射击的距离,是否爆头,以及被击杀的人是否在空中等因素,这些因素综合起来将会提升玩家的&神枪手&分数。
  要上这个镜头基本上比较碰运气,尤其是高水准的比赛中,职业选手经常各种高难度击杀,所以这个镜头不仅仅是操作难度,其实跟比赛本身的所有玩家实力水准也有关系。
  曾经C9在比赛里面那个黒百合在好莱坞A点外面的树上惊艳的飞天神狙,技惊四座,成了动图广为流传。暴雪就是意识到了这一点,所以有了神枪手这个类别。而那个树,也被暴雪很鸡贼的和谐了,黒百合再也没办法在那个地方勾到那么高了。
  因为这个镜头比较靠运气,其实很难拿到,当然刻意追求的话也不是不可以,玩黒百合狙击的时候,不要蹲在角落里狙,等法老之鹰飞起来的瞬间,你也黒百合也瞬间勾一个高处,空中360旋转开镜击杀法老之鹰。(本句删除)
  四、终结者最佳镜头
  这个分类趋向于记录一个玩家击杀了一个或者多个正在进行非常重要行动的玩家(或者多个玩家),从而阻止了他们达成目标。
  例如:黒百合狙击击杀了敌人放大招放到一半的卢西奥,在这个例子中,游戏会预测卢西奥的这个大招对于当前的战局有多么重要,并据此来给黒百合玩家一个高的&终结者&分数。
  除了高评分之外,终结者这个镜头相对比较好拿一点了。因为玩到一定程度的玩家,都肯定有一定的判断能力,知道什么样的行动有巨大威胁。
  比如说在据点上击杀了大招中的法老之鹰,这样的防止了一场团灭,击杀了马上要在点上开大的死神,等等。
  所以想要拿到终结者镜头的话,反而可能是比较容易的,让你连杀可能困难,但是只盯住一个人,等他进点里开大的时候干死他则相对容易得多。(本句删除)
  以上就是4种最佳镜头的判定机制跟获取方法。玩家对这个设定非常喜欢,而暴雪对这个特性也很满意,他们也在不断的优化改进当中。
  目前来看,这4个对玩家来说感觉意义很大的镜头,暴雪没有设置对应的成就,肉肉个人认为这是因为不希望玩家刻意在游戏中追求镜头而忽略了游戏胜负目标本身,最佳镜头应该是玩家为了取得胜利全力以赴战斗的一个附加奖赏,而不是最佳镜头本身。
  所有人全力以赴取得胜利才是最重要的,而不仅仅只是最后上镜的5秒钟。
  ps:可能某些玩家会问,为什么肉肉这么牛逼,每个镜头都能拿到?这个嘛,先容我多装一会逼。关键字是:自定义比赛,演员,以及这一篇攻略。
看完本文后有何评价?已有0人评价,点选表情后可看到其他玩家的表态。你想不到的全场最佳 这个托比昂我真的服了_锐派游戏守望先锋专区 replays.net
"不是每个全场最佳都是神级操作,也有逗比的也有无聊的,下面小编给大家带来的就是该玩家的全场最佳,只能说我服了!"
守望先锋丶索夫
overwatch老瓜
守望、六月六月
落小跑贼帅的OW直播间
川烈守望先锋教学第10期 80%左右钩中率路霸技巧
《火力全开》---锐派游戏一周TOP-5精彩集锦
守望先锋战术素养提高篇第一期侦查技术
暴走漫画王尼玛:守望先锋这屁股有毒!
守望先锋战术素养提高篇
帅炸 六杀死神激燃混剪 MandoPony原创
守望先锋最新同人漫画 这一波壁纸我服啦!
守望先锋D.VA同人再现 韩国美少女赤裸出镜
小美同人漫画形象 中国少女玩出新花样
76号只想上个最佳而已 大神漫画悲惨的士兵
有爱玩家手绘分享 守望先锋全英雄仓鼠化
OW测服天梯奖励图片 全套黄金定制闪瞎双眼
War3.Replays.Net - Dota.Replays.Net - Lol.Replays.Net - DotA2.Replays.Net - Sc2.Replays.Net - Bbs.Replays.Net - P.Replays.NetCopyright ? 2012 Replays.Net All Rights Reserved.Powered by Replays.Net 京ICP备号-1后使用快捷导航没有帐号?
 论坛入口:
  |   |    |   | 
《守望先锋》阵亡镜头、全场最佳和亮眼表现是如何设计的
游戏类型:&  设计类型:&
在GDC2017 《守望先锋》Replay System的分享会上,来自暴雪的Tim Ford介绍了《守望先锋》服务器团队开发工程师Phil Orwig,分享了回放系统(Replay System)的设计。
image003.png (457.4 KB, 下载次数: 9)
19:23 上传
image005.png (585.98 KB, 下载次数: 7)
19:23 上传
大家好, 请关闭手机或者至少调至静音状态,我们准备开始了。
欢迎来到“守望先锋”(下文统称Overwatch) 回放技术分享,今天的内容包括阵亡镜头、全场最佳和亮眼表现。我是Phil Orwig,是Overwatch服务器团队开发工程师。
image007.png (697.21 KB, 下载次数: 5)
19:23 上传
那么(下面)我们将深入到Overwatch里,这是暴雪在第一人称射击游戏领域的处女作。它是一个团队合作竞技游戏,主要特色是各个身怀绝技的英雄。
image009.png (427.47 KB, 下载次数: 4)
19:23 上传
这次分享会覆盖回放系统(Replay System)的设计目标,深入架构与实现的细节,以及深度打磨的过程。同时,还会讨论我们遇到的挑战以及对应做出的折衷,最后会展望一下回放系统未来的目标。
image011.png (302.56 KB, 下载次数: 10)
19:23 上传
那么回放系统的概要设计(high level design)目标是什么呢?
image013.png (351.2 KB, 下载次数: 7)
19:23 上传
这次分享的标题就预示着必须给出答案,对吧?概要需求是创建一个单一的中央系统,能够支持阵亡镜头、全场最佳和亮眼表现,除此之外我们还特别需要能够生成录像文件,在开发期间可以用来做内部调试。
下面开始深入介绍每个议题。
image015.png (506.09 KB, 下载次数: 6)
19:23 上传
每次玩家死亡时,游戏里就会显示临死前几秒钟的――大部分情况是以凶手(killer)视角来看的――死因及死亡过程。阵亡镜头可以帮助玩家理解他们是怎么死的,以及为什么会死,有一定的教学作用。
上面展示的视频例子(狂鼠被半藏杀死的例子)中,可以看到死亡过程中的几个关键节点,玩家死亡时有3件值得注意的事:玩家尸体布偶化(ragdoll,也叫布娃娃,专用术语,全称Ragdoll physics,是用在电子游戏的物理引擎中代替传统静态动画的可变性角色动画系统),方便判断死亡方式(译注:估计说的是炸死、摔死还是射死等);游戏镜头朝向凶手;凶手轮廓描边,即使隔着墙也可以看见,从而使你查看得更加清楚(译注:这一点在视频中没有得到展示)。
我们会以淡入的方式进入阵亡镜头,展示死前一段时间以及死后几秒钟的画面。你能看见自己的尸体布偶,然后明白自己是怎么死的。
image017.png (407.75 KB, 下载次数: 5)
19:23 上传
我们希望阵亡镜头可以让你学到东西并提高技巧,但不是每个玩家都能享受这个目睹自己死亡的过程。我个人就可以证明,看着自己的英雄一次次倒下带来的那种无力感,如果持续体验这个过程,慢慢地会感觉这简直是黑色幽默(macabre sense of humor)。
image019.png (103.95 KB, 下载次数: 4)
19:23 上传
另一方面,“全场最佳”是个赛后集会点,在此你和伙伴们可以庆祝共同创造的精彩游戏体验,也是一个共同分享的伟大时刻。它可以激励玩家为了得到最佳而拼尽全力,证明自己,同时炫耀自己独有的个性化英雄形象。
在本部分结束前,一起看下这个过程,我们会从队伍胜利播报开始,切到玩家英雄特写,最后过渡到精彩的团队合作与高超技巧的展示。
有点技术问题,稍等一下。。。好了,请欣赏我的“全场最佳”
image021.png (682.61 KB, 下载次数: 6)
19:23 上传
image023.png (567.53 KB, 下载次数: 7)
19:23 上传
嘿嘿,我没那么厉害啦,其实对面都是机器人 (众笑)(译注:视频中Phil拿了个4杀)。
玩家如果觉得自己可能能成最佳,他们就会提前卖弄,像下面这样。
image025.png (714.22 KB, 下载次数: 6)
19:23 上传
image027.png (846.95 KB, 下载次数: 5)
19:23 上传
视频中的死神本来已经被查莉娅的大招“重力喷涌”困住了,但是他通过幽灵形态成功逃脱,此时他的大招能量刚好充满。他转身放了个喷漆在墙上,紧接着开大,人挡杀人,佛挡杀佛 。
这里最值得注意到就是,这个玩家如此自信以至于在开大前就喷了个漆:Well Played(译注,守望先锋中,时机把握得当,这个行为就会被全场最佳一起记录下来,是高手炫耀的一种常见方式)。那就好像是说:我这次肯定要拿最佳!
image029.png (1.33 MB, 下载次数: 4)
19:23 上传
“全场最佳”带来了很多适于分享的话题,但是假如你没拿到最佳呢?或者虽然你拿到了但是恰好没有合适的录像软件记录这一切呢?对此,我们的解决方案就是“亮眼表现”。12个玩家里面,只有1个人能拿到最佳,而所有人都会得到“亮眼表现”(highlights),这个可以在游戏大厅入口处播放。有了这个功能,玩家可以更容易地捕捉到自己的炫酷表现,并分享给好友。
image031.png (577.3 KB, 下载次数: 5)
19:23 上传
这个例子中亮眼表现的作用,与其说是炫耀技巧,还不如说是用来搞笑。你可以看见一个路霸失手勾到了D.VA的自爆机甲,结果全队被炸上西天,太惨了(众笑)。
image033.png (314.47 KB, 下载次数: 5)
19:23 上传
当然,玩家现在还不能自由地使用回放功能,主要还是给游戏内部开发过程在用,我们也有个快捷键可以保存过去30秒钟的回放数据,并保存到硬盘。如果你的电脑上刚好有Shadow Player之类的DVR设备, 也可以保存录像。
可以想象得到,有了这两大武器,以往很难重现、定位的bug,现在都可以很容易地隔离并修复了。除此之外,性能测试做起来也更加方便了。不需要凑齐12个人,开发者就可以随意修改代码并分析测量性能数据了。
顺便说一句,我们想要凑齐12个人其实并不难,无论何时你只需喊一句:跑下版本呗!马上就能满员甚至还有富余(众笑)。不过这事关开发效率问题,不能每次都喊人帮忙。
开发期间,我们还可以把录像文件连同特制的客户端一起发给合作的硬件厂商(first party vendors),帮他们诊断特定平台下的硬件驱动bug和性能问题。
image035.png (300.9 KB, 下载次数: 6)
19:23 上传
闲话少说,咱们接着讲网络同步,主要包括设计回放系统的常规模型,以及如何交互。
image037.png (422.12 KB, 下载次数: 4)
19:23 上传
回放系统不可避免地要受限于模拟(simulation,游戏逻辑 渲染)模块和网络模块。
Glenn Fiedler(译注,TitanFall的开发)那个很棒的系列博客(译注,该系列博客地址 ),早已经把网络同步机制总结为三类了:
确定性帧同步(deterministic lockstep),常用于竞速游戏或者星际争霸2、魔兽争霸2这样的即时战略游戏;快照插值(snapshot interpolation),FPS经典同步模型,《Quake》最早采用,后续众多FPS在之上做了改进;状态同步(state synchronization),有点像是快照插值的改进版,也是Overwatch所采用的。
注意我的幻灯片下方列出了一些参考文章,正是这些文章启发了我们,设计出了我们自己的同步策略:
GDC2015 Physics for Game Programmers : Networking for Physics ProgrammersI Shot You First: Networking the Gameplay of HALO: REACH[David Aldridge]The TRIBES Engine Networking Model [Mark Frohnmayer]
image039.png (418.11 KB, 下载次数: 6)
19:23 上传
快速浏览一下这些技术。第一个是确定性帧同步,左边有4个客户端,都连接到同一个服务器上。服务器也可以由其中的一个客户端来兼任。简单起见我们假定有个专属的某某云服务器。
image041.png (357.25 KB, 下载次数: 4)
19:23 上传
客户端接收玩家输入并上报给服务器,服务器在接收到全部输入以前,保持锁定当前帧。当全部客户端的输入都上报完成时,服务器才开始模拟游戏进程。
image043.png (366.06 KB, 下载次数: 4)
19:23 上传
处理好全部输入以后,服务器批量打包所有指令,并转发给全部客户端。
image045.png (377.72 KB, 下载次数: 5)
19:23 上传
然后客户端基于刚刚收到的转发指令,开始各自模拟游戏过程。代码处理得当的话,每个客户端都会得到相同的结果,看上去很美妙。
image047.png (382.82 KB, 下载次数: 4)
19:23 上传
基于帧同步实现一个回放系统很方便,只需要记录整个输入流即可实现回放。而且输入通常很容易压缩,所以数据量也不大。
image049.png (355.34 KB, 下载次数: 5)
19:23 上传
确定性帧同步,虽然架构简单,但是也有一些缺陷。它的模拟过程需要严格一致,同一帧内,相同的输入集合在每个客户端必须产生同样的输出才行。
如果做不到这一点,客户端之间就会产生不同步,会对当前处于何种状态各执一词。例如对浮点数依赖程度很高,而浮点数运算很难做到严格一致,尤其在不同的平台架构下更是如此。 一个简单的打印驱动失灵,就能在中断控制器上改掉浮点数精度设置,从而使模拟过程偏离轨道。而且这一点的确会发生。
image051.png (354.38 KB, 下载次数: 11)
19:23 上传
维护一个确定性的模拟过程,除了代码逻辑上的工作以外,还有一个问题就是多玩家联网时产生的。
“等待全部玩家的输入”意味着,任何一个停滞的玩家都会破坏模拟过程的及时性。游戏里玩家数量越多,这个风险越高。简单计算一下,假设一个5分钟时间的游戏,每个玩家卡顿的概率是2%,4个玩家就会有8%的可能性,玩家多达12个时,已经是22%了。
我本应该画个图表来说明这一切的。
image053.png (372.21 KB, 下载次数: 6)
19:23 上传
上述缺陷都是可以克服的。 通过遵守合适的编程规范就可以开发出并维护一个确定性模拟系统的,我们开发过很多这样的游戏,这样做绝对可行。 或者可以耍点小聪明来隐藏缺失玩家输入引起的卡顿,例如你可以用倒带法(rewind)重新模拟或者维护一个预测(predict)窗口。
Overwatch最终没有选择确定性帧同步方案,原因如下:首先,我们不希望程序员和策划的因为偶现的不确定性而产生心理负担;除此之外,要支持中途加入游戏也不简单。模拟操作本身就已经很耗了,如果5分钟的游戏过程,每次都需要重头开始模拟的话,计算量太大。
最后,实现一个阵亡镜头也会很困难,因为需要客户端快照以及快播机制。按理说如果游戏支持录像文件的话,快播自然就能做到,不过Overwatch并没有这一点。
image055.png (362.29 KB, 下载次数: 4)
19:23 上传
那么下一个选项就是“快照同步”了,与确定性帧同步相类似,也是客户端接受玩家输入然后上报给服务器。然而,这个模型里的服务器是可以忽略某个玩家输入缺失而继续向前模拟的。
image057.png (500.41 KB, 下载次数: 4)
19:23 上传
这里不会转发输入操作给全体客户端,取而代之的是服务器模拟游戏世界,不停地生成整个世界状态的瞬时快照。
image059.png (430.24 KB, 下载次数: 5)
19:23 上传
然后服务器会把快照下发给客户端。客户端根据这些快照来更新各自的世界状态,通常会用“插值”方法在两个相邻的快照间做平滑(译注,关于插值,可以参考Source引擎,解释的比较权威)。
image061.png (365.48 KB, 下载次数: 4)
19:23 上传
这种方式运作良好,概念也很简单,同时避免了帧同步模型中的确定性和卡顿问题。事实上,稍加改造,再结合一些压缩,就能得到一个比较可靠地模型了。
差异计算过程比较耗费资源,你可以分摊快照成本或者“增量”(Delta,下文中会多次出现,并直接用英文单词)压缩,这样对于多个客户端来说,只需要处理一次就好了。
如果你担心有人作弊,你就需要维护一些客户端不应该看见的对象的实时数据,然后只下发客户端能看见的东西。
image063.png (384.54 KB, 下载次数: 6)
19:23 上传
基于快照同步实现回放系统的话,十分简便。从一个完整世界状态开始,更新每帧时添加差异就好了。广义上讲,这个方案就是依赖新技术的游戏客户端如何播放录像文件的问题。客户端要做的仅仅是把网络游戏数据序列化到硬盘上。
这套架构实现阵亡镜头同样很简单。 你已经有全量的快照了,对吧?所以只需要维护一个环形缓冲区,保存过去时间点到当前时间之间的世界状态历史,然后重播就行了。注意到我把这里快照(图片)的大小做成不同(译注:指的是上图中Replay Stream线上的4副图)的,你可以图片大近似想象成每一帧的序列化数据尺寸。有一些帧变化的少,所以尺寸就小。
image065.png (389.24 KB, 下载次数: 5)
19:23 上传
这个例子里,我同样使用了不同尺寸的管道(代表数据量的多少)。快照尺寸就相当于快照同步模型的阿喀琉斯之踵,它限制了在每一帧内,世界状态变化的程度和次数。需要同步的东西越多,初始快照尺寸就越大。帧与帧之间变化越多,Delta就越大。变化越多意味着需要的比特位就越多,而比特位需要消耗宝贵的带宽资源。
对于一个高频互动的游戏,如果想要在低带宽环境下正常运作,最终能依靠的就只剩几种主流的同步模式了。
image067.png (1.62 MB, 下载次数: 5)
19:23 上传
基于状态的同步。 这一页幻灯片与之前的一模一样,客户端上报输入,服务器执行模拟。
image069.png (321.32 KB, 下载次数: 3)
19:43 上传
与快照同步模型相反,服务器不会为全体客户端生成单一更新,而是给每个玩家发送纯手工定制、公平贸易换来的、原生态的数据包(众笑)。
image071.png (1.79 MB, 下载次数: 7)
19:23 上传
那么,当我们谈论“世界状态”(world state)时,是在谈论什么呢?
我想说的是,任何用来表达游戏世界所需的必要状态。可以想象一个代表个体位移、旋转、血量、动画参数和武器发射状态等等的分类数据表。在快照同步模型中,客户端会收到全部状态数据并“原子化”执行一遍。
image073.png (1021.87 KB, 下载次数: 5)
19:23 上传
基于状态的序列化,会把世界快照分解成更小的、原子的数据块。因为每个客户端收到的数据都是订制的,我们可以基于客户端相关性来设置更新的优先顺序。这样就能够基于客户端的实测带宽来智能调整数据流了。
上图的例子里,有个客户端收到1个数据包同时包含了大锤和法拉的更新信息。另一个客户端则收到了2个数据包,其中1个是大锤,然后1个是法拉的信息。这里最重要的一点就是对象可以独立更新,每个客户端的数据都是分别订制的。
客户端网络状况参差不齐的情况下,这种模型就是最灵活的,不过实现起来也是最复杂的。这就是Overwatch的实现方式。
image075.png (481.56 KB, 下载次数: 5)
19:23 上传
现在咱们深入了解一下Overwatch里是如何做到稳定的网络同步的,以及回放系统在这个同步范式下是如何实现的。
image077.png (1.16 MB, 下载次数: 3)
19:23 上传
先介绍几个术语,Overwatch使用的是ECS架构:实体是由组件组成的,由System负责更新(译注:关于ECS请读者参考另外一篇分享:GDC2017 Overwatch Gameplay Architecture and Netcode)。
有些System是序列化过程的参与者(participants),它们负责同步游戏的某些方面(aspect)给接收者(receivers)或者复制目标(replication targets)。接受者一般就是需要接收数据的活跃玩家或者观战者(spectator)。最后,复制目标会收到关于其他实体的更新消息。
通常我会把这一切简言之为:参与者把网络相关实体数据打包并发给接收者,这些实体就叫“主体”(subject)。
顺便给我的同事们打个广告,如果你对Overwatch游戏架构、网络同步的其他方面也感兴趣的话,Tim Ford和Dan Reid的分享刚好与我的互补。他们的已经结束,可以在GDCVault.com上收看(译注:另外一篇GDC2017 Networking Scripted Weapons and Abilities in Overwatch在此)。
image079.png (521.14 KB, 下载次数: 3)
19:23 上传
下面来个具体点的例子,假定你正在控制法拉(Overwatch英雄之一)你跳了一下然后飞起来准备低空轰炸,这个消息是如何发给其他玩家的呢?
image081.png (646.43 KB, 下载次数: 4)
19:23 上传
当你开心地按下按钮,客户端预测就开始了,同时(译注:原文是then,但其实这个操作不需要考虑前后顺序的)把这个命令上报给服务器。
服务器收到连同你在内全部玩家的输入后,一个经过”官方批准”的模拟过程就开始了:一些实体移动了;一些英雄倒下了;抛射物爆炸了,简直就像开Party一样,美妙极了。
image083.png (616.14 KB, 下载次数: 5)
19:23 上传
最终的每个变化和事件,我们统称为Delta,例如”位置变化”就是个(服务器上的)Delta。
image085.png (559.95 KB, 下载次数: 5)
19:23 上传
在服务器端,我们会累积所有对象状态的变化,然后保存在一个临时的“每帧脏数据集合”(per frame dirty set)里。可以简单地认为它就是个字面意义的数学概念的集合,包含所有在那一帧发生改变的实体的ID。
image087.png (583.32 KB, 下载次数: 4)
19:24 上传
那如何把这些状态转发给客户端呢?我们对每个已连接的客户端也维护了一个“脏集合”(译注:应该指的是C1, C2…作者没有交代这些集合是如何生成的,不过从下文的C1=C1-P可以推测出来)。这一页幻灯片右上方的F是当前帧脏集合。每帧结束时,所有接收者的脏集合会与帧脏集F合并(还是数学上的并集概念),这样数据打包时就可以只包含那些该客户端真正需要的实体集合了。在帧结束时,这些操作都做完以后,帧的脏集会被清空,下次更新(tick)时,一切重头开始。
image089.png (568.64 KB, 下载次数: 6)
19:24 上传
在同一个更新周期(tick)的后期,脏状态(译注:C1,C2…)会被序列化成一个数据包,通过“网线”(这个年代到处都是网线)发给接收客户端。
数据一旦被序列化,就会从脏集合中移除(C1=C1-P)。带宽是稀缺资源,所以我们不会使用原生的状态数据,而是维护了一个经客户端确认收到的状态数据的历史记录,这样就可以进行“增量编码”来改进带宽使用模型,也就是减少带宽的占用。
image091.png (581.19 KB, 下载次数: 5)
19:24 上传
对于大多数帧,我们都有足够的带宽来序列化所有数据。即使不够也没关系,这个例子里,只更新了法拉的状态,因为客户端带宽不足,无法容纳大锤的数据了。无论如何,接收者脏集合都会把它保存下来,最终会在将来的某个时刻成功序列化到某个下行包里。
这就是状态同步模型的一大优点,可以基于带宽质量来调整下行数据大小。
image093.png (559.5 KB, 下载次数: 4)
19:24 上传
网络协议我们用的是定制的UDP。众所周知,UDP是不可靠协议,意思是说一个数据包可能会成功达到目的地,也可能到达2次,或者乱序到达。这都没关系,我们自己设计的协议可以感知丢包并在必要时重传脏数据。
image095.png (396 KB, 下载次数: 5)
19:24 上传
丢包发生时,只需要简单的把这个数据包合并回脏状态集合,留待将来重传即可。在未来的包里,我们可以把法拉移动这件事,连同该帧其他事件一起重传,例如大锤开盾。
注意, 如果在首次移动包发送完与收到丢包通知之间,法拉再次移动了,那么无需重传旧状态,我们会把新的移动状态序列化到旧状态数据里。
image097.png (384.44 KB, 下载次数: 5)
19:24 上传
现在聊一下新加入游戏的玩家,是如何追赶上最新的世界状态的。
image099.png (380.91 KB, 下载次数: 5)
19:24 上传
我们在服务器上维护了一个“永久”(lifetime)脏状态集合L,是个全量的脏状态集合s。也就是服务器上所有曾经变脏的状态的“全集”。接下来的事情,你肯定能猜到。
image101.png (378.99 KB, 下载次数: 6)
19:24 上传
你猜对了,新玩家连上后,我们会把它的初始脏状态集合设置为“永久”脏集合L,这里的设置就是字面意义上的赋值。正常的打包流程最终会把所有变化都下发给这个客户端直到它赶上最新的进度。
image103.png (396.17 KB, 下载次数: 4)
19:24 上传
一切都很顺利,但是差点忘了, 我们是要做“回放”而我却一直在讲网络序列化。可以把回放数据流近似的认为就是正常游戏的网络数据流,仅仅是接收者有点区别而已,回放系统的接收者叫做“回放快照接收者“(replay snapshot receiver)。
所以实现回放系统的一个方式(并非最好,后面马上优化)就是,在每一帧都保存一个完整世界的瞬时状态快照。
UDP包大小 是有MTU限制的,然而回放流却没有这个限制,每一帧可达几百上千K字节,直到内存耗尽。
但是我们如何保证每次都能得到恰好合适的全量快照呢?
image105.png (394.69 KB, 下载次数: 5)
19:24 上传
很容易,假装发给回放快照接收者的每个包都丢了就行,然后把永久脏集合重新复制发给回放接收者。这样就可以保证每个快照上每一帧上的每个实体都是脏的。但是就像我上面提示过的,这样做太浪费了。
曾经提到过网络系统是有增量编码的,所以可以基于先前已确认的状态来优化网络流量。
image107.png (410.02 KB, 下载次数: 5)
19:24 上传
我们又弄了第二个回放接收者,Delta接收者D。这个接收者从不丢弃假想包,每次更新都只包含上一帧到现在的变化。所以基本上直接复制帧脏集然后序列化即可。
现在合起来看一下。
image109.png (405.15 KB, 下载次数: 4)
19:24 上传
帧的结尾,我们会把新的永久脏状态集合赋值给回放快照目标。同样也会把每帧脏集合序列化给回放Delta目标。然后马上就会有个问题出现:这些数据最终都去哪了?
image111.png (335.11 KB, 下载次数: 6)
19:24 上传
客户端正常接收者是通过网络包进行的,而回放数据需要通过别的途径。我们维护了两个连续的缓冲区,一个是给快照,一个给Delta。每一帧都会记录这两个缓冲区的偏移和尺寸。
这些连续的缓冲区在整局游戏运行期间都会存在,因为策划可能需要游戏开始到现在的一卷全场最佳镜头(reel,一段连续的影像,无法直译,下文只好统称为“卷”)。
实际看来,每个缓冲区每分钟需要1M的内存,而每帧记录快照所需的内存量比一分钟1M要多得多。所以我们把快照频率降低为每秒1个。
你们中可能有些人想知道Delta的更新频率。服务器的频率是以62.5赫兹(16ms每帧)更新客户端的,死亡重播和回放系统如果都运行在这个频率会使内存和带宽消耗达到3倍之多,所以我们把Delta的频率降低到20Hz。
image113.png (318.73 KB, 下载次数: 5)
19:24 上传
现在我们终于可以生成回放“卷”(replay reel ,译注:reel这里强调是“卷”)了,这是客户端所需的核心驱动数据。策划同学想要生成阵亡镜头或者全场最佳的时候,他们请求一个“卷”即可。这些”卷”由一个快照加上一系列递增的Delta组成。
要生成”卷”,就要找到”卷”所需开始时间之前的最近的那个快照,然后不停添加Delta直到”卷”的截止时间。这里输出缓冲区的生成也很高效,就是2次内存拷贝而已。
缓冲区会被压缩,添加一些元数据,然后发给客户端。阵亡镜头,亮眼表现和全场最佳都是用的这套机制,而且都是通过这一个System支撑的。
如果这时候你开始怀疑这到底还是不是快照同步了,你绝对是正确的。这是有意而为的。两个World都运转良好:基于状态的模型的十分灵活,可以基于客户端网络质量动态调整;快照同步用于回放”卷”时也带来了简洁性。
image115.png (320.14 KB, 下载次数: 6)
19:24 上传
现在”卷”已经生产,需要发送给客户端了。为了方便传输,”卷”会被切分为MTU大小的多个片段,然后通过我们开发的BlockTransferSystem在网络上传输。BlockTransferSystem会考虑到网络状况,只有在带宽充足的情况下才会发送。
当然,玩家死亡期间除了盯着凶手,发怒以外什么也做不了,这一点也让我们松了口气。因为他们太全神贯注了,根本不会注意到因为传输大块数据而引起的微小抖动。
image117.png (419.44 KB, 下载次数: 3)
19:24 上传
最后看一下同步的API长什么样。这里省略了一些参数;另外返回值应该是枚举而不是整形值,但是用来说明问题已经足够了。
System通常会通过一个内部的通用序列化函数来实现网络参与者和回放参与者两个接口,这两个API的差异很小。例如:网络参与者API会限制带宽,上面已经讲过很多次了。而回放参与者API则完全不用担心这一点。所以很多情况下,回放接口会自行调用含有带宽限制的内部函数,来设置一些永远都达不到的阈值。
网络和回放接口共享序列化策略带来的结果就是,网络复制所节省下的每个字节,在回放系统里也是如此。
image119.png (417.13 KB, 下载次数: 5)
19:24 上传
这两个接口是每个接收者,针对每个相关Subject(译注:之前有提到,网络相关实体,就是Subject)分别调用一次的。基本就是每个接收者都遍历一次完整的脏集合了。
如果所有的参与者都认为他们对于某个Subject没有需要序列化的数据了,那就可以安全的把这个实体从接收者脏集合中删除了。
image121.png (410.86 KB, 下载次数: 4)
19:24 上传
这些带Fixed的函数,给参与者提供了一个机会,去同步那些与实体无关的信息,在网络侧,就包括带外控制消息和握手之类的操作。
image123.png (416.03 KB, 下载次数: 6)
19:24 上传
由于采用了不可靠协议,每个参与网络同步的System,对于每个数据包都需要接受一个ACK或者NACK消息,来判断是否收到或者丢失。
我们就是靠之前已经确认收到过的状态,结合内部帧压缩来增量的发送更新。
image125.png (411.71 KB, 下载次数: 5)
19:24 上传
另一方面,回放参与者完全不需要收包确认和丢包通知,因为它们从不“丢”包。取而代之的是在序列化过程开始前和结束后使用一对回调函数。如果要举例说明,如何在完成回放帧的序列化之后立即使用这些函数,那就是,对所有来自Delta接收者的内部序列化状态立即ACK,对所有快照接收者上的数据立即NACK。
image127.png (758.76 KB, 下载次数: 6)
19:24 上传
上图是发送数据给网络接收者的API使用的简化版用例。对于该接收者的每个参与者,都执行 Fixed调用,然后获取到该接收者的脏Subject集合,并允许每个System都参与到Subject的序列化过程。
这个例子忽略了很多技术细节,而这些细节几乎可以拿出来单独开设分享议题了,包括:脏状态追踪,相关性,优先级,过滤,带宽模型,丢包与确认,以及其他能单独分享的技术。
image129.png (636.4 KB, 下载次数: 6)
19:24 上传
回放Before和After的API调用例子。
image131.png (402.5 KB, 下载次数: 6)
19:24 上传
现在回头看一下参与者API,大家可能会有疑问,为什么这些System的设计没有基于组件,而是基于实体 ,而一个良好的ECS架构实现应该是基于组件的。起初我们的同步API确实是基于组件的,例如MovementSystem简单遍历所有Mover组件然后序列化即可。不幸的是,这种范式有些潜在的关联,假如带宽受限会发生什么呢?通过组件序列化意味着一个System就有可能扼杀其他System序列化能力。除此之外还增加了同一实体被多个包更新的可能性。所以我们团队的实用主义者在这个案例中,从纯粹的ECS转向了非纯ECS,而且是完全可以接受的,也是为了更好地玩家体验。
image133.png (299.17 KB, 下载次数: 6)
19:24 上传
image135.png (338.15 KB, 下载次数: 10)
19:24 上传
好了,上面讲过的都是干货,现在来点好玩的:挑战和躺坑。如果说之前讲的是“如何做”,那么现在讲的就是“何时”与“何地”,而“为什么”,最开始我就说过了。
首先,何时下发阵亡镜头的”卷”?阵亡镜头有几个固定时间点:死亡、重生。
image137.png (346.1 KB, 下载次数: 5)
19:24 上传
这两个点之间的距离是由重生定时器控制的,不同的地图、游戏模式、是否加时及所有策划可以配置的情况下,这个定时器的值都不同。
image139.png (327.63 KB, 下载次数: 5)
19:24 上传
为了使得阵亡镜头看起来更合理,下发数据时需要比死亡那一刻再超前一点点,这样看起来就没那么突兀了,避免出现一脸懵bi的情况下突然被人爆头。
image141.png (326.82 KB, 下载次数: 5)
19:24 上传
另外,我们也不想在玩家死亡的那一瞬间就突然停止回放,那样会很突兀,因为你的大脑需要一些时间来处理上下文,最好能看到死亡后几秒钟内周围都发生了什么,看着尸体布偶化,然后再从死亡的痛苦中恢复过来。
image143.png (329.7 KB, 下载次数: 7)
19:24 上传
现在这幻灯片越来越拥挤了。
在死后的尾声阶段,和重生之间,才需要观看阵亡镜头,对吧?这本来就是要开发阵亡镜头系统的真正原因。
image145.png (463.83 KB, 下载次数: 5)
19:24 上传
所以这里只剩下一个很小的时间窗口,可以让我们把回放数据发下去。大概是半秒钟左右的时间。然而在低带宽条件下,半秒钟是不够传输数据的。
这些情况也进一步地促使我们开发团队节省带宽。事实再一次证明,我们用来减少带宽消耗的所有努力,同时也减少了回放数据的大小。实践中,我们严格遵守这个纪律,最终实现了一个可靠的阵亡镜头体验,即使低带宽高丢包的网络环境下也是如此。
image147.png (719.33 KB, 下载次数: 5)
19:24 上传
这里我们还有另外一种选择,与其一次性传输大块数据,不如分开成几次:死亡时立即发送死前(pre-death)数据,之后再陆续下发增量Delta数据。
这个方法会需要一些工程上的折衷,总带宽消耗会变大,因为没办法对整个“卷”进行压缩了。另外还需要一些控制逻辑:即使收到阵亡镜头的第一个数据块也不能立即开始播放,因为数据还不足够,有可能引起回放镜头卡顿,没人想要这种体验。
所有困难都是可以克服的,我们也不一定非要这样做,不过这也是选项之一。
image149.png (708.55 KB, 下载次数: 4)
19:24 上传
哦,这部分是我的最爱:可打断的阵亡镜头。“复活”是个麻烦事,因为我们已经把“卷”发给客户端了,客户端也开始播放了。但是因为回放随时可能被取消或者打断,所以我们不能完全销毁live世界的状态,也不能停止接收live的网络数据,因为live的游戏脚本可能会需要根据这些数据控制UI、特效,触发事件来表明你被“复活”了。
我们没有使用快照同步模型,所以没办法保持住整个世界的状态,我们没做过类似的游戏,也从来没有实现过。
回放可能立即就会被打断,怎么办呢?
image151.png (397.82 KB, 下载次数: 5)
19:24 上传
我之前提到过Overwatch的游戏架构用的是ECS,于是乎我有了一个想法,为什么不弄一个独立的”域”(domain)来实现回放呢?或者用Overwatch的术语来说,一个独立的EntityAdmin。通过这两个不相干的ECS域,我们可以把回放和live环境隔离开来。
最后问了一圈,从工程师到团队领导,到技术总监,我基本可以确定在我们这个游戏项目中,CPU绝对不会成为瓶颈。不过这也可能是错的,我的意思是,永远不会成为计算密集型(sim-bound,可以参考CPU Bound和I/O Bound的定义)程序,看起来是那么的明智。
image153.png (857.13 KB, 下载次数: 5)
19:24 上传
而且对于我们来说这是一个清理旧架构中设计不合理部分的机会,例如对全局变量和静态变量的内部依赖。有了两个完全不相关的域以后,所有的参数都需要限制在域的范围内,所以借此机会整理代码也不错。
大概花了几周的时间,我们把所有东西都隔离了,并且有了两个域(Domain),一个做实时游戏,一个做回放。在这个模型里,实时游戏数据来自网络,直接进入实时渲染的世界里。而来自回放的数据块直接进入了回放模块。进入阵亡镜头时,实际上两个域的数据都在同时处理。
image155.png (1013.88 KB, 下载次数: 3)
19:24 上传
这一页的视频里会演示一个简单的阵亡镜头。回放开始时会有一个黑色淡入效果,这代表我们停止运行实时游戏,转而渲染回放域,等到回放结束时又会转移回去。
image157.png (250.12 KB, 下载次数: 5)
19:24 上传
image159.png (814.28 KB, 下载次数: 5)
19:24 上传
总得来说执行“域”的实验很成功,或许是过于成功了,所以我们就在想,既然2个域是ok的,那为什么不搞4个呢?
实际上这并不是一点代价都没有的,我的意思是说,我们需要实现一套机制,可以在过渡时,供域之间进行协调、通信。现在大部分域内部冲突都依赖于EntityAdmin这个全局管理器,通过脚本化行为来协调何时进入何域。
image161.png (478.81 KB, 下载次数: 4)
19:24 上传
那么这个额外的模拟过程的代价又是什么呢?我们最终还是计算密集型的了,尤其是在主机和低端PC机上。多域模型带来了这个额外负载,但它绝不是唯一一个打破性能预算的系统。有了回放系统以后,我们就可以用录像文件来做整体性能优化。
录像文件使得我们可以直接在主机上发布60帧的FPS游戏,也在低端PC机上达到了性能目标。
就像《辛普森一家》里说的:回放系统,既是性能问题的罪魁祸首,也是救世良方(译注,原剧台词:To Alcohol! The cause of… and solution to… all of life’s problems.)。
image163.png (255.6 KB, 下载次数: 5)
19:24 上传
image165.png (966.1 KB, 下载次数: 5)
19:24 上传
继续讲下打磨过程,这一部分最后才讲,也是我最喜欢的一个话题。
先说炮台吧,对于我们来说,炮台击杀敌人是个棘手问题。建造炮台的玩家会得到击杀奖励。炮台杀人的时候,主人可能已经在半个地图以外了。更糟的是,主人在这时候被干掉了。
下面就是一个托比昂的炮台拿到全场最佳的例子,炮台杀人无数,咱们可以数一下它杀了几个。
image167.png (863.87 KB, 下载次数: 3)
19:24 上传
image169.png (866.55 KB, 下载次数: 5)
19:24 上传
image171.png (936.61 KB, 下载次数: 4)
19:24 上传
image173.png (892.15 KB, 下载次数: 5)
19:24 上传
两个(注意视频里托比昂死在了悬崖边上)。
image175.png (640.22 KB, 下载次数: 5)
19:24 上传
三个(众笑,因为这时候托比昂的尸体已经掉到悬崖中部了)
真的很滑稽,这段视频是从beta版来的,那时候我们还没修复这个bug呢。后来我们就不再把全场最佳送给死掉的英雄了,也就解决了这个问题。(译注,守望先锋最初的版本确实有这类全场最佳镜头,也是十分搞笑)
所以,托比昂视角的阵亡镜头(译注:感觉这里有点问题,作者强调的应该是全场最佳),只有在他自己而非炮台杀人时才有意义。
image177.png (416.63 KB, 下载次数: 6)
19:24 上传
那么该如何处理(炮台击杀时的阵亡镜头)呢?基本上是脚本硬编码的。我们最初调整了炮台的阵亡镜头角度,使它看起来是真的站在炮台的视角看的。但不幸的是,炮台是由AI控制的,而不是一个真实的玩家,并没有用第一人称视角在那里打枪。更重要的是,炮台AI与真人瞄准的方式也是完全不同的。
通过创建一个基于炮台动画骨骼的定制化摄像机,我们解决了这个问题。从那个骨骼位置后方一点,发射出一个由点组成的环,来得到稳定的视角。这种处理方式对于秩序之光的炮台(哨兵炮)尤其有用,因为它们很小,经常被放在天花板上,或者角落里、墙上。另外让炮台和受害者都处于画面中也是很重要的。
下面的视频展示了一个炮台摄像机的例子。注意玩家死亡时,尸体布偶化,摄像机自动对准凶手。阵亡镜头期间,摄像机会追踪玩家,他从角落里一露头,就可以看见炮台把他打到人事不省。这里你还会有个额外收获,可视化的摄像机位置调试信息。这里绘制了一些绿色的点,表示摄像机的朝向,用来在阵亡镜头期间,保证炮台始终位于画面中央的。
最后的提示是关于debug功能的,即使是在回放过程中也依然可用。如果发现bug了,同样可以依赖脚本系统的调试功能去解决。
image179.png (982.58 KB, 下载次数: 5)
19:24 上传
image181.png (887.92 KB, 下载次数: 5)
19:24 上传
image183.png (943.74 KB, 下载次数: 5)
19:24 上传
image185.png (844.46 KB, 下载次数: 4)
19:24 上传
你应该能注意到摄像机目标位置会有一些插值。可能还有些其他的被掩盖了,但是这些就是基本原理了(nuts and bolts,螺母和螺栓)
image187.png (317.61 KB, 下载次数: 5)
19:24 上传
image189.png (343.03 KB, 下载次数: 5)
19:24 上传
下面是一个炮台摄像机未能按照预期工作的例子,发行版本也是如此。可以看见死亡发生时摄像机再次指向了凶手。但是这个例子里,阵亡镜头里的摄像机没有成功地把炮台聚焦到画面中央。
image191.png (297.23 KB, 下载次数: 4)
19:24 上传
image193.png (308.36 KB, 下载次数: 4)
19:24 上传
如果你注意到那些天花板上的龙骨,就知道摄像机的位置没办法更好了,实在没办法。
除了炮台以外,还有其他几种情况,需要我们来实现定制化的阵亡镜头。
image195.png (409.22 KB, 下载次数: 6)
19:24 上传
image197.png (419.22 KB, 下载次数: 4)
19:24 上传
当你跌出地图边缘时,我们会解除摄像机绑定,然后追踪你的布偶直到沃斯卡亚的冰河里。
image199.png (339.21 KB, 下载次数: 5)
19:24 上传
image201.png (391.74 KB, 下载次数: 4)
19:24 上传
如果是因为有人把你推下去,我们就会转而从他的视角回放死亡过程。
image203.png (414.12 KB, 下载次数: 5)
19:24 上传
image205.png (433.28 KB, 下载次数: 6)
19:24 上传
说实话我挺讨厌被推下去的。Alright, and I really hate like this.
image207.png (412.42 KB, 下载次数: 5)
19:24 上传
image209.png (475.6 KB, 下载次数: 4)
19:24 上传
半藏的神龙之魂,有同样的炮台问题。他甚至可以从重生室里放大,然后他的龙可以飞跃整个地图直到干掉你,然而半藏的本尊还在重生室里潇洒呢。
image211.png (454.15 KB, 下载次数: 3)
19:24 上传
image213.png (486.03 KB, 下载次数: 6)
19:24 上传
在这种情况下显示来自凶手视角的阵亡镜头显然没什么意义,取而代之的是我们使用了一个第三人称视角的摄像机去追踪龙的飞行过程,直到杀死你的那一刻 。
实际上还有好几种需要定制的阵亡镜头,但是没时间全部覆盖到了。上面讲的是最常见的几种,对每一种情况进行打磨都使得阵亡镜头的体验更好,可以减少玩家突然被弄死时的困惑。
image215.png (488.93 KB, 下载次数: 5)
19:24 上传
好吧,插值提示(interpolation hinting)。从服务器的角度来看,进攻者永远都是在“过去”采取行动的,如果没有额外提示的话,客户端播放阵亡镜头时,就会看到准星瞄的永远都是目标后方,这样不好,会导致论坛上很多投诉。
为了修复这个问题,我们在回放系统里记录了每个玩家的插值延迟提示,然后在客户端播放回放期间,把这个和插值延迟缓冲区集成到了一起。这样就保证了阵亡镜头可以准确显示服务器端进攻者的瞄准动作。
接下来我会用“时间缩放”的方式来连续播放两个阵亡镜头过程(内部回放文件支持这么做)。重点观察开火反冲(recoil)那一刻士兵76的位置。Overwatch里,武器的操作是不会有任何延迟的,一旦你扣下扳机,立马就能看到开火特效和其他相关的表现,包括爆炸、拖尾等。
结果就是,因为客户端帧率的粒度较低,你能看见一点点偏离。但是开火反冲却是由该帧脚本精确控制的。
image217.png (444.94 KB, 下载次数: 4)
19:24 上传
image219.png (450.91 KB, 下载次数: 5)
19:24 上传
在爆头的瞬间,(从视频中可以看出)目标刚好处于准星的位置,完美。
image221.png (445.8 KB, 下载次数: 7)
19:24 上传
image223.png (453.37 KB, 下载次数: 5)
19:24 上传
再看看同样的情况下,没用插值的表现(译注,从视频上来看,这种情况下,目标已经越过准星了,弹道和击杀提示才出现)。
image225.png (125.47 KB, 下载次数: 6)
19:24 上传
好吧,现在讲一下”快进”(fast forwarding)。前面已经说了每秒钟记录一次快照,而死后还有2秒的收尾过程(译注:正如前面讲的,摄像机对准凶手,再看看周围环境的变化)。会发生什么呢?如果所需“卷”开始之前最近的一次快照,超前了整整1秒的话(译注:也就是说,“卷”开始时,该快照刚好结束),收尾过程会被截短只剩下1秒,完全不够你用来思考世界的。
解决方案就是把回放过程快进到“卷”开始的时间。这是个很小的工作,却很重要,保证了阵亡镜头体验的一贯性。
此处没有视频举例。
image227.png (135.51 KB, 下载次数: 7)
19:24 上传
服务器步进。
服务器超载时会落后1到2帧然后再赶上来。这种情况下,当前模拟的帧可能得不到所需要的时长了。如果此时客户端执行回放的话,它能得到的帧数就会比预期要少,实际表现就是回放结束的很突兀。
解决方案也很直接,我们在beta版 的客户端采用更高的帧率。同时记录客户端期望时长和服务器实际时长,这样的话就可以在期望的时间窗口内完成回放。
image229.png (161.72 KB, 下载次数: 6)
19:24 上传
游戏中大多数消息都是针对单个玩家的,回放系统只需要其中的一部分消息,观战者也有同样的问题,不是所有发给指定客户端的消息都应该分发给观战者。
为了实现这一点,我们把每个游戏消息都加上注释,来指定它是否需要被包含到回放或者观战数据流当中。然后在把这些消息委托给适当的接收者。
客户端实际回放期间,我们会过滤掉那些不是发给当前主体的消息。
如果没有这些打磨的话,“卷”里就会缺失受击提示、命中确认、UI变化和一些语音对话。这些处理帮我们改善了回放的体验。
image231.png (93.32 KB, 下载次数: 6)
19:24 上传
未来的工作。
image233.png (218.67 KB, 下载次数: 6)
19:24 上传
提高Delta录制频率。之前有提到过,我们的Delta刷新频率是20HZ,这基本上是精确度期望值对带宽消耗妥协的结果。顶级玩家可以做到(也的确这么做了)在快速旋转的同时进行射击,但我们当前的频率达不到那么精确。
一个办法就是在回放数据里增加一些朝向提示,就像插值提示那样。另外一个办法就是加快Delta录制频率,使之能够配合当前上下行包的频率。这两个方法都会使得带宽与存储空间的消耗急剧增长。玩家移动的数据包约占到总带宽的一半。
那为什么这会是个问题呢?这里我找了一个游戏高手,人非常好,在录制完成最终的视频以前,一遍一遍地尝试爆我的头。他有一手绝活,你可以看见准星经过爆头点。但是因为没有更细粒度的Delta更新,我们只能用插值填满这50毫秒的范围。
image235.png (252.69 KB, 下载次数: 5)
19:24 上传
这是实时录像,他真的有那么厉害!This is real-time, yeah he’s that good.
image237.png (264.46 KB, 下载次数: 5)
19:24 上传
慢动作(slo-mo)再看一遍,“I have so much to live for her”(译注:一句歌词,作者哼唱起来了~~~)。
注意,准星虽然经过了头的位置,但是这里就能看出增加Delta的重要性了(译注:视频中弹道已经明显偏离准星位置了,就是因为Delta的频率不够高所致)。
image239.png (222.13 KB, 下载次数: 5)
19:24 上传
优化回放文件:快进,倒带,拖放 (scrub,随意指定开始位置去播放)。当前的回放文件,只在起始处有一个快照,所以它其实就是一个普通的基于输入的回放机制,你唯一能做的就是从头向后顺序播放。
正因为如此,就不可能支持拖放,快进也是通过从头开始快速播放实现的,而且倒带实现方式也不咋地,真正想用这个功能的人一定很失望,因为还是从头快速播放到你想要的位置之前多少秒钟。
目前都是团队内部在使用,但是不能排除将来会发布的特性会用到这些。
image241.png (192.02 KB, 下载次数: 4)
19:24 上传
这一页给出了一个简洁的修复方式:插入更多快照!
考虑到目前快照的尺寸,我们大概可以在回放文件里增加几个10秒钟粒度的快照,在不用对文件尺寸做过多妥协的情况下,至少可以以10秒钟的步幅做快进。
image243.png (212.77 KB, 下载次数: 4)
19:24 上传
如果我们真的在回放文件增加那些快照,会有一个额外的好处:可扩展的观战功能。增加一个代理或者服务器,与实时游戏服务器独立开来,专门接收带Delta的回放快照即可。越来越多的玩家都想要在诸如锦标赛之类的比赛中实时观战,只需要下发快照给这些玩家,然后不断塞Delta数据流给他们就行了。所有这一切都发生在一个隔离的服务器上,完全不用担心实时游戏服务器过载什么的,这一点非常非常重要。
增加一点缓冲区,画几个UI再加上可扩展的观战功能,这事就成了。当初设计这组内部功能时,真是太明智了。
尽管后来发现,我其实没那么聪明,这个点子也绝非原创。它的实现方式与Valve公司的那个叫做“Source TV”的观战系统如出一辙,我猜Dota2的游戏内观战也是基于这个架构的。
image245.png (170.98 KB, 下载次数: 4)
19:24 上传
给玩家用的回放。
最后,如果不谈一下把回放系统开发给玩家使用,那就太过分了。玩家需要回放,而我们希望玩家开心,做到以下几点就够了。
image247.png (189.04 KB, 下载次数: 7)
19:24 上传
老实说,最大的阻碍就是找到一种方法来保留全部回放数据。
根据当前帧率,一周的回放文件大约需要200TB的存储空间,这还是压缩后的。不幸的是,压缩需要消耗CPU资源,而CPU是我们当前最大的瓶颈。
除此之外,这200T数据也不是需要永久保存的。我们现在正在研究如何在存储需求和留存(retention)需要之间做权衡,以使得这个功能尽快上线。
我们希望可以允许玩家保存一段合理的时间内,把回放文件保存在他们自己的机器上。我们也考虑过允许玩家在比赛结束时下载回放文件,但是那样会增加本已捉襟见肘的服务器带宽资源,所以也行不通。
image249.png (211.9 KB, 下载次数: 5)
19:24 上传
谈到留存,就不可避免的提到补丁和序列化兼容性问题。为了能够序列化,网络和回放数据的结构定义,都依赖于资源定义。如果补丁中含有任何与stream有关的东西,都会使得回放文件失效。
从2016年5月游戏发布到2016年12月份,我们打了大概22个补丁。也就带来了22个潜在的不兼容的可能性,谁都不希望看到。所以我们的客户端必须支持加载上一个补丁版本的资源的能力,这样才能正确播放回放文件。这也意味着,如果硬盘上没有所需资源,就要从网络上下载。还要支持某种形式的新旧版本资源覆盖。从CDN按需下载,显示进度条,需求越列越多。
我们到现在还在想象这个功能应该做成什么样,但是这多少会给你们一些信息,了解我们正在克服哪些阻碍,突破什么边界。
image251.png (138.39 KB, 下载次数: 7)
19:24 上传
快要结束了,现在回顾一下。
回放系统很酷,上面的例子已经展示了我们影院级的回放镜头支持。
image253.png (250.14 KB, 下载次数: 7)
19:24 上传
耶,子弹时间!想象一下,把这个功能和一些时间压缩、暂停技术相结合,就能得到类似电影《黑客帝国》中的子弹时间。在我们游戏玩法预告里,大量的采用了这个功能。所以这里取消暂停,旋转一下摄像机,这样就有了一个更好的角度。
image255.png (167.45 KB, 下载次数: 4)
19:24 上传
到目前为止都学到了哪些知识呢?设计网络同步模型时就考虑到回放的需求;不相关的执行域提供了更大的灵活性和隔离性;网络带宽优化重于回放文件尺寸优化,能省一个字节就省一个字节;社区都很喜欢分享亮眼表现和全场最佳,能在Reddit和其他论坛上看到这些分享,感觉真的很棒。
image257.png (140.93 KB, 下载次数: 7)
19:24 上传
如果你在开发时不支持回放文件,那就太悲惨了。你应该说服你的工程师,或者如果你自己就是工程师的话,你真的应该先实现这个功能,对于debug来说太有用了。回放文件对于性能分析也很有用。可以提早优化,使得游戏可以运行于任何硬件。
image259.png (155.85 KB, 下载次数: 5)
19:24 上传
学到的教训。更多的域意味着更多的负载。这一点绝对有些意外,让人头疼,在域之间平滑过渡,就需要大量人力开发。我们做了很多工作去保证,无论什么背景场景,场景更新都保持在一个低频模式,以减少CPU的消耗。
服务器定制的序列化很昂贵,定制越多,优化就越多。如果你决定采用这种方式的话,就要对困难有所准备。
最后一件事,你或许已经注意到幻灯片里的API与发行版本用的很相似。所以,一定要尽早开发同步模式,这样的话API会干净很多。
就这么多了!
关注我们官方微信公众号
下载我们官方APP-游戏行
关注手游动态微信公众号
打破游戏行业性别鸿沟,推动多元化发展 - F炸裂!苹果半日下架App超2万,涉赌类、马甲第三届腾讯游戏品鉴会启动项目征集全方位助不谈公式,谈谈数值工作中的目标Steam上“差评如潮”的7个游戏,都是些什么“Netflix式游戏”正在威胁着开发者
微信扫一扫关注我们→

我要回帖

更多关于 守望先锋全场最佳bgm 的文章

 

随机推荐