游戏中的网络同步机制——Lockstep(帧同步)

本文来自: https://bindog.github.io/blog/2015/03/10/synchronization-in-multiplayer-networked-game-lockstep/#top

值得参考文章:https://blog.codingnow.com/2018/08/lockstep.html

可参考的项目工程:https://github.com/CraneInForest/LockStepSimpleFramework-Shared

0x00 前言

每个人或多或少都接触过网游,那个虚拟的世界给予了我们无穷的乐趣,而这个虚拟世界是如何完美的将身处天南地北的玩家连接在一起的呢?我们每个人的电脑配置都不一样,网络延迟也不同,但是在玩FPS(第一人称射击)游戏时,战斗感受与真实世界并无二致,网游是如何做到这一点的呢?

本文将介绍和分析早期广泛在RTS(即时策略)游戏中应用的同步机制——Lockstep

RTS游戏有很多,比如我们都玩过的的Warcraft III(大家耳熟能详的Dota是它的一张地图)和StarCraft,还有EA的代表作命令与征服系列(Command & Conquer)等等,以及现在非常流行的Dota2LOL

那么为什么要强调早期呢?因为Dota2LOL等新兴的游戏使用的同步机制不再是传统的Lockstep了。严格来说,Warcraft和现在意义上的网游有很大区别,因为它所谓的网是局域网(LAN)。早期RTS游戏出现时互联网还没有现在那么普及,网速也很慢,更没有什么像样的网游,能够支持局域网对战已经很不错了。

有人可能会有疑问,我们平时经常在对战平台上和全国各地的人打Dota,你为什么说Warcraft III只支持局域网呢?这又是一个很有意思的话题,实际上,对战平台使用了虚拟局域网(VLAN)技术,通过进程注入,HOOK WinSock函数调用,将数据包发送到对战平台服务器上,由服务器分配虚拟IP,这里还能够进行天梯匹配等等,在随后的游戏过程中游戏数据包都是通过对战平台的服务器进行转发,但是这一切对Warcraft III进程本身来说是透明的,它依然感觉自己在一个局域网环境中。

0x01 为什么要有同步机制

一致性

在虚拟世界中,保证游戏的一致性是一个基本前提。什么是一致性?通俗的说就是虚拟世界中的事实,比如在一个FPS游戏中,大家的延迟都很高,A、B两个玩家同时发现了对方,并向对方射击,如果没有很好的同步机制,那么A的屏幕上显示B还没有开枪就被击杀,而B的屏幕上显示A还没有开枪就被击杀,这就出现了不一致的问题,那么这个游戏还怎么愉快的进行下去?

可以这么说,延迟是造成不一致问题的主要原因。如果延迟都为0(即A玩家作出行动的同时B玩家就能看到),那么也就不存在不一致的问题了,就像在真实世界中一样。而同步机制除了基本的通信作用外,最重要的任务就是解决不一致问题,即保证游戏的一致性。同步机制有许多种,根据游戏类型、技术条件甚至时代背景的不同,选择的同步机制也会不同。

分类

游戏的网络同步机制有很多,国外也有这方面的论文,抛开具体实现细节,总体来看可以分为下面几类

  • Peer-to-Peer,在这类方法中,没有服务器,游戏参与者的身份是对等的,依靠参与游戏的玩家电脑自行解决同步问题的,最为典型的就是Lockstep
  • Client-Server,在这类方法中,Server端是绝对的权威,所有计算基本在Server上完成。例如,在游戏中向前移动一步,要等待服务器确认“你向前移动了一步”之后,才可以在客户端上进行这个行为。(延迟较低的时候是察觉不到这个过程的,延迟高时会有明显的卡顿现象)
  • Client-Side Prediction,严格来说这并不是一类方法,而是对第二类方法的改进。试想如果所有的操作都必须在得到服务器的确认,然后才在客户端上进行,在延迟较高时用户体验会非常的差。这时可以把常用一部分计算转移到客户端进行,服务器辅助校正即可。

0x02 什么是Lockstep

Lockstep最初是军队行进中使用的,后来在19世纪的时候广泛在美国监狱使用,成为那个时期美国监狱的一个标识。就像这样

或者这样

意思就是大家同步的走,谁超前了要等待,落后了的要赶上。后来就引申到游戏的网络同步机制上了

上一章节中我们说到Lockstep是Peer-to-Peer架构中的一种同步方式,而我们平时在局域网中玩Dota时,也的确没有大型的游戏服务器,只有一台所谓的主机,那么你可能会想,是不是所有的计算都是在那台主机上完成的呢?也就是说其他玩家的机器只发送施放了某某技能这样的数据包给主机,而造成多少伤害、某某效果是由主机计算并返回的。

