为什么游戏的回放功能不能倒放或者跳跃进度?

分类: bat365app手机版下载 时间: 2025-12-07 17:31:09 作者: admin 阅读: 6924
为什么游戏的回放功能不能倒放或者跳跃进度?

它的作用是是在“重播”模块下,让画面渲染指定逻辑帧(frameIndex)的情况,只要实现了这个功能,并且运行高效(肯定不能是重新演算一下这么低效把),那么真正的“视频录像”功能也就算是完成了(至少是核心功能完成了吧,UI什么的当然还没做)。那么这个函数里到底要做些什么事情呢?最早接到做一个完整的视频功能,并且甲方高度重视这个功能,是在2014年,当时直播行业刚起步(萌芽阶段),甲方的意图是,如果游戏能够更方便用户去录制各种视频,包括一些好像是叫“鬼畜”的功能,就是玩家可以选定一段然后让这段来回播放什么的。所以需求中,的确存在了倒播、甚至是复制和插播的功能。而那个游戏玩一局的时间,短的差不多5分钟,长一点可能得2小时(甲方设计的确傻逼,但是作为乙方在提议无效的情况下,是只能当做正确的需求来做的,这里顺便吐槽一下很多公司的程序员,其实是“甲方程序员”,甚至可以否决策划设计,这工作也太轻松了点了吧)。其实一切逻辑你也想得到,就像上面这个问题的答案一样“就是把frameIndex下的数据拿出来渲染”,说这句话非常轻松,但是只要批判一下,你就发现一个很严重的问题——那么每一帧要记录的数据是什么?要知道长达2小时的游戏局(如果用户当中挂机一会甚至可以玩6、7个小时一局,算了这就不吐槽了),即使FPS是30,也要14400帧的数据,如果按照每一帧都是一个位图来存,这显然是没法接受的;但是如果我只记录操作,那么倒播是根本无法实现的,这不符合甲方的需求,正如吐槽里说的。那么当时最先想到的最佳的方案就是,记录每一帧的Snapshot——这是这两个方案的折中方案。但是如果顺着这个Snapshot的思路继续下去,你就会问出下一个问题——那么我要存些什么数据呢?最早进入我大脑的方案是这个:因为既然客户端可以“重播”一段来自服务器的数据,那么它重播自己的数据,本身没有问题,并且这样一套机制,看起来是通用的,适合于任何其他游戏。当时我也沉迷于造轮子,追求的是一个机制可以用在所有的游戏当中,这也是使用ECS的目的之一。但是在实际动手写代码(Haxe写ECS)的时候,新的问题产生了,让我不得不重新思考这个问题:是不是我之后开发的每一个游戏,我针对这个游戏产生这些“重播数据”,以及使用这些“重播数据”,都得有一个约定的写法?或者说每开发一个游戏,都得为它的“重播数据”定制一套“重播功能”?虽然主要工作是游戏策划,但是我对于编程还是有一定的要求的,原则告诉我,ECS需要的不是这个玩意儿,不是一份说明书,不是一个约定,也不是一个应付的玩意儿,它真正需要的是:一个RecordSystem和一个RecordComponent,来实现一个:不依赖于游戏逻辑产生数据,并且能够重播,并实现TGameRecord.Render()的这么个玩意儿。或者换句话说,如果它本身是一段“录像”,是否能被“重播”?(是不是感觉这个需求说起来有点绕了?)然后我根据这个真实的需求,仔细思考了一下实现的细节,它应该是……RecordComponent在这个Component中的只有2个数据:currentValue:Array和modifications:Array,这玩意记录了这个RecordComponent的宿主Entity下,所有position和render这两个Component中的数据变化。ComponentKeyValue的属性包括:component:string,之所以用string,而不是用Component,是因为Component之间是不应该存在互相依赖的关系,所以使用一种类似“密码”的方式来实现自欺欺人的效果,是的,大多程序员都善于自欺欺人,比如C#里的单例模式(当然这就扯远了)。这个的值就是Component的名字,这是在ECS里能正常Get得到的(简单地说就是.toString()而已)。key:string,对应Component中的属性,跟component一样道理。value:dynamic,对应的值,可以吃haxe的dynamic类的糖。这些值在初始化这个component的时候,可以从对应的Component里读取,实际上这个东西也是为了实现“对比”功能而存在的,当然为了重播,可以再开一个initValue,即创建时候所关心的Component的数据,并且不再改动,用于录像功能更方便的追溯当前帧的数据。ComponentModification的属性包括:tick:int,即这次改动所发生的游戏tick,确切的说是RecordSystem运作了的tick数。modification:ComponentKeyValue,即属性变化的样子信息,那个属性发生了变化,变成了什么。通过这样一个array,我就可以知道这个entity的“有效生命周期”里,它的renderSystem所依赖的数据(Component)发生的变化。RecordSystem这个系统关注的Component有:RecordComponent:即上面的Component。Render和Position:之所以只关心这两个,是因为大多游戏中RenderSystem关注这两个就足够了,但是可能存在第三个,这个当初没有完成归纳,不幸的是团队解散了,各奔东西了,ECS也被雪藏了,直到很多年后守望先锋再次提起,当然这是另外一个故事了。这个System的工作核心有2件事情:记录每次运行时,所捕捉到的entity,整理后归纳到一个临时文件,这个临时文件中,实际记录的是一个entity被“创建”即首次被捕获的tick,和被“移除”的tick,即不再捕捉到这个entity,当然实现方式很多种,这是一个不咋得的方式,但是当时也没来得及优化,it just works。对比RecordComponent中的currentValue和对应的Component中的对应属性,如果发生变化了,则要在RecordComponent中记录,并刷新对应的属性。这样一来,在整个RecordComponent生命周期中,“发生过的变化”都被一一记下了。“重播数据”产生及应用一局游戏结束后,即这个System被整个游戏世界Shutdown的时候,导出数据,这个数据中主要包的内容是:一个RecordComponent被创建和移除的时间点(tick),以及被移除时的数据。通过这些数据信息,我们就可以实现这个TGameRecord.Render:首先获得当前帧所有的entity:这个entity和游戏运行时候的entity不同,ECS的特性也是如此,即我们人理解的一个东西,他未必只是一个entity,他可以是很多个“同步的”entity,当然这是一个ECS的使用方式问题了,这里不细说。从数据中,我们能得出当前帧存在的RecordComponent的数组,有多少个存在的RecordComponent,就有多少个entity。为entity创建render和position两个Component,这里你很容易就能从RecordComponent中过滤出当前帧的属性信息来,不论你是用RecordComponent.initValue还是用currentValue,只是一个for循环的方向问题而已。通过一个RecordRenderSystem来把他们绘制出来。这个“重播功能”的核心部分就算是完成了。总结耐心看到这里,你可以发现,实际上一个“完善的重播功能”实现方式是这样的,与我们“小聪明产生的笨办法”看起来相似,但实际上差距还是存在的——它既不是存显卡信息,也不是存操作指令;它看起来存的是snapshot(或者说它存的就是snapshot),但是此snapshot非彼snapshot,并且这才是泛用型的“重播功能”,因为我可以把这个RecordSystem和RecordComponent用于任何游戏,而不用单独写什么额外逻辑,唯一可能需要维护的,就是关心的Component而已(但这讲道理也是不该变化的)。既然实现方式不同,自然根本就不是一个东西,所以你提的需求,和现在市面上游戏所做的东西,就不是一个东西,他们满足不了你的这些倒播、跳播,自然也是很正常的事情了。

相关文章

三生三世:素锦对夜华的爱,为何在被贬以后就忽然消失了呢?
生物信息学分析之如何看懂火山图?
徐州徐州轮胎乐园游玩攻略简介,徐州徐州轮胎乐园门票/地址/图片/开放时间/照片/门票价格【携程攻略】
如何看ipad型号