【游戏开发】TPS游戏网络同步总结

2021-03-30
4145
0

  本次Demo是C/S一体化的设计,即服务端也是Unity做的。网络模块采用了UDP KCP,即先前BNB的强化版,而之所以没用UNet是因为之前搞出了乌龙所以换了现在这套,但序列化部分还是用的UNet。

实现思想

  如果你涉及到这方面,那你必须对什么是状态同步有一个大致的了解。市场上的大多数文章都认为它与帧锁定同步相反,但我不认为它们是相反的。这篇文章非常清楚这一点,希望读者不要拘泥于形式。在详细阐述实施思路之前,我们先来看看FPS/TPS游戏的需求:

  • 非常迅速的操作反馈(若采用服务器应答后方有反馈的设计,很难达到要求,尤其是操作镜头) → 本地先行

  • 个人体验第一(对于是否命中敌人与被命中不是很敏感) → 玩家之间看到画面情况不一致 ACT元素低(不存在ACT游戏的打击控制链,不需要帧判定) → 不需要精确到帧的同步

  • 服务器权威(命中判定由服务器决定) → 服务端模拟游戏世界、同步验证 房间战斗(玩家人数不多) → 与MMORPG同步不同

  • 相对同步(玩家之间的时间差不可拉得太大) → 追赶进度

  Well done,由以上几点需求已经得出了TPS游戏同步的实现思想,下文将根据实现思想阐述具体实现细节。

快照

  在探究同步流程之前,首先要了解同步的核心:快照。换言之,也就是我们所同步的内容。快照(Snapshot)通俗来讲就是玩家的操作指令与相关数据的集合,由于需要做同步验证,所以将数据分为必要数据(Must)与验证数据(Check),先来看看移动的快照数据结构吧:

// Actor/Common.cs
public class Move {
    public string fd; // Address:Port(Must)
    public int frame; // Game Frame(Must)
    public bool fromServer; // It is from server, or client?(Must)
    public Vector3 velocity; // Moving Velocity(Must)
    public Vector3 position; // Position before moving(Check)
}

  如上文所示,position为移动前的坐标,这样的数据客户端不需要上传,只需要与服务器发送的快照进行同步验证。

同步流程

  由于服务端模拟游戏世界,所以采用了C/S一体化的设计。在代码层面上则是分为ServerMgrClientMgr两个MonoBehaviour,ServerMgr负责收集客户端的快照并整合下发,而ClientMgr负责发送快照与模拟来自服务端的快照以驱动同步单位的运行。如下图所示:

  图中的同步快照是一个特殊的快照列表。它由服务器的每一帧打包,包括多个客户端的一帧快照。客户机可以通过模拟其他客户机所代表的对象来驱动它们。此同步过程只能确保在客户端生成在同一帧上生成的快照,并将其打包在服务器上的同一个同步快照中。除了不需要精确同步到帧之外,没有任何保证(不考虑快照之间帧间距的执行)。

追赶进度

  在正常的同步过程中情况总是理想的,但是一旦出现网络延迟或卡住的话,在恢复之时便会面临大量的快照,那么按照现有的做法便会导致与其他玩家的时间轴拉得太远(看到的画面是很久以前的了),这便需要设计追赶进度的机制。需要注意的是,追赶进度是服务端与客户端都需要的(服务器也有网络延迟和卡住的可能),客户端的追赶处理相当简单,同步快照超过一个数量则循环模拟:

// ClientMgr.cs
if (this.syncList.Count > 0) {
    this.Simulate();

    // SYNCMAX = 15
    while (this.syncList.Count > SYNCMAX) {
        this.Simulate();
    }
}

本地先行

  本地先行可谓这类同步最玄学之处,不过只要了解其原理倒也无甚。需要本地先行的理由在上文已经阐述,由于是以服务端权威且不那么介意判定的问题,所以是可以允许玩家之间看到画面情况不一致这种情况的。况且在大多数场合下,玩家先行并不会造成什么问题(最终的结果趋于一致),但假设在这么一个场合下:玩家A一直行走,在玩家B的视角里对玩家A进行了眩晕。如此便会造成不同步了,所以需要进行同步验证以将问题修正。

  要实现同步验证的思路倒也朴素:就是用一个验证列表将快照保存,当收到同步快照列表时就进行逐个对照(对比它们的验证数据,见前文),一旦发现不一致之处,就以当前位置开始,循环模拟同步快照,然后再继续循环模拟验证列表里进度比目前快的快照,追上最新进度:

// ServerMgr.cs
var list = new List < Snapshot > (); // sync-snapshot
// Foreach all clients.
foreach(var i in this.unitMap) {
    int frame = -1;
    var sl = i.Value.list;

    // INTERVAL = 10, i.Value.count that is count of frame.
    while (sl.Count > 0 && (i.Value.count > INTERVAL || (frame == -1 || sl[0].frame == frame))) {
        var s = sl[0];
        list.Add(s);
        sl.RemoveAt(0);

        if (frame != s.frame) {
            frame = s.frame;
            i.Value.count--;
        }
    }
}

服务端权威

  从上文可以看出,本地先行会修正的范围只有本地玩家而已,回到之前的例子:在玩家B的视角里对玩家A进行了眩晕,假设这个行为在服务端上并没有达成(玩家A闪现走了),那么该如何修正呢?很显然可以选择搞个更大的修正系统,但我认为这样并不符合业界的常规做法,所以我给出的答案是: 眩晕行为需要在服务端触发了,然后由服务端将其作为快照,以正常同步的形式在诸客户端上展示。

  事实上在网络正常的情况下,这样的间隔最多也只是0.1x秒左右而已,完全可以接受。当然这么做对于玩家B而言肯定会发生修正(眩晕按理来说是之前的事了),所以我对此作了个措施: 为快照设计了fromServer属性,一旦是fromServer = true且属于本地玩家的快照,本地玩家会直接模拟而不会将其进行修正对比。这也可以看出这套同步的一个规则:会影响他人的操作,都需要由服务端发起

后记

  很显然,目前这个demo仍很不成熟,不少地方在业界应该会有更好的处理,如CS的射击纠正(服务端根据客户端的射击时间回滚之前的场景进行判定)。如此只能算是一个雏形,还是缺少实战项目的淬炼,先根据接下来的项目看看效果吧。

好啦,以上就是今天要分享的内容!

 

转载声明:本文来源于网络,不作任何商业用途。

免责声明:本文内部分内容来自网络,所涉绘画作品及文字版权与著作权归原作者,若有侵权或异议请联系我们处理。
收藏

全部评论

您还没登录

暂无留言,赶紧抢占沙发
王氏教育是国内专业的CG数字艺术设计线上线下学习平台,在王氏教育,有原画、插画、漫画、影视、3D模型等培训课程,也有学习资源下载、行业社区交流。CG数字艺术学习,来王氏教育。
绘学霸iOS端二维码

IOS下载

绘学霸安卓端二维码

安卓下载

绘学霸微信小程序二维码

小程序

版权声明
本网站所有产品设计、功能及展示形式,均已受版权或产权保护,任何公司及个人不得以任何方式复制部分或全部,违者将依法追究责任,特此声明。
热线电话
18026259035
咨询时间:9:00~21:00
在线客服
联系网站客服
客服微信:18026259035
公司地址
中国·广州
广州市海珠区晓港中马路130号之19
绘学霸客户端(权限暂无,用于CG资源与教程交流分享)
王氏教育 王氏教育