时间倒放的有趣实现,在Creator中作物理回溯,开发《时空幻境》一样的倒退玩法

时间倒放的有趣实现,在Creator中作物理回溯,开发《时空幻境》一样的倒退玩法

Hello大家好,我是Nowpaper,一爸学游戏,越来越有趣,文章是B站视频的论坛文字版本:

前言

f2d32070082abae3d7ad100a26156b8d716d614d960×600 94.2 KB

第一次接触到电子游戏中的时间倒退玩法,着实被惊艳的表现震惊到了,那种掌控时间的感觉,让人意犹未尽,可是这类游戏并不很多,其中有个原因是在于,时间倒退的功能,会对游戏机制的设计要求极高,程序处理也较为复杂,理解实现原理,才能更好的开发出来同类型的游戏,本文章将使用CocosCreator3,实现一个时间回溯的效果,如果您觉这个很酷,还请点赞收藏支持

2D和3D的时间倒退技术方案,基本上是一样的,在本文中将使用3D物理,来实现这样的展示,在一个平台上堆一个墙,通过发射的球打散,按住一个键产生时间倒退,松开键时间流逝就会继续

实现回溯效果的原理并不复杂,我们只需要记录,时间点上的物体状态数据,而次数直接影响了内存量级,一般来说回放只记录小范围的时间段,然后在游戏循环逻辑中,倒着播放出来即可,每个记录间隔,而具体多少需要看你的数据设计

我们不需要将每个帧都记录,因为它是在太快了,一般的做法是记录固定时间间隔上状态数据,然后做中间插值,而快速倒放甚至都不需要做中间插值,视频里的实现是没有作插值的快速倒放

准备演示场景

首先用来展示的这个场景的搭建也不复杂,包含了一个平台,一个方块墙,一个球的预制体,素材来源于官方Store

image851×502 53.8 KB

场景准备好之后,我们在世界上创建一个节点,名字叫做RewindSystem,后面在这个节点下面的所有物体,都会按照规则记录数据状态,它之外的都不会被记录,具体的实现我们等会儿再说

添加一个Canvas节点,加入一个倒退图标,用来标记是否在倒退状态

image856×586 110 KB

现在我们建立一个发射小球的脚本,比如叫SphereShooter的组件,进入代码编辑中,加入Canvas、Camera,以及Prefab的可引用属性,在Start里注册按键事件,通过on方法,监听一个KeyUp的事件,在事件获取中,通过KeyCode判断是否为空格键抬起,触发一个发射方法,为了方便,我们将发射封装成一个shoot方法,传入点击屏幕的坐标,我们期望是从屏幕中心发射出去,因此传入(0,0)点即可,核心代码如下:

private shoot(x:number,y:number){

const outRay = new geometry.Ray();

this.camera.screenPointToRay(x,y,outRay);

let clone = instantiate(this.sphere);

clone.setPosition(this.camera.node.position);

this.node.addChild(clone);

clone.getComponent(RigidBody).applyImpulse(outRay.d.multiplyScalar(40));

}

// 完整代码请移步Store : https://store.cocos.com/app/detail/3407

在这个方法里,通过摄像机screenPointToRay方法,取得一个由摄像机为起点,经过屏幕点击点的射线,这个射线就是小球要发射出去的推力向量,而小球的发射位置,就直接是相机的位置,将它添加到父节点,最后把推力设置给它。

保存一下返回到Creator里面,将刚刚写的组件脚本,添加到RewindSystem节点上,属性中把Camera和小球的Prefab,引用到组件属性上,运行一下看看效果,按空格键的时候小球发射,球体撞击方块堆砌成的墙面,场景环境发生变化,测试场景就准备好了

实现回放

现在实现的目标是,按一个按键执行回放,松开按键停止回放,为了实现这个效果,新建一个回放系统组件,在组件脚本中定义isRecording属性,来判断控制是否在回放,Start中注册KeyDown和KeyUp的事件,通过KeyCode值判断按键R键是否按下,来修改是否回放的成员变量

记录和回放

在整个回放系统中,我们需要记录所有的,可以回放物体的重要信息,因此首先定义一个record类,里面保存坐标数据和旋转数据,用一个静态方法来设置回放数据,定义一个基于此记录类的数组类RecordBuffer,它的作用是不断的Push数据记录,回放的时候利用Pop取出最后一个数据,还原给对应的物体,数据结构代码:

class RecordItem{

public vec3:Vec3;

public quat:Quat;

public linearVelocity:Vec3 = new Vec3();

public angularVelocity:Vec3 = new Vec3();

public constructor(rig:RigidBody){

this.vec3 = rig.node.position.clone();

this.quat = rig.node.rotation.clone();

rig.getLinearVelocity(this.linearVelocity);

rig.getAngularVelocity(this.angularVelocity);

}

public static Rewind(rig:RigidBody,item:RecordItem){

rig.node.setPosition(item.vec3);

rig.node.setRotation(item.quat);

rig.setLinearVelocity(item.linearVelocity);

rig.setAngularVelocity(item.angularVelocity);

}

}

class RecordBuffer extends Array{

}

// 完整代码请移步Store : https://store.cocos.com/app/detail/3407