但事实并不是这样的,玩Dota所有的一切都是在本地计算完成的。包括技能伤害、效果,命中与否,随机刷新野怪等等。也就是说每个玩家的电脑都完整计算了整盘游戏的全过程,且计算过程与计算结果都一模一样。而主机只是负责把每个玩家的操作指令(鼠标点击、键盘按键等等)广播给其他玩家。

是不是感到难以理解?

想要理解Lockstep的机制,先看看下面三个问题。

  • 什么是动画?是会动的画吗?当然不是,人眼的记忆时间为0.1s,也就是大约100ms,只要把一帧一帧的静态图像快速播放一遍,我们就会感觉画面就动了起来,比如下面这样

  • 最容易实现同步的游戏类型是什么?当然是回合制游戏,比如棋类游戏和卡牌游戏,它们有严格的先后顺序,不容易出现逻辑错误,更不会出现不一致的情况;而且回合时间较长,能够容忍高延迟。
  • 什么是状态机?状态机是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型。给定一个状态机模型F,处在某一个状态S1,这时给定一个输入I,此时状态机会转移到一个新的状态S2。在这个过程中,只要FS1I是确定的,那么S2就是确定的。

其实把以上三点结合起来,就是Lockstep的基本思路

我们来看下面这张图

图中是A、B、C三个玩家的时间轴,这个时间轴不是电脑上的本地时间,而是A、B、C联机时定义的一个时间轴。虚线分隔出来时间片称为turn,可以理解成一个回合。箭头表示该玩家将自己的操作指令广播给其他玩家。我们把一盘游戏看成一个大型的状态机,因为大家玩的是同一款的游戏,因此F是相同的,初始状态S0也是相同的。在第一个turn结束时,所有玩家都接收到了完全一样的输入I,注意这里的I不是一个值,而是包含了当前游戏中所有玩家的操作指令集合。t1时刻所有玩家的电脑自行计算结果。由于FS0I是固定的,所以每个玩家电脑上计算出的下一个状态S1一定是相同的。

同理,第二个turn也是如此

可以看出,Lockstep其实也是“回合制”的,当然这个所谓的回合与我们理解的棋类、卡牌游戏的回合是不太一样的。Lockstep的回合(也就是turn)中,所有玩家都可以采取行动,最终结果是在回合结束时统一计算的。在同一个turn接收到的操作指令,是不分行动先后顺序的,只要是在同一个turn里,就认为是同时发生的。

举个例子,假设A、B、C是游戏中3个互相敌对的单位,攻击力都为100。在某一个turn内,A和B都右键点击了C(warcraft这类游戏好像都是右键普攻),C右键点击了A,这些操作指令都广播到了其他玩家电脑上,则该turn的输入为“A攻击C、B攻击C、C攻击A”。那么该turn结束后,每个人的电脑都开始计算,且计算结果是相同的,即“A损失100生命值,B不变,C损失200生命值”。

这就是Lockstep同步机制,其实也没有多复杂是吧~这里还有几点需要注意:

  • Lockstep把游戏过程划分成了一个个turn,为什么游戏不会出现卡顿的现象呢?回到前面动画的那个问题,人眼是容易被欺骗的,人的反应其实也是很慢的(相对电脑来说)。人眼的记忆时间为0.1s,只要每秒进行10turn,我们是感觉不出卡顿的。而通常游戏的帧数为60fps,即每秒60幅图像在屏幕上显示,你会感觉游戏非常流畅~当然“帧”和Lockstep中的“turn”并不是一一对应的,这里只是想说明一个turn的时间是非常短的,至少比我们的反应要快的多。
  • Lockstep对网络延迟的要求是非常高的,因为每个turn要向所有玩家广播操作,同时也要接收来自其他玩家的操作。只有当每个turn集齐了所有玩家的操作指令,也就是输入确定了之后,才可以进行计算,进入下一个turn,否则就要等待最慢的玩家。当然局域网可以很好的满足这个要求,延迟基本都在1ms左右。
  • Lockstep中会不会出现延迟导致的不一致问题?显然不会,从上面的分析可以知道,使用Lockstep的游戏是严格按照turn向前推进的,如果有人延迟比较高,其他玩家必须等待该玩家跟上之后再继续计算,不存在某个玩家领先或落后其他玩家若干个turn的情况。使用Lockstep同步机制的游戏中,每个玩家的延迟都等于延迟最高的那个人。(当然这个说法还有待讨论,后面还会说到这个问题)
  • Lockstep是非常严格的,要求每一步的的计算结果都完全一样,任何计算错误都有可能导致蝴蝶效应,产生严重的后果,因为状态不能同步的话游戏根本就没有办法进行下去,最终将崩溃退出。
  • 《Algorithms and Networking for Computer Games》这本书中关于Lockstep的部分与本文的说法有些不同,我觉得也没有谁对谁错之分,Lockstep更多的是一种思想,算是不一样的理解吧,感兴趣的同学也可以看看书中的章节。书中将Lockstep分为commit和reveal两个阶段,并且采用了流水线的方式。如下图所示

