这是【游戏开发那些事】第56篇原创
两周前,在Epic举办的UnrealCircle会议上,我受邀分享了一场关于“UE4回放系统”的技术演讲。不过由于时长限制,很多细节都没有得到进一步的阐述。
这篇文章会在演讲的基础上拓展更多内容,更好的帮助大家去理解虚幻引擎的回放系统,建议大家结合源码进行阅读和学习。
目录(上篇)
-
一、帧同步、快照同步、状态同步
-
二、UE4网络同步基础
-
三、回放系统框架与原理
-
3.1 回放系统核心实现思路
-
3.2 回放系统简单使用
-
3.3 UE4回放系统架构
-
3.3.1 数据的存储和读取
-
3.3.2 数据的组织和存储
-
3.3.3 小结
-
-
回放,是电子游戏中一项常见的功能,用于记录整个比赛过程或者展示游戏中的精彩瞬间。通过回放,我们可以观摩高手之间的对决,享受游戏中的精彩瞬间,甚至还可以拿到敌方玩家的比赛录像进行分析和学习。
>>彩虹6号中的击杀回放
早在20世纪90年代,回放系统就已经诞生并广泛用于即时战略、第一人称射击以及体育竞技等类型的游戏当中。
在上一篇文章“游戏中的回放系统是如何实现的?”里,我们简单讲解了实现回放系统的三种思路:
-
逐帧录制游戏画
-
逐帧录制玩家的输入操作
-
定时录制玩家以及游戏场景对象的状态
总的来说,由于第一种录制画面的方案存在着“占用大量存储空间”、”加载速度慢”、“不够灵活”等比较严重的问题,我们通常采用后两种方式来实现游戏中的回放。
帧同步、快照同步与状态同步
虽然不同游戏里回放系统具体的实现方式与应用场景不同,但本质上都是对数据的记录和重现,这个过程与网络游戏里面的同步技术非常相似。举个例子,假如AB两个客户端进行P2P的连接对战,A客户端上开始时并没有关于B的任何信息。当建立连接后,B开始把自己的相关信息(坐标,模型,大小)发给A,A在自己的客户端上利用这个信息重新构建了B,完成了数据的同步。
思考一下,假如B不把这个信息发给A,而发给自己进行处理,是不是就相当于录制了自己的机器上的比赛信息再进行回放呢?
>>AB两个客户端进行联机对战
没错,网络游戏中的同步信息正是回放系统中的录制信息,因此网络同步就是实现回放系统的技术基础!
在正式介绍回放系统前,不妨先概括地介绍一下游戏开发中的网络同步技术。我们常说网络同步可以简单分为帧同步、快照同步和状态同步,但实际上这几个中文概念是国内开发者不断摸索和自创的名词,并非严格指某种固定的算法,他们有很多变种,甚至可以结合到一起去使用。
-
帧同步,对应的英文概念是LockStep/ Deterministic Lockstep。其基本思路是每固定间隔(如0.02秒)对玩家的行为进行一次采样得到一个“Input指令” 并发送给其他所有玩家,每个玩家都缓存来自其他所有玩家的“Input指令” ,当某个玩家收到所有其他玩家的“Input指令”后,他的本地游戏状态才会推进到下一帧。
-
快照同步,可以翻译成Snapshot Synchronization。其思想是服务器把当前这帧整个游戏世界的状态进行一个备份,然后把这个备份发送给所有客户端,客户端按照这个备份对自己的世界状态进行修改和纠正进而完成同步。(快照,对应的英文概念是SnapShot,强调的是某一时刻的数据状态或者备份。从游戏世界的角度理解,快照就是整个世界所有的状态信息,包括对象的数量、对象的属性、位置线信息等。从每个对象的角度理解,快照就是指整个对象的各种属性,比如生命值、速度这些。所以,不同场景下快照所指的内容可能是不同的。)
-
状态同步,可以翻译成State(State Based) Synchronization。其思想与快照同步相似,也是服务器将世界的状态同步给客户端。但不同的是状态同步的粒度变得非常小(以对象或者对象的属性为单位),服务器不需要把一帧里面所有的对象状态进行保存和同步,只需要把客户端需要的那些对象以及需要的属性进行保存和发送即可。
拓展:快照同步其实是状态同步的前身,那时候整个游戏需要记录的数据量还不是很大,人们也自然的使用快照来代表整个世界在某一时刻的状态,通过定时地同步整个世界的快照就可以做到完美的网络同步。但是这种直接把整个世界的状态进行同步的过程是很耗费流量和性能的,考虑到对象的数据是逐步产生变化的,我们可以只记录发生变化的那些数据,所以就有了基于delta的快照同步。
更进一步的,我们可以把整个世界拆分一下,每一帧只针对需要的对象进行delta的同步,这样就完全将各个对象的同步拆分开来,再结合一些过滤可以进一步减少没必要的数据同步,最后形成了状态同步的方案。更多关于网络同步技术的发展和细节可以参考我的文章——《细谈网络同步在游戏历史中的发展变化》。
UE4网络同步基础
在虚幻引擎里面,默认实现的是一套相对完善的状态同步方案,场景里面的每个对象都称为一个Actor,每个Actor都可以单独设置是否进行同步(Actor身上还可以挂N个组件,也可以进行同步),Actor某一时刻的标记Replicated属性就是所谓的状态信息。服务器在每帧Tick的时候,会去判断哪些Actor应该同步给哪些客户端,哪些属性需要进行同步,然后统一序列化成二进制(可以理解为一个当前世界