欢迎参与讨论,转载请注明出处。
前言
因友人的项目要做TPS联机对战游戏,本人遂对此进行了一番研究,经过四回的辗转反侧,Demo总算是做出来了。本次Demo是C/S一体化的设计,即服务端也是Unity做的(可选择1P兼任服务器或者将Unity以命令行模式运行于服务器)。网络模块采用了UDP+KCP,即先前BNB的强化版,而之所以没用UNet是因为之前搞出了乌龙所以换了现在这套,但序列化部分还是用的UNet。以上只是背景交代,本文仅聚焦于网络同步方面的细节。
实现思想
如果你对这方面有所涉猎,想必大致了解何为状态同步。市面上的大多文章将其与帧锁定同步对立而论,但本人认为两者并非是对立的存在,关于这点这篇文章讲的非常清楚,希望读者不要拘泥于形式。在阐述详细的实现思想之前,我们先来看看FPS/TPS游戏的需求:
- 非常迅速的操作反馈(若采用服务器应答后方有反馈的设计,很难达到要求,尤其是操作镜头) → 本地先行
- 个人体验第一(对于是否命中敌人与被命中不是很敏感) → 玩家之间看到画面情况不一致
- ACT元素低(不存在ACT游戏的打击控制链,不需要帧判定) → 不需要精确到帧的同步
- 服务器权威(命中判定由服务器决定) → 服务端模拟游戏世界、同步验证
- 房间战斗(玩家人数不多) → 与MMORPG同步不同
- 相对同步(玩家之间的时间差不可拉得太大) → 追赶进度
Well done,由以上几点需求已经得出了TPS游戏同步的实现思想,下文将根据实现思想阐述具体实现细节。
快照
在探究同步流程之前,首先要了解同步的核心:快照。换言之,也就是我们所同步的内容。快照(Snapshot)通俗来讲就是玩家的操作指令与相关数据的集合,由于需要做同步验证,所以将数据分为必要数据(Must)与验证数据(Check),先来看看移动的快照数据结构吧:
如上文所示,position
为移动前的坐标,像这类数据客户端是不需要上传的,仅用于与服务端传来的快照作对比,以进行同步验证。
同步流程
由于服务端模拟游戏世界,所以采用了C/S一体化的设计。在代码层面上则是分为ServerMgr
与ClientMgr
两个MonoBehaviour
,ServerMgr负责收集客户端的快照并整合下发,而ClientMgr负责发送快照与模拟来自服务端的快照以驱动同步单位的运行。如下图所示:
图中所说的同步快照,是一种特殊的快照列表,它由服务端每帧打包,包括了多个客户端的一帧快照,客户端模拟它们即可驱动其他客户端代表的对象。采用这种同步流程只能保证在客户端是同一帧生成的快照,在服务端也会打包到同一个同步快照里。除此之外都不会保证(不会考虑到快照之间的帧间隔执行情况),即不需要精确到帧的同步。
追赶进度
在正常的同步过程中情况总是理想的,但是一旦出现网络延迟或卡住的话,在恢复之时便会面临大量的快照,那么按照现有的做法便会导致与其他玩家的时间轴拉得太远(看到的画面是很久以前的了),这便需要设计追赶进度的机制。需要注意的是,追赶进度是服务端与客户端都需要的(服务器也有网络延迟和卡住的可能),客户端的追赶处理相当简单,同步快照超过一个数量则循环模拟:
服务端方面则较为复杂,简而言之就是要知道每个客户端快照列表有多少帧(如4个快照,帧号分别为1, 2, 2, 3,则为3帧),当某个每个客户端快照的帧数过高,则循环打包到同步快照列表:
本地先行
本地先行可谓这类同步最玄学之处,不过只要了解其原理倒也无甚。需要本地先行的理由在上文已经阐述,由于是以服务端权威且不那么介意判定的问题,所以是可以允许玩家之间看到画面情况不一致这种情况的。况且在大多数场合下,玩家先行并不会造成什么问题(最终的结果趋于一致),但假设在这么一个场合下:玩家A一直行走,在玩家B的视角里对玩家A进行了眩晕。如此便会造成不同步了,所以需要进行同步验证以将问题修正。
要实现同步验证的思路倒也朴素:就是用一个验证列表将快照保存,当收到同步快照列表时就进行逐个对照(对比它们的验证数据,见前文),一旦发现不一致之处,就以当前位置开始,循环模拟同步快照,然后再继续循环模拟验证列表里进度比目前快的快照,追上最新进度:
服务端权威
从上文可以看出,本地先行会修正的范围只有本地玩家而已,回到之前的例子:在玩家B的视角里对玩家A进行了眩晕,假设这个行为在服务端上并没有达成(玩家A闪现走了),那么该如何修正呢?很显然可以选择搞个更大的修正系统,但我认为这样并不符合业界的常规做法,所以我给出的答案是: 眩晕行为需要在服务端触发了,然后由服务端将其作为快照,以正常同步的形式在诸客户端上展示。事实上在网络正常的情况下,这样的间隔最多也只是0.1x秒左右而已,完全可以接受。当然这么做对于玩家B而言肯定会发生修正(眩晕按理来说是之前的事了),所以我对此作了个措施: 为快照设计了fromServer
属性,一旦是fromServer = true
且属于本地玩家的快照,本地玩家会直接模拟而不会将其进行修正对比。这也可以看出这套同步的一个规则:会影响他人的操作,都需要由服务端发起。
后记
很显然,目前这个demo仍很不成熟,不少地方在业界应该会有更好的处理,如CS的射击纠正(服务端根据客户端的射击时间回滚之前的场景进行判定)。如此只能算是一个雏形,还是缺少实战项目的淬炼,先根据接下来的项目看看效果吧。