欢迎参与讨论,转载请注明出处。
本文转载自https://musoucrow.github.io/2018/12/02/tps_sync/

前言

因友人的项目要做TPS联机对战游戏,本人遂对此进行了一番研究,经过四回的辗转反侧,Demo总算是做出来了。本次Demo是C/S一体化的设计,即服务端也是Unity做的(可选择1P兼任服务器或者将Unity以命令行模式运行于服务器)。网络模块采用了UDP+KCP,即先前BNB的强化版,而之所以没用UNet是因为之前搞出了乌龙所以换了现在这套,但序列化部分还是用的UNet。以上只是背景交代,本文仅聚焦于网络同步方面的细节。

实现思想

如果你对这方面有所涉猎,想必大致了解何为状态同步。市面上的大多文章将其与帧锁定同步对立而论,但本人认为两者并非是对立的存在,关于这点这篇文章讲的非常清楚,希望读者不要拘泥于形式。在阐述详细的实现思想之前,我们先来看看FPS/TPS游戏的需求:

  • 非常迅速的操作反馈(若采用服务器应答后方有反馈的设计,很难达到要求,尤其是操作镜头) → 本地先行
  • 个人体验第一(对于是否命中敌人与被命中不是很敏感) → 玩家之间看到画面情况不一致
  • ACT元素低(不存在ACT游戏的打击控制链,不需要帧判定) → 不需要精确到帧的同步
  • 服务器权威(命中判定由服务器决定) → 服务端模拟游戏世界、同步验证
  • 房间战斗(玩家人数不多) → 与MMORPG同步不同
  • 相对同步(玩家之间的时间差不可拉得太大) → 追赶进度

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

快照

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

// Actor/Common.cspublic 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.csif (this.syncList.Count > 0) {this.Simulate();// SYNCMAX = 15while(this.syncList.Count > SYNCMAX) {this.Simulate();}
}

服务端方面则较为复杂,简而言之就是要知道每个客户端快照列表有多少帧(如4个快照,帧号分别为1, 2, 2, 3,则为3帧),当某个每个客户端快照的帧数过高,则循环打包到同步快照列表:

// ServerMgr.csvar 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--;}}
}

本地先行

  本地先行可谓这类同步最玄学之处,不过只要了解其原理倒也无甚。需要本地先行的理由在上文已经阐述,由于是以服务端权威且不那么介意判定的问题,所以是可以允许玩家之间看到画面情况不一致这种情况的。况且在大多数场合下,玩家先行并不会造成什么问题(最终的结果趋于一致),但假设在这么一个场合下:玩家A一直行走,在玩家B的视角里对玩家A进行了眩晕。如此便会造成不同步了,所以需要进行同步验证以将问题修正。
  要实现同步验证的思路倒也朴素:就是用一个验证列表将快照保存,当收到同步快照列表时就进行逐个对照(对比它们的验证数据,见前文),一旦发现不一致之处,就以当前位置开始,循环模拟同步快照,然后再继续循环模拟验证列表里进度比目前快的快照,追上最新进度:

// ClientMgr.cs// Compare sync list and check list.
for (int i = 0; i < list.Count; i++) {if (!list[i].Equals(this.checkList[i])) {index = i;print(i);break;}
}if (index == list.Count) { // Agreementthis.checkList.RemoveRange(0, list.Count);
}
else { // Need to fix.var frame = list[list.Count - 1].frame;// Remove useless snapshots.for (int i = this.checkList.Count - 1; i >= 0; i--) {if (this.checkList[i].frame <= frame) {this.checkList.RemoveAt(i);}}// Loop simulate.ClientMgr.Resolve(this.fd, list, index);ClientMgr.Resolve(this.fd, this.checkList, 0);
}

服务端权威

  从上文可以看出,本地先行会修正的范围只有本地玩家而已,回到之前的例子:在玩家B的视角里对玩家A进行了眩晕,假设这个行为在服务端上并没有达成(玩家A闪现走了),那么该如何修正呢?很显然可以选择搞个更大的修正系统,但我认为这样并不符合业界的常规做法,所以我给出的答案是: 眩晕行为需要在服务端触发了,然后由服务端将其作为快照,以正常同步的形式在诸客户端上展示。事实上在网络正常的情况下,这样的间隔最多也只是0.1x秒左右而已,完全可以接受。当然这么做对于玩家B而言肯定会发生修正(眩晕按理来说是之前的事了),所以我对此作了个措施: 为快照设计了fromServer属性,一旦是fromServer = true且属于本地玩家的快照,本地玩家会直接模拟而不会将其进行修正对比。这也可以看出这套同步的一个规则:会影响他人的操作,都需要由服务端发起

后记

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

