前言

今天看到群友提出游戏回放功能怎么设计的问题,感觉挺有意思,胡思乱想片刻,就有了此文。

正文

什么是回放功能

回放功能也可以叫录像功能,指的是玩家可以从服务端拉取数据,然后在本地进行历史游戏的复现。 直观点的例子就是LOL的观战系统还有录像功能,R6或者OC里的死亡回放功能。 观战系统

各种类型的回放区别

LOL的录像和观战都不是实时的,也就是说一个人并不能一边看录像,一边玩游戏。 R6和OC里的死亡回放是实时的,因为他是在游戏运行时进行的,玩家死了就要死亡回放。 这种差异也直接导致了实现难度的差异。 首先是LOL的录像,解决方案很简单,只需要保存每次玩家输入的消息以及那些会与客户端产生交互的信息,然后解析消息,向系统内发送信息即可。优点很明显,录像文件体积极小,当然了,这种方法也有很大缺点,无法保证回退到播放过的时间段时游戏中的各个实体状态是否与当时的游戏一致,因为并没有记录世界快照,回退也就无从说起了。也就是说,只能进,不能退。 但是OC的死亡回放单纯用上面那种方法却行不通,因为他是实时的,强硬的更改状态会破坏当前游戏逻辑顺序。只能保存每个时间点的世界快照,然后让客户端表现。优点是数据全面,方便做回滚,缺点就是文件过大。 实际上这两种回放功能应该是都采用了记录玩家输入信息+实时世界快照的方式,推测的依据是,我玩过的一款手游它的观战功能做的很完善,有快进,有回退,并且观战一场战斗,它在初始化耗费了几MB的流量(应该是从专门的观战服务器下载了玩家操作信息文件),在正式进入观战界面后,每当拖动进度条并且松开手指时,会有每秒80kb左右的流量消耗,这应该是继续从云端实时拉取当前世界快照信息对比当前客户端显示内容,冲突的要销毁,所以还会有明显的卡顿。这应该是一个比较折中的解决方案了。 正常情况下的流量消耗 拖动后的流量消耗 对于OC的回放功能,官方在GDC上有分享:https://gameinstitute.qq.com/community/detail/115186

我想到了什么

这里假设使用的纯状态同步方案 首先明确回放功能几个重点

  • 1.需要达成的基本目标是尽可能的逻辑共用,这就要求双端处理消息的逻辑尽可能和战斗系统本身没有太大关系,最多用战斗系统留给外界的接口。
  • 2.每次交互消息都将导致状态的改变,所以需要把所有交互的消息都存下来。反过来,没有产生交互的信息就没必要保存,因为它不交互就意味着客户端不会对他响应,不会对他响应就一定没有表现,所以就没有保存的必要。
  • 3.需要做好世界快照之间的对比,销毁那些冲突的部分。

针对前面提到的信息字眼,我认为指令更加合适,游戏中所有类型的指令都可以抽象成一种指令——对实体执行一个操作,还能免去解析这个步骤,那么这时候应用命令模式来抽象我们的指令就是一个很好的选择。 然后应当单独写一套指令系统,它包含指令的序列化反序列化执行撤销功能,直接反序列化出指令类执行指令,直接对实体进行操作,能避免很多不必要的周转与性能的浪费,因为说到底,一个客户端的回放只是我们一个人在看。这个指令系统,就是我们抽象出来的本地服务端。 举个最简单的例子,客户端A和B,A释放火球术,打中了B,B扣除了100点血,这里假设A释放火球术是个确定性行为,也就是说,他一定会释放出来。 对于这个过程,保存的指令就包括

  • 1.A创建火球请求(玩家输入指令)
  • 2.火球打中了B(需要与客户端进行交互的指令)
  • 3.B受伤-100血(需要与客户端进行交互的指令)
  • 4.这段时间里的每帧世界快照

首先是从头开始的正常的回放: 游戏初始化,里面的所有实体都是和当时一样的id(事实上所有的状态都一样)。 然后是指令的执行,他主要包含以下信息,作用Unit的id,指令的具体内容 在播放到A释放火球术这一阶段时,会是以下情形

  • 1.解析指令1,直接让A播放创建火球术的表现,播放动画,生成火球,播放声效,播放特效等,因为我们不需要再校验指令的合法性了,这并没有意义,这里如果数据逻辑分离的好,直接传入一个启动数据,这些表现应当是自动执行的(联想一下事件驱动的行为树)
  • 3.解析指令2,播放打中音效,特效
  • 4.解析指令3,B减血

然后是拖动进度条,回到A请求释放火球术的那个时间点 对比客户端当前时间点的世界快照与从观战服务器拉取的当前时间点的世界快照,发现有几点是冲突的

  • 1.玩家A的蓝量
  • 2.玩家B的血量
  • 3.多了一个火球实体
  • 4.多了一些特效。。。

所以就需要把这些冲突的信息按照从服务端拉取的快照文件进行修正,这里同样可以再应用指令系统里面的撤销机制,将冲突的指令进行撤销。

总结

以上基本就是我对游戏回放系统设计的思考,总结就是回放文件要记录玩家输入指令交互指令以及每帧的世界快照,视情况选择全部下载还是一半下载,一半从观战服务器实时拉取,然后进行数据注入和冲突修正。 当然了,真正实现起来难度肯定不会小,从OC在GDC分享的技术就可见一斑,这里仅仅是一个思路总结而已啦。