0x03 与Lockstep无关的细节问题

为什么要讨论与Lockstep无关的问题呢?因为这些问题虽然与Lockstep无关,但却与游戏本身有关,而且很多问题是由Lockstep引起的

野怪刷新与暴击

我们都知道,Dota中有许多问题是与概率相关的,比如整点时野怪是随机刷新的,出了水晶剑之后是有概率暴击的。那么按照Lockstep同步机制,计算都是在每个玩家自己电脑上完成的,那么在有概率存在的情况下,怎么可能保证每台电脑的计算结果一致呢?!

这时就轮到伪随机数派上用场了,我们来看下面这个Java程序

import java.util.Random;
public class PseudoRandom {
    public static void main(String[] args) {
        Random r1 = new Random(10);//这里的10就是随机种子(Random Seed)
        for (int i = 0; i < 10; i++) {
            System.out.print(r1.nextInt(100) + "\t");
        }
        System.out.println();
        Random r2 = new Random(10);
        for (int i = 0; i < 10; i++) {
            System.out.print(r2.nextInt(100) + "\t");
        }
    }
}

//Output
//13	80	93	90	46	56	97	88	81	14
//13	80	93	90	46	56	97	88	81	14	

如果你的jdk版本没有什么问题的话,那么你看到的结果一定也是上面那样,而且无论你运行多少次都是这个结果。大部分编程语言内置库里的随机数都是利用线性同余发生器产生的,如果不指定随机种子(Random Seed),默认以当前系统时间戳作为随机种子。一旦指定了随机种子,那么产生的随机数序列就是确定的。

所以,游戏开始前,参与游戏的玩家电脑协商确定一个随机种子,就可以保证在游戏进行过程中大家产生的随机数序列是相同的,也就可以保证计算结果一致。

例如一个英雄的暴击率为30%,对某个目标持续普攻,如果随机数序列为12 32 90 25,小于等于30判定暴击,大于30判定不暴击,那么每个玩家电脑的计算结果都是暴击 不暴击 不暴击 暴击。(这里只是举例说明原理,游戏中的实现细节不一定是这样)

外挂问题

在对战平台打Dota的人都见识过全图挂,而且屡禁不止,那么为什么Warcraft III中会有全图挂,而没有其他游戏中的变态挂(如加强攻击力,瞬间移动等等)呢?为什么无法从根本上杜绝全图挂呢?

前面已经说过了,使用Lockstep同步机制,所有计算都是在本地完成的,每轮turn都必须接收来自其他所有玩家的操作指令才能完成计算,也就是说其他玩家的一举一动你的电脑其实是知道的,只不过没有在你的屏幕上展现出来罢了(被战争迷雾遮挡了)。因此,全图挂只要修改Warcraft III的内存数据,就可以去除战争迷雾,达到开全图的效果。而对战平台不可能改变Lockstep这种同步机制,只能在本地检测是否有其他程序修改Warcraft III的内存数据(就像病毒查杀一样),而外挂程序总有办法绕过检测,所以全图挂总是层出不穷。

而另一方面,恰恰因为Lockstep同步机制,其他比较变态的外挂根本不可能在Warcraft III上存在。比如强化攻击力,我们同样可以利用修改内存的方式将增加自己的攻击力,可以直接秒杀其他任何单位,但是别忘了其他玩家电脑也在同步的进行计算,而我们的攻击力在其他玩家电脑上仍然是不变的,这就造成了状态不一致。前面说过了,Lockstep中出现状态不同步的情况时很容易产生蝴蝶效应,最终崩溃退出。

断线重连

这个问题一直被广大玩家诟病,因为其他网游从来没有断线之后连不回去的情况,为什么Warcraft III不支持断线重连呢?

Lockstep同步机制是非常严格的,中途加入游戏是从技术上来讲是非常困难的。首先中途加入的玩家要进行状态同步,而状态同步本身包含的内容太多了,其中包括时间戳、伪随机数序列、所有单位的位置属性信息等等,不是简单的复制粘贴就能同步的。这说起来容易,要具体实现起来没那么简单,而且在局域网中掉线情况并不常见,因此早期暴雪的开发人员可能也就忽略这个问题了。

