仍是先说下追帧的问题吧。飞机项目采用的是帧同步的方案,渲染层与逻辑层分离,由定时器一秒20帧来驱动逻辑层作update,而对于渲染层则是以一秒40帧的速度来驱动。渲染层轮循逻辑层作插值。在网络抖动的状况下,本地演算的帧LocalFrameId可能会落后或领先服务器下发的ServerFrameId。顺便提一句,ServerFrameId在这里主要是为了给各个客户端一个统一的参照系,并不会在Frame中下发其它客户端的命令。也能够不经过ServerFrameId,而是经过对比其它客户端的帧命令来校订各客户端的时钟尽量地保持一致,我我的不推荐这种方案,这里也就不展开讨论了。那么当LocalFrameId与ServerFrameId差距较大时,我以前的方案会调整本地逻辑层演算的速率,即LocalFrameId落后于ServerFrameId时加快(也就是调小了定时器驱动逻辑层的间隔),而领先于ServerFrameId时则减慢。好比在领先5帧(也就是100MS)左右可能会调整逻辑层驱动间隔由50MS=>120MS,落后5帧时则50MS=>20MS。可是实际测试时效果反而变差了。网络不太好,但也不是那么差时会出现飞机忽快忽慢的现象。原本是但愿快速调整差距的,没想到手感反而变差了。
我仔细想了下,以为以前的认识仍是有一些问题的。首先为了排除干扰因素,咱们假设客户端开始游戏后,其计时时钟与服务器计时时钟彻底一致。对客户端而言,到底什么是网络抖动呢?事实上就是网络帧在某处大量累积,在某刻又忽然大量涌入。这样致使在一段时间前LocalFrameId远远领先ServerFrameId,此时逻辑层的逻辑帧被调得很慢,而在以后后网络帧又忽然涌入,致使LocalFrameId远远落后于ServerFrameId,逻辑帧又被调得很快。若是只考虑当前玩家的操做体验的话,在必定的网络抖动范围内(假设为500MS),不调节逻辑层帧率,对玩家而言手感上天然是更好的。只是在预测其它玩家或AI时,可能会过分预测一些,或者某些极少数状况下玩家的操做在本地演算是成功的,可是实际上并不能成功,会对玩家体验形成一些影响。可是一方面逻辑层回滚加上渲染层平滑插值会大大减轻这种不适感,二则不少时候的预测是正确或者误差较小的,整体上来看是彻底能够接受的。
我接着作了一些修改,在-250MS~250MS的浮动范围内是50MS一帧,250MS~750MS范围内为60MS,-750MS~-250MS范围为40MS。简单来讲就是逻辑帧的速率调整变得很是温和。游戏开始后服务器与客户端时钟可能有误差,可是这个误差在平均程度上也会在几秒内被追平。若是LocalFrameId落后或者领先ServerFrameId超过1S,则采用暴力的手段直接拉平便可。
OK,追帧的算是说完了。还有个快照发送的问题,以前作得不完全,今天回来的路上又整理了下,仍是记下来吧。如今时间已经10点33分了。
以前的一个目标是但愿有一个在时间上无限制的竞技场,玩家能够不断地加入进来或者随时退出。若是是状态同步的方案那么就很好处理,客户端直接拉取服务器的战斗状态,重建战场便可。可是帧同步方案下,服务端是不保存状态的。有两种方案。一种是服务器起运算结点跑同一套战斗逻辑,这样服务器就有了状态,但这样也就丧失了帧同步节省服务器资源的优势了;二是由客户端按期向服务器上传状态快照,服务器保存快照以及自快照以后的全部客户端命令,客户端进来后拉取快照和命令演算至当前帧。粗粗一看,彷佛状态快照的上传颇为简单,不过,很快你就会看到事实并不是如此。
快照上传的一个重点就是要保证这个快照是不会再改变的有效快照。ServerFrameId同步到客户端后,在必定的窗口范围内,[ServerFrameId-WindowSize,ServerFrameId+WindowSize],其中的帧可能会发生回滚。好比在收到ServerFrameId为100时又收到另外一个客户端在第90帧的命令,此时客户端要回滚到第90帧从新演算。所以咱们只须要发送ServerFrameId-WindowSize帧的快照给服务器便可。OK,思路上没有问题了,不过事情尚未完。
直接的想法固然是在逻辑帧中直接发送ServerFrameId-WindowSize帧。可是这样可能出现几个问题:
一是LocalFrameId<ServerFrameId-WindowSize,有效快照还没有演算出来,没法发送。
二是一次计时器回调中可能会连续更新多个逻辑帧,此时ServerFrameId是不变的,每一个逻辑帧内部都不加思考地发送ServerFrameId-WindowSize帧会致使重复发送。
三是可能会发生漏帧,好比在LocalFrameId1时由于有效快照ServerFrameId1-WindowSize还没有演算出来,所以没法发送。可是在下一帧以前,ServerFrameId可能会改变为ServerFrameId2,那么在LocalFrameId2时即便ServerFrameId1-WindowSize已经演算出来,可是此时计算要发送的快照为帧ServerFrameId2-WindowSize,出现了漏帧的状况。
其实仔细考虑下,这个问题是有简明的方案的。说来讲去其实就两种状况。
1)由于网络帧可能会一次性大量涌入,也就是说在网络的回调函数中可能会一次性扔出连续多个ServerFrameId。ServerFrameId-WindowSize可能会大于LocalFrameId;
2)可能会出如今一次计时器回调中连续更新多个逻辑帧的状况,此时ServerFrameId是不变的。LocalFrameId+WindowSize可能会>ServerFrameId。
也就是说,在网络回调和逻辑帧中都要考虑是否发送状态快照的问题。虽然也能够解决,不过这样的作法又繁琐又丑陋。能够将ServerFrameId与LocalFrameId的变化扔到一个单独的模块X中,那么问题变转变成在不超过两个数组的各自上限的条件下,尽量计算可发送的元素范围。而X更是能够由本身来定制快照发送的策略。
我记得以前还有更细微的一些地方要注意,不过我懒得再回想或深刻考虑了,对这个问题而言,上面的这些讨论也已经差很少了。命令窗口的问题还要记录下,不过等下次吧。本想休息下的,谁知道竟23点23分了。烦哪。数组