在系统中添加它的Map数据结构,Key用来记录UUID,Value则存放RecordBuffer,在Start中,我们启用一个调度,让它在指定的时间间隔中,记录这个节点下的每个子节点数据,在逻辑中需要不停的,将这些记录数据push给Buffer,时间间隔我设置为一秒内记录30次,视频中没有考虑那么多,在update中,对是否回放进行判定,如是回放状态也就是按下了R键,遍历记录的Map数据,通过uuid找到对应的记录缓存,取出最后一个记录点,将数据还原回去

图示22157×1007 64.3 KB

KINEMATIC和DYNAMIC

这里有个重点,倒放时候的游戏时间并没有变化,物理系统仍然运转,此时回放系统在不停的设置位置和旋转,因此动态类型物体的物理,可能会因为不停的赋值,造成大量的不必要运算和错误,为了避免这样的问题,在按键按下的时候,设置所有的物体物理类型为,运动学刚体KINEMATIC,按键抬起的时候设置,所有物体物理类型为,动力学刚体DYNAMIC

// 回放时需要设置

RigidBody.type = ERigidBodyType.KINEMATIC

// 正常时需要设置

RigidBody.type = ERigidBodyType.DYNAMIC

初步成果

将刚刚写的组件脚本,添加到回放控制用的节点上,将自由摄像机控制组件添加给主摄像机,并且添加一下倒放UI的引用,启动一下看看效果,打出几个小球看看效果,然后按R键,怎么样不错吧,回放速度比较的快,是因为我们在update里面执行的修改,之前是1秒30次的记录,而在这里的update通常是在60帧,也就是每秒60次的赋值修改,因此看起来比较快

优化修正

线性速度和旋转(角)速度

就目前来看,还有一些瑕疵,第一是回放到一半,恢复正常,物体不会按照之前的物理状态运行,上面的动图中,收回到一半的时候,它会在半空落下或者按照恢复时候的物理行进,我们的期望是,回放前是什么样,回放后恢复的时候还是什么样子,如下动图

为了达到这个效果,这里我们需要记录的数据,除了位置和旋转信息,还需要记录刚体的线性速度,和旋转速度,因此只需要改造一下记录数据结构,在记录上做一些处理,添加线性速度和角速度的记录数据,恢复的时候也要将这些数据还原,仅仅这样还不够最重要的是,最后一次的记录缓存到一个Map中,在按键抬起的时候,将最后一次的记录给物体恢复一下物理数据,完成之后保存返回到Creator运行,现在物理回放前和回放后几乎保持一致了

核心代码:

private lastRecords = new Map();

OnKeyUp(event:EventKeyboard){

if(event.keyCode == KeyCode.KEY_R){

this.isRewind = false;

this.playbackIcon.active = false;

for(let node of this.node.children){

this.changeRigidBodyType(node.getComponent(RigidBody),ERigidBodyType.DYNAMIC);

const item = this.lastRecords.get(node.uuid);

if(item){

RecordItem.Rewind(node.getComponent(RigidBody),item);

this.lastRecords.delete(node.uuid);

}

}

}

}

// 完整代码请移步Store : https://store.cocos.com/app/detail/3407

收回生成额外物体

第二个问题是小球恢复回去之后,不会消失,这是因为小球是通过Prefab创建的,它没有之前的数据,因此只需要将队列为空的时候,判断一下是不是小球即可,如果是的话直接销毁

结束

到此已经完成了,这是一个小而有趣的技术点,其实无论对于2D还是3D游戏,要想完美的实现回放,还需要很多工作,回放点记录数值的策略,取决于你的游戏结构设计,可以做比如计数调用、过度插值、动画倒放等优化这个功能,但是考虑的问题也会很多,这里就不展开说了,如果你打算用Creator做游戏,收藏一下绝对不亏,希望文对您有用,我是Nowpaper,一个混迹游戏行业的老爸,我们下次再见

其他

本人喜欢研究各种有趣的玩法,以下是往期制作,可以移步研究

摄像机视角的有趣玩法,实现《饥荒》同款视觉表现,一毛一样

Raycast射线实现3D世界交互,如何实现立体界面UI

用RenderTexture实现Sprite版小地图和炫酷的传送门

好玩的编队代码,魔性队伍排列惊喜不断完全停不下来

手撸三个有关Bundle详细教程,大厅+子游戏模式从入门到进阶

Cocos3D《病毒传播模拟器》游戏版本1 开发日志和总结

案例开发 四图猜词 Part1~4 全集教程

相关推荐

苹果6怎么增加内存多少钱 , 苹果手机可以内存扩容多少钱
365彩票软件app下载

苹果6怎么增加内存多少钱 , 苹果手机可以内存扩容多少钱

⏱️ 07-13 👁️ 2653
因为遇见你
365bet手机娱乐

因为遇见你

⏱️ 08-10 👁️ 9613
神途游戏手游哪个职业厉害?平民玩家看过来!
365彩票软件app下载

神途游戏手游哪个职业厉害?平民玩家看过来!

⏱️ 08-13 👁️ 5794
在香港买三星note4多少钱
365bet手机娱乐

在香港买三星note4多少钱

⏱️ 08-02 👁️ 1312
折叠自行车笔记
Bet体育365提款流水

折叠自行车笔记

⏱️ 08-03 👁️ 5205
世界杯首例:球员遭死亡威胁 警方已介入 想起94世界杯埃斯科