TPS游戏网络同步总结相关推荐

  1. 游戏网络同步:帧同步和状态同步

    游戏网络同步机制分为帧同步和状态同步. 帧同步是将客户端的操作通过服务端转发给所有客户端,其他玩家同步当前其他玩家的操作,具体执行逻辑写在客户端,再根据处理逻辑后的状态显示,以迁移一致性为主.具体游戏 ...

  2. 浅谈RTS游戏网络同步:3种同步机制模式的实现

    RTS游戏有很多,可能大家比较熟悉的有Warcraft III (dota)和 StarCraft,早期西木的沙丘,红色警戒更是rts游戏的鼻祖,带给我们无限的欢乐和回忆.还有当下比较流行lol与do ...

  3. 游戏网络同步——MMO位置同步

    前提 1.client和server之间或多或少存在网络延迟,需要提前做好对时,并在网络环境发生变化时校正时差.国内的公网通信,非跨网的情况下,一般在120ms左右.本地的网络会好一些. 2. 要努力 ...

  4. 网络同步在游戏历史中的发展变化(二)—— Lockstep与帧同步

    前言: 网络同步属于游戏开发中比较重要且复杂的一部分,但是由于网上的资料内容参差不齐,很多人直接拿别人的结论写文章,导致很多人对这一块的很多概念和理解都是错误的.本文参考了大量的相关论文和资料(三十篇 ...

  5. 游戏中的网络同步机制——Lockstep(转载)

    原文转自http://bindog.github.io/blog/2015/03/10/synchronization-in-multiplayer-networked-game-lockstep 0 ...

  6. 网络同步在游戏历史中的发展变化(三)—— 状态同步的发展历程与基本原理(上)...

    前言: 网络同步属于游戏开发中比较重要且复杂的一部分,但是由于网上的资料内容参差不齐,很多人直接拿别人的结论写文章,导致很多人对这一块的很多概念和理解都是错误的.本文参考了大量的相关论文和资料(花了半 ...

  7. 细谈网络同步在游戏历史中的发展变化(中)

    非常不好意思让大家久等了,上一篇文章细谈网络同步在游戏历史中的发展变化(上)我们讨论了网络同步的基本概念以及锁步同步(帧同步)的发展历史,这篇我们继续讲述状态同步的发展历程与基本原理.本文作者依旧是网 ...

  8. 游戏中的网络同步机制(一)帧同步Lockstep

    转载自:https://www.jianshu.com/p/64b3f162dcf4 参考游戏中的网络同步机制--Lockstep 一.前言 每个人或多或少都接触过网游,那个虚拟的世界给予了我们无穷的 ...

  9. 【游戏开发】多人游戏网络同步相关技术(基础原理篇)

    常见网络同步模型 1.C/S 模型 (Client-Server) : 状态同步 2.对等网络模型(Peer-To-Peer): 帧同步 网络同步数据类型 将数据分为四个类型 非保障数据(可丢弃) 保 ...

最新文章

  1. Git教程:最详细、最傻瓜、最浅显、真正手把手教!
  2. mysql版本 时间_【MySQL】MySQL版本时间线和MySQL各版本的区别
  3. php ci url,URL路由设置-CI(codeigniter)PHP框架再探
  4. 快递员要失业?两位前谷歌工程师研发出自动驾驶汽车只送货不载人
  5. Qt文档阅读笔记-QThread::setPriority(Priority priority)官方解析及实例
  6. c语言学习-利用函数指针的方法,求任意给出两个整数的x和y的和、差。
  7. (转)C# Enum,Int,String的互相转换 枚举转换
  8. FMS集群的安装和配置
  9. 一个简单的 javascript 中的正则表达式例子
  10. Replica set 的选举策略之一 (转)
  11. 微信开发,调用js-SDK接口
  12. EVEREST - 测试软硬件系统信息的工具
  13. linux 内核态 抓屏代码,Android screencap截屏指令
  14. 2019年7款3D扫描仪APP(Android和iOS),让你手机秒变3D扫描仪!
  15. matlab 报童 泊松分布函数,数学建模和工科数学分析(2)
  16. 自动量程万用表的实现原理_自动量程万用表各个按钮的含义?
  17. 合天网安就业班_【合天网安实验室】SQL注入入门一
  18. 计算机提示应用程序无法启动,告诉你电脑提示应用程序无法正常启动0x000007b怎么办...
  19. Linux capability初探
  20. JAVA基础(注释,关键字与标识符)

热门文章

  1. ORACLE如何删除重复数据
  2. 云数据库没有公网ip如何访问,navicate ssh隧道 + com.jcraft.jsch
  3. 外行人看信息安全(占座贴)
  4. 芋道平台工程名、包名修改工具
  5. Java文本框设置灰色_如何在JTextField中显示灰色的“幽灵文本”?
  6. 【C语言小题】用C语言实现sign符号函数
  7. 【微信每日早安推送】
  8. 某211高校食堂饭菜价格贵得离谱惹争议!学生:吃不起饭了!
  9. 嵌套循环 顺序效率比较
  10. 教你用OpenCV和Python实现手掌检测和手掌计数