Unity3D中实现帧同步 (二):跟踪平均数
概览
在上次实现的帧同步模型中,游戏帧率和通信频率(即帧同步长度)采用固定间隔。然而,实际情况中每个玩家的延迟和性能存在差异。在 update 方法里,我们会跟踪两个关键变量:一是玩家通信的时长,二是游戏的性能时长。
移动平均数
为应对延迟的波动,我们期望在延迟增加时能快速延长帧同步回合的时长,而在低延迟时缩短该时长。若游戏更新节奏可依据延迟测量结果自动调节,而非采用固定值,游戏体验将更为流畅。我们可以通过累加过往所有信息得到“移动平均数”,并以此作为调节的权重。
当一个新值大于当前平均数时,我们将平均数更新为该新值,这样能实现延迟的快速增加。当新值小于当前平均值时,我们会运用以下公式对该值进行权重处理: [newAverage = currentAverage\times(1 - w)+newValue\times w] 其中,(0 < w < 1)。
在我的实现里,我将 (w) 设置为 (0.1)。同时,我会跟踪每个玩家的平均数,并始终采用所有玩家中的最大值。以下是添加新值的方法。
为确保计算结果的确定性,计算过程仅使用整数。因此,公式调整为: [newAverage=\frac{currentAverage\times(10 - w)+newValue\times w}{10}] 其中,(0 < w < 1),在我的示例中,(w = 1)。
运行时间平均数
每次游戏帧更新的时间由运行时间平均数决定。若游戏帧需要变长,我们需减少每次帧同步回合更新游戏帧的次数;反之,若游戏帧执行速度加快,每次帧同步回合可更新游戏帧的次数则会增加。对于每次帧同步回合,最长的游戏帧时间会被纳入平均数的计算。每次帧同步回合的第一个游戏帧包含处理动作的时间,这里使用 Stopwatch 类来计算流逝的时间。
需要注意的是,我们也使用了 Time.deltaTime。在游戏以固定帧率执行时,使用该方法可能会导致与上一帧时间重叠。但我们必须使用它,因为这能让 Unity 为我们完成的渲染及其他操作具备可测量性。这种重叠是可以接受的,因为只需更大的缓冲区即可。
网络平均数
在该模型中,确定网络平均数的具体指标并非一目了然。我最终选择使用 Stopwatch 计算从玩家发送数据包到确认动作的时间。此帧同步模型发送的动作将在未来两个回合中执行。为结束帧同步回合,我们需要所有玩家都确认该动作。此后,可能会有两个动作等待对方确认。为解决这一问题,我们使用了两个 Stopwatch 实例:一个用于当前动作,另一个用于上一个动作。这一逻辑被封装在 ConfirmActions 类中。当帧同步回合推进时,上一个动作的 Stopwatch 会成为当前动作的 Stopwatch,而旧的“当前动作 Stopwatch”会被复用为新的“上一个动作 Stopwatch”。
每当接收到确认信息时,我们会检查是否已接收所有确认。若已全部接收,则暂停 Stopwatch。
发送平均数
为使一个客户端能向其他客户端发送平均数,Action 接口被修改为一个包含两个字段的抽象类。
每当处理动作时,这些数值会被累加到运行平均数中,随后帧同步回合和游戏帧回合开始更新。