事实上使用对战平台的人才会经常遇到这个问题,在互联网中,延迟高、掉线的情况时有发生。那么11平台的掉线重连功能是怎么来的呢?个人观点,那并不是真的掉线重连,只是我们的丢包情况比较严重,暂时卡了而已。11平台可能做了一些优化,将其他玩家的操作指令保存起来,等延迟稳定的时候再发送给我们。但由于此时我们电脑所处的turn可能落后了其他玩家,所以此时游戏就会像快放一样赶上其他玩家的进度。如果是真的掉线,如停电、程序崩溃等直接退出的情况,我们是无法重新加入游戏的。

Warcraft III是不是使用严格的Lockstep机制?

接上一个问题,如果Warcraft III是严格的Lockstep同步机制,那么一定会出现一人卡、大家都卡的情况,而事实上只有当我们是主机时才会出现这种情况,其他情况下我们延迟高甚至掉线都不影响其他玩家的操作。

因此,Lockstep实际是一种理想的模型,如果在实际中使用会造成非常差的用户体验。那么Warcraft III使用的到底是什么同步机制呢?参考What every programmer needs to know about game networking这篇文章后面一个评论的说法

TOADCOP

FEBRUARY 10, 2010 AT 9:15 AM

btw afaik StarCraft don’t use peer-to-peer it uses client-server model with lockstep (at least warcraft 3 does so). It has the advantage what theoreticaly laggers will not affect gameplay/response latency at all (but to not let them fall behind server do timeouts so the lagger can catch up, also doing temporary local game speed increasing) and imo it’s the only and true way to do sync in RTS like games. (and for some reasons this technic isn’t good covered in the web)

然后是作者的回复

GLENN FIEDLER

FEBRUARY 10, 2010 AT 10:20 AM

You are correct. Also something cool is that in a C/S RTS model the server could also theoretically arbitrate to ignore turns from lagging players, and kick them if they don’t catch up – removing various exploits where you can time-shift your packets and lag out other players.

也就是说Warcraft III使用的是基于Client-Server的Lockstep模型。这就是为什么Warcraft III中有主机这个概念,当然这里主机的作用并不是完成所有计算。

(以下为个人观点)

Warcraft III中的主机的主要功能是广播并设置timeout,也就是说在每个turn内,游戏玩家并非直接将自己的操作指令广播给其他玩家,而是先发送给主机,由主机负责广播,且每个turn都有timeout,如果超过了timeout仍然没有收到某个掉线玩家的操作指令,则忽略该玩家在该turn的行为,即认定他什么都没有做,并与其他延迟正常的玩家同步进入下一个turn。而当掉线玩家网络恢复时,主机会将之前保存的turn中操作指令集合发送给该名玩家,而该名玩家为了赶上进度,就会出现游戏快放的情况。

所以Warcraft III中只有在主机延迟高或掉线时,其他玩家才会受影响,否则不受影响。在局域网中,如果主机是正常退出的,那么会选定另一玩家电脑作为主机,如果是崩溃退出的,则所有人都会直接掉线。至于在对战平台上是否有优化就不太清楚了。

原文地址:https://www.cnblogs.com/sanyejun/p/10795186.html

时间: 2024-10-10 13:08:40

游戏中的网络同步机制——Lockstep(帧同步)的相关文章

位同步(比特同步)和帧同步的区别是什么?

在数据通信中最基本的同步方式就是“位同步”(bit synchronization)或比特同步.比特是数据传输的最小单位.位同步(比特同步)是指接收端时钟已经调整到和发送端时钟完全一样,因此接收端收到比特流后,就能够在每一位的中间位置进行判决(如下图所示).位同步(比特同步)的目的是为了将发送端发送的每一个比特都正确地接收下来.这就要在正确的时刻(通常就是在每一位的中间位置)对收到的电平根据事先已约定好的规则进行判决.例如,电平若超过一定数值则为1,否则为0. 但仅仅有位同步还不够.因为数据要以

什么是游戏中的帧同步

游戏中的帧同步是一种客户端与服务器的同步方式,是为了实现高实时性的需求而设计的.在实时pvp游戏中,要求每个客户端高度同步,怎么做到精确的同步呢,那就是向同步的所有客户端广播同步消息.由于网络存在延迟,因此一个客户端发送消息给服务器的过程存在延迟,服务器广播同步消息给其他客户端也存在延迟,为了降低这个延迟,服务器应该尽量减少逻辑,快速地转发消息让客户端能够同步,因此在帧同步中,游戏的逻辑计算应该放到客户端来实现,服务器在收到消息后应该迅速地进行广播,而不应该做过多的逻辑计算.既然是客户端进行计算

