精华内容
下载资源
问答
  • 主流方法:帧同步和状态同步 (1)状态同步 基本认知:狭义的理解,就是所有客户端将动作信息全部发给服务端,由服务端计算游戏行为,再将所有状态数据通过广播的方式发给客户端。(比喻:相当于所有客户端都在远程...

    主流方法:帧同步和状态同步

    (1)状态同步
    基本认知:狭义的理解,就是所有客户端将动作信息全部发给服务端,由服务端计算游戏行为,再将所有状态数据通过广播的方式发给客户端。(比喻:相当于所有客户端都在远程操控服务端)
    状态同步流程:

    • 客户端上传信息到服务器
    • 服务器收到信息后计算行为结果(计算,逻辑处理)
    • 服务器广播各种信息到客户端
    • 客户端接收后,更新本地状态

    总结:重视逻辑计算的结果一致性,对客户端的视频是否是否一致没有要求。(即,甲、乙计算结果相同,甲先加载出,乙可能因为自身网速问题过一段时间加载出)

    (2)帧同步
    基本认知:客户端将信息发给服务端,服务端汇总后直接发给所有客户端,让他们自己处理信息;又或者直接不用服务端,直接P2P,客户端发给客户端,互相处理自己的数据。这样所有客户端只接受对方的行为数据,进行自己的数据计算,由此减少了数据量,又因为数据均一致,结果也一样,最大限度保持客户端之间画面的一致性。
    扩展功能:①服务端接收信息的特性,可以记录信息,完成回放功能。②服务端记录仅仅只有双方的计算结果和行为逻辑,由此对比防止作弊。③多人实时对战。

    总结:重视画面一致性,通过相同时机的相同计算得到一致的结果,但是因此当一个客户端出现问题,所有客户端都会为保持一致性而停止等待,造成卡顿。

    展开全文
  • 同步是什么 所谓帧同步(lockstep),其实是同步操作,确保两个客户端逻辑一致,然后服务器接收每个客户端操作(cmd), 然后再下发下去,相同的逻辑,相同的操作,便会播放(DoAction)相同的结果。 如何...

    前言

    本文会详细说明说明帧同步与状态同步、细说实现原理、分析他们的优缺点。提及注意点、保证同步的方法、验证同步、防作弊防攻击等。

     

    帧同步

    简单来说,就是相同的状态+相同的指令+ 按帧顺序执行=相同的结果。
    状态:所有客户端确保逻辑一致,接收一样的随机种子(randomseed),一样的房间信息;
    指令:服务器只负责收集接收每个客户端操作指令(cmd),转发指令,服务器以恒定帧率(30帧1秒)派发指令,没有指令或指令没有变化也需要派发;
    执行:真正游戏逻辑由各个客户端单独计算 ,客户端需要收到服务器派发的指令才能推进逻辑,没有收到指令时不能推进逻辑(LockStep)

    顺序执行

    帧同步会必定按到从第一帧开始一帧一帧的执行,才能保证运行结果一样,跳帧会导致逻辑不一样,如果玩家网络不好,则会在当前帧等待至下一帧的接受,如果丢包超时,则会再次发出需要帧的请求。

    追帧

    什么是追帧:当前玩家播放到帧比服务器的帧落后时,服务器下发多个帧,玩家便要开始快进到服务器当前帧
    为什么要追帧:如果网络波动,服务器会有最晚的接受帧时间,
    做法:超过则下次发送多个帧,然后快进播放(多次DoAction),快进期间,不播放特效音效等不影响运行结果的逻辑

    重连

    做法:接受从0开始所有帧重新快速播放到当前帧,如果帧列表count大于规定速度则按照最大速度播放,否则按照剩余的count播放相应次数的帧。

    优点

    第一,它的开发效率比较高。如果你开发思路的整体框架是验证可行的,如果你把它的缺点解决了,那么你的开发思路完全就跟写单机一样,你只需要遵从这样的思路,尽量保证性能,程序该怎么写就怎么写,服务端逻辑简单,只需要负责转发指令,压力也小。

    比如我们以前要在状态同步下面做一个复杂的技能,有很多段位的技能,可能要开发好几天,才能有一个稍微过得去的结果,而在帧同步下面,英雄做多段位技能很可能半天就搞定了。

    第二,它能实现更强的打击感,打击感强除了我们说的各种反馈、特效、音效外,还有它的准确性。利用帧同步,游戏里面看到这些挥舞的动作,就能做到在比较准确的时刻产生反馈,以及动作本身的密度也可以做到很高的频率,这在状态同步下是比较难做的。

    第三,它的流量消耗是稳定的。大家应该看过《星级争霸》的录像,它只有几百K的大小,这里面只有驱动游戏的输入序列。帧同步只会随着玩家数量的增多,流量才会增长,如果玩家数量固定的话,不管你的游戏有多复杂,你的角色有多少,流量消耗基本上都是稳定的。

    四,这点延伸开来还有一个好处,就是可以更方便地实现观战,录像的存储、回放,以及基于录像文件的后续处理。

    缺点

    第一,最致命的缺点是网络要求比较高,帧同步是锁帧的,如果有网络的抖动,一段时间调用次数不稳定,网络命令的延迟就会挤压,引起卡顿。

    第二,它的反外挂能力很弱,帧同步的逻辑都在客户端里面,你可以比较容易的修改它。但为什么《王者荣耀》敢用帧同步,一方面是因为当时立项的时候开发周期很短,半年时间要做上线,要有几十个英雄,存在时间的压力,另一方面,MOBA类游戏不像数值成长类的游戏,它的玩法是基于单局的,单局的作弊修改,顶多影响这一局的胜负,不会存档,不会出现刷多少钱刷多少好的装备的问题,而且作弊之后我们也很容易监测到,并给予应有的惩罚,所以我们认为这不是致命的缺点。

    第三,它的断线重回时间很长,相信台下也有很多王者玩家,也曾碰到过闪退以后重回加载非常长的情况,甚至加载完以后游戏也快结束了,这是帧同步比较致命的问题。

    第四,它的逻辑性能优化有很大的压力。大家应该没有见到哪一款大型游戏是用帧同步来做的,因为这些游戏的每一个逻辑对象都是需要在客户端进行运算的。如果你做一个主城,主城里面有上千人,上千人虽然玩家看不到它,但游戏仍然需要对他们进行有效的逻辑运算,所以帧同步无法做非常多的对象都需要更新的游戏场景。

    五,debug困难,出现不同的情况,难以查找问题所在,一般通过debug输出关键改变信息来定位问题,但问题可能在1-20个函数之内,但只在第20个函数打了debug信息,然后需要一层层去查找出现问题的所在,考虑把出问题的局录下来,然后不断重播和调试,方便找到问题

    协议:

    结构
    1.roomInfo ①randseed ②role ③...
    2.event ①int frame ②event type ③byte[] param
    3.cmd ①int frame ②int[] cmd

    event示例
    1.房间信息(角色数量和角色信息)
    2.角色死亡/重生
    3.生成怪物/Boss
    4.玩家信息改变(换装,位移)
    5.游戏时间结束

     

    保证同步

    • 同样的逻辑帧数(10-30),渲染帧可以更高(30以上)
    • 随机数、Time的接管
    • 接管物理的update,Physics.SyncTransforms  Physic.Simulate(time)
    • 顺序一致    角色的创建顺序、各种玩法的创建顺序、OnTriggerXX的顺序
    • 帧同步驱动各系统update代码,不使用Mono的update和Invoke等、
    • 写功能要注意 功能逻辑符合多人玩法,区分音效特效震动的播放,对是否自己的判断
    • 帧同步会动画突然变化 不过帧同步的都无法避免吧 做一些过度和假动画无逻辑(例如转身和走)前端照常播放动画 不会但不会自己切换动画处于一个循环,只有接收到服务器的cmd进行下一个操作而转变动画才会跳到下一个动画
    • 浮点数处理   方案一、定点数   方案二、公式尽量简化(随机float 转int),并且截取一定的精度范围(目前是小数点后3位)
    • 不使用不确定性的插件、例如有时间的、随机数的

    如何验证同步

    1.把一些关键的运行(例如cmd,位置,动画,攻击,发射子弹等)在一帧的最后,整理为一个string然后转hashcode,上传(①int 帧 ②int hashcode)至服务器,服务器对比hashcode,如果hashcode不一样,则发生了不一样的逻辑,然后服务器去请求这一帧的详细日志(string DebugDetail),获取回来有何不一样。

    2.做验证服校验哪里不一样,既是把让服务器把cmd、相关玩家数据和随机种子存下来,然后再播放一次本局然后一次性上传日志,比较差异。

     

    不同步debug记录

    1. 物理结果
      问题:当刚体同时进入到了2个碰撞体,有可能会在不同客户端计算出不同的结果,无法控制。
      解决:不使用物理力kinimatic
    2. 浮点数不一样
      裁剪小数点,尽量用整数计算,这个不好,用定点数好
    3. 不能使用unity的mono、time、 random、 Invoke,因为时机和结果每个客户端不确定。
    4. 据说rootmotion也会可能不一样?
    5. 据说导航结果也会不一样,因为多个角色运动互相挤兑会导致不一样的结果?还是导航结果会不一样?

     

    状态同步

    将其他玩家的状态行为同步的方式,一般情况下AI逻辑,技能逻辑,战斗计算都由服务器运算,将运算的结果同步给客户端,客户端只需要接受服务器传过来的状态变化,然后更新自己本地的动作状态、Buff状态,位置等就可以了,但是为了给玩家好的体验,减少同步的数据量,客户端也会做很多的本地运算,减少服务器同步的频率以及数据量。

    优点

    第一,它的安全性非常高,外挂基本上没有什么能力从中收益。

    第二,状态同步对于网络的带宽和抖动包有更强的适应能力,即便出现了200、300的输入延迟再恢复正常,玩家其实也感受不到不太舒服的地方。

    第三,在开发游戏过程中,它的断线重连比较快,如果我的游戏崩溃了,客户端重启之后只需要服务器把所有重要对象的状态再同步一次过来,重新再创建出来就可以了。

    第四,逻辑性能优化有优势,它的客户端性能优化优势也比较明显,比如优化时可以做裁剪,玩家看不到的角色可以不用创建,不用对它进行运算,节省消耗。

    缺点

    第一,它的开发效率低,相对帧同步而言要差一些,很多时候你需要保证服务器与客户端的每一个角色对象的状态之间保持一致,但事实上你很难做到一致。

    比如客户端和服务器端更新的频率,对优化的一些裁剪,网络的抖动等等,你要让每一个状态在客户端同步是比较难的,而你要想调试这些东西,来优化它带来的漏洞、不一致的现象,花费的周期也会比较长,想要达到优化好的水平也比较难。

    第二,打击感差,它比较难做出动作类游戏打击感和精确性。比如说你要做一个射击类角色,他的子弹每秒钟要产生几十颗,基于状态同步来做是比较难的,因为系统在很短时间内,会产生很多数据,要通过创建、销毁、位置运算来同步。

    第三,它的流量会随着游戏的复杂度,而逐渐增长,比如角色的多少。希望在3G、4G的网络条件下也能够玩PvP,所以我们希望它对付费流量的消耗能控制在比较合理的水平,不希望打一局游戏就消耗几十兆的数据流量。

     

    防攻击

    来自熊猫:
    1.特别是搞棋牌的项目,还有小公司没法通过法律手段来防止别人攻击。特别有用处。因为高防实在太贵,用不起。
    2. 需要有很多ip,客户端能够随时切换连接,这样别人攻击一个可以切换其他的进程去连接。
    3. 需要保证状态不丢失,消息不丢失不重发,显然tcp做不到。
    4. 使用udp。因为udp是无连接的
    5. 需要保证消息可靠,所以kcp是非常合理的选择
    6. 可以设计一个路由进程来转发,udp消息通过路由再转发给realm gate等等
    7. 路由进程可以起非常多个,客户端在连接realm或者gate之前先请求路由进程,告诉路由进程自己需要真正连接的地址,路由进程记录下来。然后客户端用kcp连接,路由进程把发过来的udp消息转发给真正的地址,比如gate。所以服务端对外的是路由进程,gate realm变成了内网地址。
    8. 客户端连接会每隔2秒ping一次,ping超过10s没有回消息则重新请求一个路由来连接。这样别人攻击一台路由我们就可以不停的关闭被攻击的路由进程,或者开启新的路由都可以。
    9.因为udp无连接状态,kcp会保证不丢消息会重发,所以即使换了路由进程,仍然能够保证消息一致性

     

    参考链接:

    MMORPG服务器架构 https://blog.csdn.net/a6627651/article/details/47044589

    tx王者 https://blog.csdn.net/anypkv/article/details/78480877

    熊猫:状态同步做魔兽争霸3也是可以的,每个小兵不是时时刻刻都在变化移动路线,团战的时候大部分小兵都是站着不动的。移动消息量其实不大。移动消息改成发送移动路径,只要小兵移动路径不变,就不需要发送新的移动消息,状态同步完全可以做rtx。个人

    有工具把unity的mesh导出成recast格式的mesh文件,然后自带的demo生成寻路文件。研究下吧,会c++就不难,一个星期可以搞定

     

     

     

    观点

    展开全文
  • 复盘王者荣耀手游开发全过程,Unity引擎使用帧同步放弃状态同步 如今已经大获市场成功的《王者荣耀》一直是业内各方关注的对象,而我们也知道这款产品在成为国民级游戏之前,也遇到过一段鲜有人知的调优期。也就是在...

    复盘王者荣耀手游开发全过程,Unity引擎使用帧同步放弃状态同步

    如今已经大获市场成功的《王者荣耀》一直是业内各方关注的对象,而我们也知道这款产品在成为国民级游戏之前,也遇到过一段鲜有人知的调优期。也就是在2015年8月18号正式不删档测试版本推出之后,被腾讯评级为不达六星之后的时间。

    据闪电站小猪了解,在8月之后的两个月间,《王者荣耀》技术团队对这个产品进行了非常深度的优化,并攻克了局内同步、网络要求,以及性能表现的三大难关,成功达到了腾讯六星产品的标准。比如延迟、卡顿等不同步问题的出现概率从过去的1%,降低到了0.01%,大幅度地改善了游戏体验。

    近日在Unity举办的Unite 2017 开发者大会上,《王者荣耀》项目组技术总监邓君针对这款游戏的调优历程进行了复盘,回顾了这款产品在技术层面遇到的三大问题,以及他们的解决方案。

    值得一提的是,参会的开发者也非常关注这次的演讲内容,不仅会长当中人员爆满,围住了整个演讲场地,在演讲结束之后,我们也听到一路上诸多开发者对《王者荣耀》技术经验的探讨。

    闪电站小猪认为开发手游的朋友可以看看这篇技术演讲。

    以下为邓君的演讲内容:

    大家好,我是《王者荣耀》的邓君,很高兴今天能够有这样一个机会跟在座的同行一起聊聊技术,互相交流,也感谢Unity提供这样的机会,可以由一个互动。

    ![](http://wx3.sinaimg.cn/large/6b1a311ely1fftcmkz2tcj20h00e7q3e.jpg
    在这里插入图片描述

    这次的主题主要是讲一下《王者荣耀》从立项之初经历的惨淡时期到华丽的翻盘,包括碰到技术方面的问题,以及游戏方向上的改变。

    我是技术出身的,整个课题也是技术面的,会重点介绍王者荣耀和现在见到大部分不同的技术方案,它实际原理、问题和优化的思路。

    先简单自我介绍一下,我是2004年加入腾讯,在腾讯做了4年多的应用层面开发,还包括web各种各样后台都做过,经历比较丰富。在2009年我回到成都,刚好成都的岗位也就只有游戏部门是比较合适的,就转行做游戏了。

    在这里插入图片描述

    在成都这边,参与过《QQ封神记》的开发,之后又开发了《霸三国OL》这款游戏,这款游戏开发了三年多,我经历了它从1.0、2.0,再到3.0的版本迭代,这之后才转型做手游,直接做的《王者荣耀》。

    现在了解《王者荣耀》或者在玩的人确实比较多,但是我们曾经也没有想过它有这样的结果。当时端游很久都没有做出来成绩,业绩和收入都面临比较大的问题。

    2012、2011年前后,《霸三国OL》做到1.0版本,游戏中玩家需要控制多个单位,操作起来很难,一开始可以操作5个单位然后变成3个,但即便只有3个单位,操作也让人觉得也很痛苦,于是我们慢慢5个单位的技能合在一个英雄身上,不断地优化。能看到,在MOBA领域,你要做创新,还要脱颖而出,是很难的事情。

    在2014年底,2015年初的时候,我们准备组建一个手游团队。因为当时国内市场基本上都在开发手游,能够继续开发端游或者要准备立项端游的非常少,包括腾讯也只有2、3款端游在开发。手游是一个机会,我们当时就希望在2015年把我们的《霸三国OL》端游在手机上呈现。

    这个时候我们进行了一个初期Demo的验证,做Demo的只有三个人,引擎、框架、后台,一系列制作下来大概花了两周到三周的时间。这个Demo里有基本的进游戏、选人,然后可以释放技能,正常的战斗,到结算。

    Demo的引擎我们采用了Unity,做完之后也觉得Unity很好用,开发效率确实比较高。

    2014年年底的时候,我们制作人去公司开会,当时做一个非常明智的决策:我们需要马上暂停端游的开发,直接做手游。就是这样的一次决策,真正地扭转了我们整个团队的命运。如果晚一年,可能今天爆红的MOBA游戏就是另外一个,而不是王者了。

    于是接下来在2015年,我们才有了想法开始独立招聘20、30人来做这个手游项目。

    从端游转型做手游,肯定要面临选择,到底要用什么样的引擎,采用什么样的方案进行手游的开发。当时,腾讯以及成都周边的创业团队,基本上都用Unity,我们做Demo的时候,也选择大家用过的,已经有产品进行验证的引擎,同时我们也考察它适不适合我们的团队。

    Unity在我们当时做Demo时的理解来看,它确实对中小团队,甚至对一些大型项目来说,都有几个比较明显的优势。

    1.易上手,我们花三周就可以做出Demo,可以看到易上手是它的一个非常大的优势。

    2.它的工具都是很完善的,能够做到一站式解决,你不需要在这里面下载工具,那里面额外补充一些插件。

    3.它插件资源很丰富,我们从最开始做Demo的时候,基本上都可以从Asset Store中找到一些可以用来验证我们想法的资源,可以加快我们开发的效率。

    4.上面这三点加起来,就能体现出它非常明显的优势,即开发效率特别高。

    5.它还有跨平台的优势,它本就是跨平台的引擎。

    6.最后它还能让你更方便地补充技术人员,社招也很容易招聘到熟悉Unity的开发人员。

    对比以前我们自己做引擎,或者用过其他的引擎,从效率上来讲,最终我们选择了一个开发效率最高的引擎Unity。

    在这里插入图片描述

    我们从端游转手游是在2014年底,但真正开始研发《王者荣耀》是在2015年3月份的时候,这个时候项目的要求是让开发的周期尽量短,尽快把手游做上线。我们原本在《霸三国OL》的开发团队大概有40、50个人,再加上后来加入的人员,形成了100多人的团队,进行了游戏的开发。

    在2013、2014年的时候,手游在PvP的方面都比较弱,大部分是卡牌游戏、单机游戏。我们原本是做的端游,它的生命力包括趣味性也是很足的,所以我们做手游的目标,就是即使游戏里会存在PvE的闯关内容,但我们的核心还是要把PvP做好,让玩家有真正的对抗,让玩家与玩家有交流,能体会到这样的游戏乐趣。

    既然选择PvP,那么这款产品就是一个网络游戏,而选择网络游戏的同步机制,就是我们首要考虑的问题。同步机制中最常见的应该是CS状态同步,我们的端游也是这样做的,当然,状态同步也有他的优缺点。

    先看一下状态同步的优点。

    第一,它的安全性非常高,外挂基本上没有什么能力从中收益。

    第二,状态同步对于网络的带宽和抖动包有更强的适应能力,即便出现了200、300的输入延迟再恢复正常,玩家其实也感受不到不太舒服的地方。

    第三,在开发游戏过程中,它的断线重连比较快,如果我的游戏崩溃了,客户端重启之后只需要服务器把所有重要对象的状态再同步一次过来,重新再创建出来就可以了。

    第四,它的客户端性能优化优势也比较明显,比如优化时可以做裁剪,玩家看不到的角色可以不用创建,不用对它进行运算,节省消耗。

    再说一下我认为的缺点。

    第一,它的开发效率相对帧同步而言要差一些,很多时候你需要保证服务器与客户端的每一个角色对象的状态之间保持一致,但事实上你很难做到一致。

    比如客户端和服务器端更新的频率,对优化的一些裁剪,网络的抖动等等,你要让每一个状态在客户端同步是比较难的,而你要想调试这些东西,来优化它带来的漏洞、不一致的现象,花费的周期也会比较长,想要达到优化好的水平也比较难。

    第二,它比较难做出动作类游戏打击感和精确性。比如说你要做一个射击类角色,他的子弹每秒钟要产生几十颗,基于状态同步来做是比较难的,因为系统在很短时间内,会产生很多数据,要通过创建、销毁、位置运算来同步。

    第三,它的流量会随着游戏的复杂度,而逐渐增长,比如角色的多少。我们做《王者荣耀》时,希望在3G、4G的网络条件下也能够玩PvP,所以我们希望它对付费流量的消耗能控制在比较合理的水平,不希望打一局游戏就消耗几十兆的数据流量。

    在这里插入图片描述

    而另一种同步策略是帧同步。

    这种技术应用的很广泛,最早的《星际争霸》《魔兽争霸3》都采用了帧同步,他们都基于局域网运行,网络的条件非常好,也不需要服务器就能搞定。帧同步的优点有几个:

    第一,它的开发效率比较高。如果你开发思路的整体框架是验证可行的,如果你把它的缺点解决了,那么你的开发思路完全就跟写单机一样,你只需要遵从这样的思路,尽量保证性能,程序该怎么写就怎么写。

    比如我们以前要在状态同步下面做一个复杂的技能,有很多段位的技能,可能要开发好几天,才能有一个稍微过得去的结果,而在帧同步下面,英雄做多段位技能很可能半天就搞定了。

    第二,它能实现更强的打击感,打击感强除了我们说的各种反馈、特效、音效外,还有它的准确性。利用帧同步,游戏里面看到这些挥舞的动作,就能做到在比较准确的时刻产生反馈,以及动作本身的密度也可以做到很高的频率,这在状态同步下是比较难做的。

    第三,它的流量消耗是稳定的。大家应该看过《星级争霸》的录像,它只有几百K的大小,这里面只有驱动游戏的输入序列。帧同步只会随着玩家数量的增多,流量才会增长,如果玩家数量固定的话,不管你的游戏有多复杂,你的角色有多少,流量消耗基本上都是稳定的。这点延伸开来还有一个好处,就是可以更方便地实现观战,录像的存储、回放,以及基于录像文件的后续处理。

    帧同步也有它的缺点。

    第一,最致命的缺点是网络要求比较高,帧同步是锁帧的,如果有网络的抖动,一段时间调用次数不稳定,网络命令的延迟就会挤压,引起卡顿。

    第二,它的反外挂能力很弱,帧同步的逻辑都在客户端里面,你可以比较容易的修改它。但为什么《王者荣耀》敢用帧同步,一方面是因为当时立项的时候开发周期很短,半年时间要做上线,要有几十个英雄,存在时间的压力,另一方面,MOBA类游戏不像数值成长类的游戏,它的玩法是基于单局的,单局的作弊修改,顶多影响这一局的胜负,不会存档,不会出现刷多少钱刷多少好的装备的问题,而且作弊之后我们也很容易监测到,并给予应有的惩罚,所以我们认为这不是致命的缺点。

    第三,它的断线重回时间很长,相信台下也有很多王者玩家,也曾碰到过闪退以后重回加载非常长的情况,甚至加载完以后游戏也快结束了,这是帧同步比较致命的问题。

    第四,它的逻辑性能优化有很大的压力。大家应该没有见到哪一款大型游戏是用帧同步来做的,因为这些游戏的每一个逻辑对象都是需要在客户端进行运算的。如果你做一个主城,主城里面有上千人,上千人虽然玩家看不到它,但游戏仍然需要对他们进行有效的逻辑运算,所以帧同步无法做非常多的对象都需要更新的游戏场景。

    ![](http://n.sinaimg.cn/translate/20170513/htnp-fyfeutp8592016.png “1494658497181849.png”在这里插入图片描述

    那么我们为什么选择了帧同步而放弃了状态同步呢?

    我们前面提到它两个优点缺点是相对的,这边的优点对于那边来说就是缺点。对于我们手游立项的时候,最重要就是时间。当时市面上正在开发的MOBA手游不止王者一款,大家都在争上线的时间,所以我们要选择一个开发周期最短的方案。

    然后我们做端游的时候也有一个深刻的体会,如果要做有趣的英雄,有趣的技能,它在状态同步上面很难调出一个比较满意的效果。所以最后我们依然选择帧同步的方案。

    现在来看,选择帧同步方案之后,我们再把它的缺点进行优化或是规避,之后它带来的好处是非常明显的。《王者荣耀》重除了英雄的设计以及技能的感觉,还有很重要的一点,就是它确实在做一些非常有特色的英雄,它的技能、反馈、体验上面都做得不错,这些都是基于帧同步技术方案带来的优势。

    我们选择了方案之后,当时觉得很high,觉得这样一个技术方案开发起来得心应手,效率如此之高,做出来的效果也很好。

    但事实上,它也有好的一面,也有坏的一面,技术测试版本上线后质量不好,其中技术层面遇到的问题就是下面这三座大山。

    第一是同步性,同步性这块容易解决,其实也解决了;

    第二也是最大一块网络问题,帧同步它的网络问题导致我们对它技术方案的原理没有吃透,碰到了一些问题,那时候游戏的延迟很重,画面卡顿,能明显感觉走路抖动的现象;

    第三是性能问题,这个问题始终存在,我们也一直在优化。

    第一座大山,最容易解决的同步问题。

    帧同步的技术原理相当简单,10、20年前在应用这种技术了,从一个相同初始的状态开始,获得一个相同的输入,往下一帧一帧执行,执行时所有代码的流程走得都是一样的,这个结果调用完了以后,又有一个新状态,完成循环。相同的状态,相同的流程,不停的这样循环下去。

    这个原理虽然简单,但是你要去实现它的时候,还是会有很多坑。

    右边写的是实现要点,这是我们在解决第一座大山经验的总结,也是我们实际开发过程当中做的事情。

    首先,我们所有的运算都是基于整数,没有浮点数。浮点数是用分子分母表达的。

    其次,我们还会用到第三方的组件,帧组件也要需要进行一个比较严格的甄别。我们本身用的公司里面关于时间轴的编辑器里面,最初也是是浮点数,我们都是进行重写改造的。

    再次,很多人初次接触帧同步里面的问题,就是在写逻辑的时候和本地进行了关联、和“我”相关,这样就导致不同客户端走到了不同的分支。实际上,真正客户端跟逻辑的话,要跟我这样一个概念无关。

    接下来还有随机数,这个要严格一致。这是实现的要点,严格按照这上面的规则写代码还是有可能不同步,本身就很难杜绝这样的问题。

    最后,真正重要的是开发者要提升自己发现不同步问题的能力,什么时候不同步了,不同步你还要知道不同步在什么点,这是最关键的。你需要通过你的经验和总结提升这样的能力。这个能力还是通过输出来看不同客户端不同输出,找到发生在什么点。

    比如在《王者荣耀》里,我们看到不同步的现象应该是这样,有人对着墙跑,你看到的和别人玩的游戏是不一样的,就像进入平行世界。

    最开始测试《王者荣耀》的,我们希望不同步率达到1%,就是100局里面有1局出现不同步,我们就算游戏合格,但实际上对于这么大体量游戏来说,这个比率是有问题的,经过我们不停的努力,现在已经控制在万分之几,一万局游戏里面,可能有几局是不同步的。

    这个问题不一定是代码原因或者没有遵循这些要点才出现的,有可能是你去修改内存,你去加载资源的时候,本地资源有损害或者缺失,或者是异常。说白了,你没有办法往下执行,大家走了不同分支,这都可能引起最终是不同步的。

    如果你不同步概率比较低,到了这种万分之几概率的时候,很难通过测试来去还原,去找到这样不同步的点。

    最开始我们游戏出现不同步的时候,就是在周末玩家开黑多的时候,随着你的概率越来越低,基本上你就自己就还原不出这些问题了,只能依靠玩家帮你还原这样的场景,来分析这样的不同步问题。

    同步性遵循这样的要点,按照这样的思路来写,加上你不同步定位的能力,有了监控手段能够去发现,这个问题其实就解决了。解决之后,你就可以好好享受帧同步的开发优势。

    第二座大山就是网络,《王者荣耀》技术测试版本出台的时候,延迟非常大,而且还是卡顿,现在看一下帧同步里面比较特别的地方。帧同步有点像在看电影,它传统的帧同步需要有buffer,每个玩家输入会转发给所有客户端,互相会有编号,按顺序输入帧。

    比如我现在已经收到第N帧,只有当我收到第N+1帧的时候,第N这一帧我才可以执行。服务器会按照一定的频率,不同的给大家同步帧编号,包括这一帧的输入带给客户端,如果带一帧给你的数据你拿到之后就执行,下一帧数据没来就不能执行,它的结果就是卡顿。

    网络绝对理想的情况下还好,但现实的网络环境不是这样的。帧同步要解决问题就是调试buffer,以前有动态的buffer,它有1到n这样的缓冲区,根据网络抖动的情况,收入然后放到队列里面。

    这个buffer的大小,会影响到延迟和卡顿。如果你的buffer越小,你的延迟就越低,你拿到以后你不需要缓冲等待,马上就可以执行。但是如果下一帧没来,buffer很小,你就不能执行,最终导致的结果你的延迟还好,但是卡顿很明显。

    如果调到帧同步的buffer,假如我们认为网络延迟是1秒,你抖动调到1秒,那得到的结果虽然你画面不抖动了,但是你的延迟极其高。如果连最坏的网络情况都考虑进去,buffer足够大,那么记过就跟看视频是一样的,平行的东西,看你调大条小。一些局部的措施我们都做过,都是一样的问题。

    具体我们怎么优化卡顿的问题呢?

    刚才提到该帧同步与buffer,这个buffer可以是1也可以到n,我们要解决我们的延迟问题,我们就让buffer足够小。事实上《王者荣耀》最后做到的buffer是零,它不需要buffer,服务器给了我n,马上知道是n,我收到n,我知道下一次肯定是n+1,所以我收到n之后马上就把n这一帧的输入执行了。

    那么为什么不卡顿了,画面不抖动了?

    最后一个关键点,是本地插值平滑加逻辑与表现分离。客户端只负责一些模型、动画、它的位置,它会根据绑定的逻辑对象状态、速度、方向来进行一个插值,这样可以做到我们的逻辑帧率和渲染帧率不一样,但是做了插值平滑和逻辑表现分离,画面不抖了,延迟感也是很好的。

    做了这些后,我们还把TCP换成UDP,在手机环境下,弱网的情况下,TCP很难恢复重连,所以最后用了UDP来做。整体来说,在网络好的情况下,它延迟也是很好的,在网络比较差的情况下做插值,也是传统CS的表现。

    我们经常见到角色A和B,有些客户端A在左B在右,有些是A在右B在左,帧同步逻辑上面AB之间的距离和坐标都是完全一样,但是画面上看到他们可能会不重合,那就是你把它们分离之后的表现。网络极其好的情况下,它应该是重合的,但是在网络差的情况下,可能会有些偏差。这里面是最重要的一块优化。

    第三座大山,是我们对性能的优化。

    本身帧同步逻辑上面在优化上面存在一些缺点,所有的角色都需要进行运算。这方面我们也是借助Unity的特性,如果你想追求性能上的极致,有些东西你需要寻求好的方式。

    第一点是热点的处理。

    我们是不用反射的,它都有GC性能开销,我们的做法里面,会把对象的显示隐藏放在不同的渲染层里面,尽量让整个游戏帧率是平滑的过程。还有我们本身有自己的系统,比如AI,在《王者荣耀》这样的多角色游戏中,你如果想要做出比较好的体验,那么AI就要做得比较复杂。

    而要去优化热点,我觉得就只有这三个步骤可以走。

    首先,从程序的结构上面能找到更优的,它的优化效果就是最明显的;其次,如果你的结构都是用的最好,就去挖掘局部的算法,调整你代码的一些写法。最后,如果局部的算法都已经调到最优还是没有什么办法,那只有一条路,就是牺牲整个质量,就是分帧降频。

    第二点是GC,这块刚才说不用反射,还有装箱和拆箱的行为也是尽量少用。

    Unity指导过我们的优化,从GC上面的考虑,他们建议每一帧应该在200个字节以内是比较好的状态,其实很难做到,王者也是每一帧在1k左右,很难做到200。

    第三点是Drawcall,这些传统的优化手段大家都用的很熟了。

    第四点是裁剪,帧同步里面是不能裁剪的,表现里面我看不到的可以降低频率或者不更新它,这在表现里面可以做的。

    第五点是3DUI的优化,比如《王者荣耀》的血条、小地图上面叠的元素等等,这些UI都比较丰富,这块我们用了31UI的方式来优化,没有用UGUI里面进行血条方面的处理。

    我们也牺牲了一些东西,我们把所有东西都加载了,在游戏过程当中,我们希望不要有任何IO行为,包括输出我们都是要布局的。你处理的决策和复杂度,如果在一帧里面放出100颗子弹,在放100颗子弹的时候一定要掉帧的,一定要在力所能及的时候把这些东西做到极致。

    上面提的是我们的第一代,也是在去年5月份以前做的优化方案。5月份以后,我们还做了另外一件事情:GameCore。

    首先,为什么我们觉得iOS比安卓的优化效率高一些,一方面是iOS的CPU架构包括系统确实都优化的比较好,另一方面我们用的Unity4.6,在IOS下面它本身效率高一些,在安卓端的机器各种各样,性能也是千差万别,我们只能用性能比较差的方式。

    因为我们已经做到逻辑和表现的分离,那么我们能不能把逻辑独立出来,做成一个C++的东西,实际上我们在去年开始已经在这样做了。做之前也测试过C++和Mono性能的差别,大概是2.5左右,本身我们的逻辑占比游戏消耗20%多,逻辑不是一个大头,我们做了这件事情之后,还是有效的,帧率提升了2到3帧,花的时间很长。

    其次,做GameCore以后最明显的变化是我们以前逻辑上的GC没有了,我们有自己内存的管理、对象的管理,包括里面所有的容器类这些东西都是我们自己实现的,包括反射整个一套。它有了自己的内存管理,本身的效率就会比较高,这就已经是一个比较明显的优势了。

    再次,有了GameCore之后,又多了很多应用场景,这个东西就是玩法的服务器版本,应用场景运行服务器要做很多的分析,还有第三方使用都是可以的。

    最后,GameCore还有可以扩展多线程的潜力。

    今后,我们也有几个计划。

    第一,我们考虑能不能在热更新上面有所突破。因为王者这样一个游戏类型,包括它的体量,我们对于性能有一个比较极致的追求,不会轻易使用脚本层面在性能层面本身就不是最好的。这个我们要去研究的就是热更新,性能最好的方式。

    第二,我们也在和硬件厂商沟通,他们希望游戏能够真正发挥多核性能上的优势,大部分的游戏在单核上面,把一个核吃的满满的,很多时候我们现在得出的结论,GPU性能也很强,王者并没有对GPU占满,可能只用了30%,CPU反而吃的比较满,吃满以后它还有另外一个坏处,它的发热、降频,你如果用多线程、多核去尽量平坦,让它不要处于高频的工作方式,反而会有更好的效果。

    第三,我们现在用的是Unity 4.6版本,但Unity已经进化到5.7版了,后面他们还会推出新的特性,我们希望结合一些Unity新特性,现在已经有些游戏用5.6可以提升性能。

    最后,不光是提升性能问题,Unity在多线程的渲染,也有很好的作用,使用引擎优势也是很必要的。随着性能的提升,我们会对王者的画质进行升级。

    好的,我今天的演讲就到此结束了。

    转载请注明来自:[王者荣耀闪电站]http://king.shandian.biz/88.html

    展开全文
  • [文章翻译自Unity5.2的官方文档]状态同步状态同步是从服务器向客户端方向上的。本地客户端没有序列化的数据,因为它和服务器共享同一个场景。任何为本地客户端序列化的数据都是多余的。然而,SyncVar钩子函数会被...

    [文章翻译自Unity5.2的官方文档]

    状态同步

    状态同步是从服务器向客户端方向上的。本地客户端没有序列化的数据,因为它和服务器共享同一个场景。任何为本地客户端序列化的数据都是多余的。然而,SyncVar钩子函数会被本地客户端调用。

    数据不会从客户端向服务器同步,这个方向上的操作叫做命令(Commands)。

    同步变量

    同步变量是NetworkBehaviour脚本中的成员变量,他们会从服务器同步到客户端上。当一个物体被派生出来之后,或者一个新的玩家中途加入游戏后,他会接收到他的视野内所有物体的同步变量。成员变量通过[SyncVar]标签被配置成同步变量:

    class Player :NetworkBehaviour
    {
        [SyncVar]
        int health;
    
        public void TakeDamage(int amount)
        {
        if (!isServer)
        return;
    
        health -= amount;
        }
    }

    同步变量的状态在OnStartClient()之前就被应用到物体上了,所以在OnStartClient函数中,物体的状态已经是最新的数据。

    同步变量可以是基础类型,如整数,字符串和浮点数。也可以是Unity内置数据类型,如Vector3和用户自定义的结构体,但是对结构体类型的同步变量,如果只有几个字段的数值有变化,整个结构体都会被发送。每个NetworkBehaviour脚本可以有最多32个同步变量,包括同步列表(见下面的解释)。

    当同步变量有变化时,服务器会自动发送他们的最新数据。不需要手工为同步变量设置任何的脏数据标志位。

    注意在属性设置函数中设置一个同步变量的值不会使他的脏数据标志被设置。如果这样做的话,会得到一个编译期的警告。因为同步变量使用他们自己内部的标识记录脏数据状态,在属性设置函数中设置脏位会引起递归调用问题。

    同步列表(SyncLists)

    同步列表类似于同步变量,但是他们是一些值的列表而不是单个值。同步列表和同步变量都包含在初始的状态更新里。同步列表不需要[SyncVar]属性标识,他们是特殊的类。内建的基础类型属性列表有:

    SyncListString
    SyncListFloat
    SyncListInt
    SyncListUInt
    SyncListBool

    还有个SyncListStruct可以给用户自定义的结构体用。从SyncListStruct派生出的结构体类可以包含基础类型,数组和通用Unity类型的成员变量,但是不能包含复杂的类和通用容器。

    同步列表有一个叫做SyncListChanged的回调函数,可以使客户端能接收到列表中的数据改动的通知。这个回调函数被调用时,会被通知到操作类型,和修改的变量索引。

    
    
    public class MyScript :NetworkBehaviour
    {
        public struct Buf
        {
            public int id;
            public string name;
            public float timer;
        };
    
        public class TestBufs : SyncListStruct<Buf> {}
        TestBufs m_bufs = new TestBufs();
    
        void BufChanged(Operation op, int itemIndex)
        {
            Debug.Log("buf changed:" + op);
        }
    
        void Start()
        {
            m_bufs.Callback = BufChanged;
        }
    }

    定制序列化函数

    通常在脚本中使用同步变量就够了,但是有时候也需要更复杂的序列化代码。NetworkBehaviour中的虚函数允许开发者定制自己的序列化函数,这些函数有:

    public virtual boolOnSerialize(NetworkWriter writer, bool initialState);
    public virtual voidOnDeSerialize(NetworkReader reader, bool initialState);

    initalState可以用来标识是第一次序列化数据还是只发送增量的数据。如果是第一次发送给客户端,必须要包含所有状态的数据,后续的更新只需要包含增量的修改,以节省带宽。同步变量的钩子函数在initialState为True的时候不会被调用,而只会在增量更新函数中被调用。

    如果一个类里面声明了同步变量,这些函数的实现会自动被加到类里面,因此一个有同步变量的类不能拥有自己的序列化函数。

    OnSerialize函数应该返回True来指示有更新需要发送,如果它返回了true,这个类的所有脏标志位都会被清除,如果它返回False,则脏标志位不会被修改。这可以允许将多次改动合并在一起发送,而不需要每一帧都发送。

    序列化流程

    具有NetworkIdentity组件的游戏物体可以带有多个从NetworkBehaviour派生出来的脚本,这些物体的序列化流程为:

    在服务器上:

    • 每个NetworkBehaviour上都有一个脏数据掩码,这个掩码可以在OnSerialize函数中通过syncVarDirtyBits访问到

    • NetworkBehavious中的每个同步变量被指定了脏数据掩码中的一位

    • 对同步变量的修改会使对应的脏数据位被设置

    • 或者可以通过调用SetDirtyBit函数直接修改脏数据标志位

    • 服务器的每个Update调用都会检查他的NetworkIdentity组件

    • 如果有标记为脏的NetworkBehaviour,就会为那个物体创建一个更新数据包

    • 每个NetworkBehaviour组件的OnSerialize函数都被调用,来构建这个更新数据包

    • 没有脏数据位设置的NetworkBehaviour在数据包中添加0标志

    • 有脏数据位设置的NetworkBehavious写入他们的脏数据和有改动的同步变量的值

    • 如果一个NetworkBehavious的OnSerialize函数返回了True,那么他的脏标志位被重置,因此直到下一次数据修改之前不会被再次发送

    • 更新数据包被发送到能看见这个物体的所有客户端

    在客户端:

    • 接收到一个物体的更新数据包

    • 每个NetworkBehavious脚本的OnDeserialize函数被调用

    • 这个物体上的每个NetworkBehavious脚本读取脏数据标识

    • 如果关联到这个NetworkBehaviour脚本的脏数据位是0,OnDeserialize函数直接返回;

    • 如果脏数据标志不是0,OnDeserialize函数继续读取后续的同步变量

    • 如果有同步变量的钩子函数,调用钩子函数

    对下面的代码:

    
    
    public class data :NetworkBehaviour
    {
        [SyncVar]
        public int int1 = 66;
    
        [SyncVar]
        public int int2 = 23487;
    
        [SyncVar]
        public string MyString = "esfdsagsdfgsdgdsfg";
    }
    
    //产生的序列化函数OnSerialize将如下所示:
    
    public override boolOnSerialize(NetworkWriter writer, bool forceAll)
    {
        if (forceAll)
        {
            // 第一次发送物体信息给客户端,发送全部数据
            writer.WritePackedUInt32((uint)this.int1);
            writer.WritePackedUInt32((uint)this.int2);
            writer.Write(this.MyString);
            return true;
        }
        bool wroteSyncVar = false;
        if ((base.get_syncVarDirtyBits() & 1u) != 0u)
        {
            if (!wroteSyncVar)
                {
                    // write dirty bits if this is the first SyncVar written
                    writer.WritePackedUInt32(base.get_syncVarDirtyBits());
                    wroteSyncVar = true;
                }
            writer.WritePackedUInt32((uint)this.int1);
        }
        if ((base.get_syncVarDirtyBits() & 2u) != 0u)
        {
            if (!wroteSyncVar)
            {
                // write dirty bits if this is the first SyncVar written
                writer.WritePackedUInt32(base.get_syncVarDirtyBits());
                wroteSyncVar = true;
            }
            writer.WritePackedUInt32((uint)this.int2);
        }
        if ((base.get_syncVarDirtyBits() & 4u) != 0u)
        {
            if (!wroteSyncVar)
            {
                // write dirty bits if this is the first SyncVar written
                writer.WritePackedUInt32(base.get_syncVarDirtyBits());
                wroteSyncVar = true;
            }
            writer.Write(this.MyString);
        }
    
        if (!wroteSyncVar)
        {
            // write zero dirty bits if no SyncVars were written
            writer.WritePackedUInt32(0);
        }
        return wroteSyncVar;
        }
    //反序列化函数将如下:
    
    public override voidOnDeserialize(NetworkReader reader, bool initialState)
    {
        if (initialState)
        {
            this.int1 = (int)reader.ReadPackedUInt32();
            this.int2 = (int)reader.ReadPackedUInt32();
            this.MyString = reader.ReadString();
            return;
        }
        int num = (int)reader.ReadPackedUInt32();
        if ((num & 1) != 0)
        {
            this.int1 = (int)reader.ReadPackedUInt32();
        }
        if ((num & 2) != 0)
        {
            this.int2 = (int)reader.ReadPackedUInt32();
        }
        if ((num & 4) != 0)
        {
            this.MyString = reader.ReadString();
        }
    }
    

    如果这个NetworkBehaviour的基类也有一个序列化函数,基类的序列化函数也将被调用。

    注意更新数据包可能会在缓冲区中合并,所以一个传输层数据包可能包含多个物体的更新数据包。

    远程动作

    网络系统允许在网络上执行远程的动作。这类动作有时也叫做远程过程调用(RPC)。有两种类型的远程过程调用,命令(Commands) – 由客户端发起,运行在服务器上;和客户端远程过程调用(ClientRpc) - 服务器发起,运行在客户端上。

    下图显示了两种远程过程调用的方向

    命令(Commands)

    命令从客户端上的物体发给服务器上的物体。出于安全考虑,命令只能从玩家控制的物体上发出,因此玩家不能控制其他玩家的物体。要把一个函数变成命令,需要给这个函数添加[Command]属性,并且为函数名添加“Cmd”前缀,这样这个函数会在客户端上被调用时在服务器上运行。所有的参数会自动和命令一起发送给服务器。

    命令函数的名字必须要有“Cmd”前缀。在阅读代码的时候,这也是个提示 – 这个函数比较特殊,他不像普通函数一样在本地被执行。

    class Player :NetworkBehaviour
    {
        public GameObject bulletPrefab;
    
        [Command]
        void CmdDoFire(float lifeTime)
        {
            GameObject bullet =(GameObject)Instantiate(bulletPrefab,transform.position+transform.right,Quaternion.identity);
            var bullet2D =bullet.GetComponent<Rigidbody2D>();
            bullet2D.velocity = transform.right *bulletSpeed;
            Destroy(bullet, lifeTime);
            NetworkServer.Spawn(bullet);
        }
    
        void Update()
        {
            if (!isLocalPlayer)
                return;
    
            if (Input.GetKeyDown(KeyCode.Space))
            {
                CmdDoFire(3.0f);
            }
        }
    }

    注意如果每一帧都发送命令消息,会产生很多的网络流量。

    默认情况下,命令是通过0号通道(默认的可靠传输通道)进行传输的。所以默认情况下,所有的命令都会被可靠地发送到服务器。可以使用命令的“Channel”参数修改这个配置。参数是一个整数,表示通道号。

    1号通道是默认的不可靠传输通道,如果要用这个通道,把这个参数设置为1,示例如下:

    [Command(channel=1)]

    从Unity5.2开始,可以从拥有客户端授权的非玩家物体发出命令。这些物体必须是使用函数NetworkServer.SpawnWithClientAuthority()派生出来的,或者是使用NetworkIdentity.AssignClientAuthority()授权过的。从物体发送出来的命令会在服务器上运行,而不是在相关玩家物体所在的客户端上。

    客户端远程过程调用(ClientRPC Calls)

    客户端远程过程调用从服务器的物体发送到客户端的物体上去。他们可以从任何带有NetworkIdentity并被派生出来的物体上发出。因为服务器拥有授权,所以这个过程不存在安全问题。要把一个函数变成客户端远程过程调用,需要给函数添加[ClientRpc]属性,并且为函数名添加“Rpc”前缀。这个函数将在服务端上被调用时,在客户端上执行。所有的参数都将自动传给客户端。

    客户端远程调用必须带有“Rpc”前缀。在阅读代码的时候,这将是个提示 – 这个函数比较特殊,不像一般函数那样在本地执行。

    class Player :NetworkBehaviour
    {
    
        [SyncVar]
        int health;
    
        [ClientRpc]
        void RpcDamage(int amount)
        {
        Debug.Log("Took damage:" +amount);
        }
    
        public void TakeDamage(int amount)
        {
        if (!isServer)
        return;
    
        health -= amount;
        RpcDamage(amount);
        }
    }

    当使用伺服器模式运行游戏的时候,客户端远程调用将在本地客户端执行 – 即使他其实和服务器运行在同一个进程。因此本地客户端和远程客户端对客户端远程过程调用的处理是一样的。

    远程过程的参数

    传递给客户端远程过程调用的参数会被序列化并在网络上传送,这些参数可以是:

    • 基本数据类型(字节,整数,浮点树,字符串,64位无符号整数等)

    • 基本数据类型的数组

    • 包含允许的数据类型的结构体

    • Unity内建的数学类型(Vector3,Quaternion等)

    • NetworkIdentity

    • NetworkInstanceId

    • NetworkHash128

    • 带有NetworkIdentity组件的物体

    远程过程的参数不可以是游戏物体的子组件,像脚本对象或Transform,他们也不能是其他不能在网络上被序列化的数据类型。

    转载自:

    http://www.aichengxu.com/view/2405129


    欢迎关注我的微信个人订阅号
    这里写图片描述
    每天多学一点0.0

    展开全文
  • 对于联网游戏来讲,同步的方式主要分为两种,状态同步、帧同步。 1、状态同步:顾名思义,是指的将其他玩家的状态行为同步的方式,一般情况下AI逻辑,技能逻辑,战斗计算都由服务器运算,只是将运算的结果同步给...
  • Off代表不想使用状态同步,后两种是状态同步的两种不同的传输模式,一个是基于差异的压缩,另一个是非可靠的机制。两种都可以实现所需功能,只是根据对带宽、延迟等要求不同决定具体用哪种。 下面是设置Observed,...
  • 帧同步和状态同步

    2019-10-24 21:19:34
    帧同步和状态同步 参考: 腾讯游戏学院:Unity3D RTS游戏中帧同步实现 unity帧同步游戏极简框架及实例(附客户端服务器源码) 腾讯游戏学院:程序丨网络同步和卡顿有多要命?《球球大作战》客户端优化分享 帧同步...
  • unity5.1 Unet同步问题总结

    千次阅读 2017-02-13 13:24:29
     状态同步是从服务器向客户端方向上的。本地客户端没有序列化的数据,因为它和服务器共享同一个场景。任何为本地客户端序列化的数据都是多余的。然而,SyncVar的hook函数会被本地客户端调用。注意数据不会从客户端...
  • 状态同步 状态同步是从服务器向客户端方向上的。本地客户端没有序列化的数据,因为它和服务器共享同一个场景。任何为本地客户端序列化的数据都是多余的。然而,SyncVar钩子函数会被本地客户端调用。 数据不会从...
  • 战斗同步策略之状态同步

    千次阅读 2017-08-02 15:43:44
    采用状态同步策略,客户端采用Unity实现多人战斗游戏
  • Unity

    2020-10-28 17:35:13
    Unity网络模块 Unity 回放功能 关于Unity的回放功能 两种同步模式:状态同步和帧同步 unity 自动重播战斗,并录制视频
  • Photon与Unity核心技术之角色动作同步

    千次阅读 2018-03-14 09:26:24
    上篇介绍了角色更换武器同步处理,本篇介绍关于使用Photon实现角色同步,客户端还是选择Unity开发,在实现动作之前,Unity为我们提供了我们俗称为老动画和新动画状态机,我们先介绍老动画的实现方式,它是通过...
  • B也命名为M,A的主机中M身上绑定脚本不断发送M的最新状态,B收到后更新M的状态,形成轨迹,这样的状态同步,请问有什么问题吗,这么做比帧同步主要差在哪里,我知道这类游戏最好是帧同步,可是,要制作确定性的系统...
  • 同步的原理大意是 ,游戏运行时以10fps(100毫秒间隔,具体数值可根据实际情况调整)运行一个逻辑帧,逻辑帧负责物理、ai、攻击判定等等。...双方客户端接收的操作内容是一致的,初始状态是一致的,所以运行的
  • unity5. Unet同步问题总结

    千次阅读 2017-03-25 16:00:39
     状态同步是从服务器向客户端方向上的。本地客户端没有序列化的数据,因为它和服务器共享同一个场景。任何为本地客户端序列化的数据都是多余的。然而,SyncVar钩子函数会被本地客户端调用。注意数据不会从客户端向...

空空如也

空空如也

1 2 3 4 5 ... 7
收藏数 127
精华内容 50
关键字:

unity状态同步