帧同步技术是早期RTS游戏常用的一种同步技术,本篇文章要给大家介绍的是RTX游戏中帧同步实现,帧同步是一种前后端数据同步的方式,一般应用于对实时性要求很高的网络游戏,想要了解更多帧同步的知识,继续往下看。
一.背景
帧同步技术是早期RTS游戏常用的一种同步技术。与状态同步不同的是,帧同步只同步操作,其大部分游戏逻辑都在客户端上实现,服务器主要负责广播和验证操作,有着逻辑直观易实现、数据量少、可重播等优点。
部分PC游戏如帝国时代、魔兽争霸3、星际争霸等,Host(服务器或某客户端)只当接收到所有客户端在某帧输入数据后,才会继续执行,等待直至超时认为该客户端掉线。很明显,当部分客户端因网络或设备问题无法及时上传操作数据,会影响其它客户端的表现,造成不好的游戏体验。考虑到游戏公平竞争性,这种需要等待的机制是必需的,但并不符合手游网络环境的需求。为此,需要使用“乐观”模式,即是Host采集客户端上传操作并按固定频率广播已接收到的操作数据,不在乎部分客户端的操作数据是否上传成功,且不会影响到其它客户端的游戏表现,如图1所示。
(图1)
二.剖析Unity3D
帧同步技术最基础的核心概念就是相同输入,经过相同计算过程,得出相同计算结果。按照该概念,下面将简单描述Unity3D实现帧同步时所需要改造的一些方面,Unity3D中脚本生命周期流程图如图2所示。
(图2)
帧同步需要避免使用本地计时器相关数值。因此,使用Unity3D实现帧同步的过程所需注意的几点:
1. 禁用Time类相关属性及函数,如Time.deltaTime等。而使用帧时间(第N帧 X 固定频率)
2. 禁用Invoke()等函数
3. 避免在Awake()、Start()、Update()、LateUpdate()、OnDestroy()等函数中实现影响游戏逻辑判断的代码
4. 避免使用Unity3D自带物理引擎
5. 避免使用协程Coroutine
三.具体实现
对于本文的实现,有如下定义:
关键帧:服务器按固定频率广播的操作数据帧,使用唯一ID标识,主要包括客户端输入数据或服务器发送的关键信息(例如游戏开始或结束等消息)
填充帧:由于设备性能和网络延迟等原因,服务器广播频率不可能达到客户端的更新频率。若只使用关键帧来驱动游戏运作,就会造成游戏卡顿,影响体验。因此,除关键帧外,客户端需要自行添加若干空数据帧,以使游戏表现更为流畅
逻辑帧更新时间:客户端执行一帧所需时间,可根据设备性能和网络环境等因素动态变化
服务器帧更新时间:服务器广播帧数据的固定频率,一般用于帧间隔时间差的逻辑计算
3.1 主循环
帧同步要求相同的计算过程,这就涉及到两个方面,其一是顺序一致,Unity3D主循环不可控,需自定义游戏循环,统一管理游戏对象以及脚本的执行,确保所有对象更新与逻辑执行顺序完全一致。另一方面是结果一致,凡有浮点数参与的逻辑计算需要特殊处理。
class MainLoopManager : MonoBehaviour { bool m_start; int m_logicFrameDelta;//逻辑帧更新时间 int m_logicFrameAdd;//累积时间 void Loop() { ......//遍历所有脚本 } void Update() { if (!m_start) return; if (m_logicFrameAdd <