记得以前看过一个电影,叫做《独立游戏大电影》,其中有个一个游戏可以实现时间回退的功能,可以像倒带一样,十分有趣。因此我就想着用Unity也实现一个类似的简单Demo,说不定哪天会用到。
效果
这个Demo可以回退Transform的Position和Rotation。
思路
一个简单的思路就是用Stack来记录物体的Position和Rotation,当需要时间回退的时候就Pop出来,赋值到物体上。
不过为了可以进行拓展,比如只能回退到某段时间内的,而不是一下子回退到最开始的地方,我们需要剔除太久之前的信息。如下图:
因此我选择使用List而不是Stack。
代码
[完整代码看文章尾]
//Pos
Vector3 pos = this.transform.position;
float horizontal = Input.GetAxis("Horizontal");
float vertical = Input.GetAxis("Vertical");
if (Mathf.Abs(horizontal) > 0.0001f) //左右移动
{
pos.x += Time.deltaTime * horizontal * Speed;
}
if (Mathf.Abs(vertical) > 0.0001f) //上下移动
{
pos.y += Time.deltaTime * vertical * Speed;
}
this.transform.position = pos;
HistoryPos.Add(pos);
这里HistoryPos就是我们用来存储历史位置的List,我们每帧都存储物体的位置。
当我们需要时间回退时,可以每帧调用下面的代码:
if (HistoryPos.Count > 0)
{
int index = HistoryPos.Count - 1;
this.transform.position = HistoryPos[index];
HistoryPos.RemoveAt(index);
}
这就是每次取出最后的位置(即最新的),赋值到物体上
当我们需要限制时间回退的时间跨度,可以在HistoryPos.Add后加上下面这些代码:
HistoryPos.Add(pos);
if (ShouldLimit && HistoryPos.Count > Limit)
{
HistoryPos.RemoveAt(0);
}
因为旋转是雷同的,因此就不贴代码出来了。
改进
- 这里我们是每帧都记录信息,这样List的大小很容易暴走,因此我们可以每隔一段时间来记录,然后要时间回退的时候就进行插值。
- 通常我们的物体都带有动画,这时倒播动画就行。如果在时间回退过程中存在多个动画,我们就需要自己设计数据结构来保存某个时刻对应的动画和动画状态。
完整代码
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
/// <summary>
/// 就是利用Stack的原理来获取历史位置
/// 如果同时有动画,把动画倒放就是
/// </summary>
public class TBPlayer : MonoBehaviour {
public int Speed = 3;
public int RotateSpeed = 100;
public bool ShouldLimit = false;
public int Limit = 100; //可以存放的坐标上限
private List<Vector3> HistoryPos;
private List<Quaternion> HistoryRot;
private bool _IsTimeBack = false;
void Start () {
HistoryPos = new List<Vector3>();
HistoryRot = new List<Quaternion>();
}
void Update () {
if (_IsTimeBack)
TimeBack();
else
ControlPos();
}
void ControlPos()
{
//Pos
Vector3 pos = this.transform.position;
float horizontal = Input.GetAxis("Horizontal");
float vertical = Input.GetAxis("Vertical");
if (Mathf.Abs(horizontal) > 0.0001f) //左右移动
{
pos.x += Time.deltaTime * horizontal * Speed;
}
if (Mathf.Abs(vertical) > 0.0001f) //上下移动
{
pos.y += Time.deltaTime * vertical * Speed;
}
this.transform.position = pos;
HistoryPos.Add(pos);
//Rotation
Quaternion rot = this.transform.rotation;
Vector3 rotv = rot.eulerAngles;
float rotate = Input.GetAxis("Fire1");
if (Mathf.Abs(rotate) > 0.0001f)
{
rotv.z += Time.deltaTime * rotate * RotateSpeed;
}
rot = Quaternion.Euler(rotv);
this.transform.rotation = rot;
HistoryRot.Add(rot);
if (ShouldLimit && HistoryPos.Count > Limit)
{
HistoryPos.RemoveAt(0);
HistoryRot.RemoveAt(0);
}
}
void TimeBack()
{
if (HistoryPos.Count > 0)
{
int index = HistoryPos.Count - 1;
this.transform.position = HistoryPos[index];
HistoryPos.RemoveAt(index);
}
if (HistoryRot.Count > 0)
{
int index = HistoryRot.Count - 1;
this.transform.rotation = HistoryRot[index];
HistoryRot.RemoveAt(index);
}
}
void OnGUI()
{
if (GUILayout.Button("时间倒流"))
{
_IsTimeBack = true;
}
if (GUILayout.Button("Reset"))
{
HistoryRot.Clear();
HistoryPos.Clear();
_IsTimeBack = false;
}
}
}