Unity3D RTS游戏中帧同步实现

帧同步技术是早期RTS游戏常用的一种同步技术,本篇文章要给大家介绍的是RTX游戏中帧同步实现,帧同步是一种前后端数据同步的方式,一般应用于对实时性要求很高的网络游戏,想要了解更多帧同步的知识,继续往下看. 一.背景 帧同步技术是早期RTS游戏常用的一种同步技术.与状态同步不同的是,帧同步只同步操作,其大部分游戏逻辑都在客户端上实现,服务器主要负责广播和验证操作,有着逻辑直观易实现.数据量少.可重播等优点. 部分PC游戏如帝国时代.魔兽争霸3.星际争霸等,Host(服务器或某客户端)只当接收到所有

Unity 3D实现帧同步技术

笔者介绍:姜雪伟,IT公司技术合伙人,IT高级讲师,CSDN社区专家,特邀编辑,畅销书作者,国家专利发明人;已出版书籍:<手把手教你架构3D游戏引擎>电子工业出版社和<Unity3D实战核心技术详解>电子工业出版社等. CSDN视频网址:http://edu.csdn.net/lecturer/144 现在竞技类网络游戏比较火,市面上也出现了很多这种类型的游戏竞赛,提到网络游戏就回避不了一个问题:同步技术,多个人在一个游戏场景围攻一个怪物或者说多人组队战斗等等.现在在移动端的游戏由

Linux内核同步机制

http://blog.csdn.net/bullbat/article/details/7376424 Linux内核同步控制方法有很多,信号量.锁.原子量.RCU等等,不同的实现方法应用于不同的环境来提高操作系统效率.首先,看看我们最熟悉的两种机制——信号量.锁. 一.信号量 首先还是看看内核中是怎么实现的,内核中用struct semaphore数据结构表示信号量(<linux/semphone.h>中): [cpp] view plaincopyprint? struct semaph

帧同步和状态同步

帧同步 说白了就是在客服端跑一个服务器,每个服务器基于相同的帧率,同步在相同帧的输入,达到一致性. 状态同步 服务器运算,把状态的改变发给客服端 首先要说 帧同步不是说不能加入状态同步的东西,状态服务就不能有帧同步类似的东西,所以他们的优点只基于 帧同步 服务器和客服端在一块,只发输入 状态同步 服务运算,发状态 网络: 帧同步 的网络负担肯定更小 但是延迟的问题大家都有,解决办法点也不一样.帧同步解决同步问题就是好了,状态解决的办法和思路比较多,比如客服端预判,部分运行客服也做一次等.总的来说

分析.Net里线程同步机制

我们知道并行编程模型两种:一种是基于消息式的,第二种是基于共享内存式的. 前段时间项目中遇到了第二种 使用多线程开发并行程序共享资源的问题 ,今天以实际案例出发对.net里的共享内存式的线程同步机制做个总结,由于某些类库的应用属于基础,所以本次不对基本使用做出讲解,基本使用 MSDN是最好的教程. 一.volatile关键字      基本介绍: 封装了 Thread.VolatileWrite() 和  Thread.VolatileRead()的实现 ,主要作用是强制刷新高速缓存.     

Linux 内核的同步机制,第 1 部分 + 第二部分(转)

http://blog.csdn.net/jk198310/article/details/9264721  原文地址: Linux 内核的同步机制,第 1 部分 一. 引言 在现代操作系统里,同一时间可能有多个内核执行流在执行,因此内核其实象多进程多线程编程一样也需要一些同步机制来同步各执行单元对共享数据的访问.尤其是在多处理器系统上,更需要一些同步机制来同步不同处理器上的执行单元对共享的数据的访问.在主流的Linux内核中包含了几乎所有现代的操作系统具有的同步机制,这些同步机制包括:原子操作

游戏中的弹道学手册(转)

[1]直线单体必中飞弹 代表单位:<帝国时代1>的弓箭手,<海岛奇兵>的步兵 直线飞行,只会击中被攻击的单位,命中率100%.看起来飞弹会穿过一些单位,但其实只会击中被锁定为攻击目标的单位.如果飞弹在飞行的过程中目标单位已经被摧毁,则飞弹会继续飞行一段距离之后消失. 虽然理论上来说弓箭手的弹道应该是抛物线,但<帝国时代1>中的弓箭手是直线射击的,我们就不要对老游戏太苛求啦! [2]直线单体真实碰撞飞弹 代表单位:<合金弹头>的小手cc枪.H枪,绝大多数射击游