玩家移动脚本
RegisterObjectForTimeRecord方法在希望开始记录特定对象的时间状态时进行调用
UnregisterObjectForTimeRecord方法在不再需要记录特定对象的时间状态时被调用,没调用的话,上一次纪录的痕迹不会删除
using UnityEngine;
public class MOVE : MonoBehaviour
{
public float moveSpeed = 5f; // 移动速度
private bool isMove=false;
public TimeReturn timeReturn;//时间回溯脚本
void Update()
{
float h = Input.GetAxis("Horizontal"); // 获取水平输入
float v = Input.GetAxis("Vertical"); // 获取垂直输入
if (Mathf.Abs(h) > 0 || Mathf.Abs(v) > 0)
{
isMove = true;
Vector3 moveDirection = new Vector3(h, 0f, v).normalized; // 构造移动方向向量并归一化
transform.position += moveDirection * moveSpeed * Time.deltaTime; // 更新物体的位置
}
else
{
isMove = false;
}
if (isMove)
{
timeReturn.RecordTime(); // 记录当前时间状态
}
if (Input.GetKeyDown(KeyCode.R))
{
StartRewind();//然后逐帧回溯 所有对象的时间状态
}
if (Input.GetKeyDown(KeyCode.T))
{
timeReturn.UnregisterObjectForTimeRecord(this.transform);//再注销
}
if (Input.GetKeyDown(KeyCode.E))
{
timeReturn.RegisterObjectForTimeRecord(this.transform);//先注册
}
// 示例中使用了Input.GetKeyDown检测用户输入,实际应用中可以根据实际需求调用方法
}
private void StartRewind()
{
timeReturn.StartRewindTime();
}
}
时间回溯脚本
TimeRecord被定义为一个结构体主要是出于以下考虑:
轻量级:结构体是一个值类型,它在内存中的存储方式更加简单,这使得它在一些情况下比类更加高效。由于TimeRecord可能会被大量创建和使用(比如记录每一帧的游戏对象状态),使用结构体可以减少内存开销。
不可变性:结构体是不可变的,这意味着一旦创建后,它的字段不能被修改。在记录时间状态时,这有助于保持记录的不变性,防止无意中对记录的状态进行修改。
语义上的合理性:TimeRecord作为一个记录时间状态的数据结构,其内容在逻辑上更类似于一个值而不是一个对象的实例。这种语义上的相符性使得使用结构体更加合适。
然而,需要注意的是结构体也有其适用的场景和限制。结构体适合简单的数据传输和封装,但对于复杂的对象以及需要动态修改的状态,类可能更为合适。
using UnityEngine;
using System.Collections.Generic;
using System.Linq;
// 时间记录的结构体
public struct TimeRecord
{
public Vector3 position;
public Quaternion rotation;
// 其他需要记录的状态,比如速度、动作等
}
public class TimeReturn : MonoBehaviour
{
private Dictionary<Transform, List<TimeRecord>> objectTimeRecords = new Dictionary<Transform, List<TimeRecord>>(); // 存储多个对象的时间状态
private bool isRewinding = false; // 标识是否正在回溯
private int currentRewindFrame = 0; // 当前回溯到的帧数
/// <summary>
/// 记录所有注册对象的时间状态
/// </summary>
public void RecordTime()
{
foreach (Transform objTransform in objectTimeRecords.Keys)
{
if (!objectTimeRecords.ContainsKey(objTransform))
{
objectTimeRecords[objTransform] = new List<TimeRecord>();
}
TimeRecord record = new TimeRecord();
record.position = objTransform.position;
record.rotation = objTransform.rotation;
// 存储其他状态
objectTimeRecords[objTransform].Add(record);
}
}
/// <summary>
/// 每帧更新回溯状态
/// </summary>
void Update()
{
if (isRewinding)
{
RewindTimeFrameByFrame(); // 倒流时间
}
}
/// <summary>
/// 逐帧回溯所有对象的时间状态
/// </summary>
public void StartRewindTime()
{
if (objectTimeRecords.Count > 0 && objectTimeRecords.Values.First().Count > 0)
{
currentRewindFrame = objectTimeRecords.Values.First().Count - 1; //objectTimeRecords.Values.First().Count 定位到第一个注册对象的时间记录列表,并获取它的长度。这样的语法利用了 LINQ 扩展方法,需要确保在使用之前引入 System.Linq 命名空间
isRewinding = true;
Time.timeScale = 0f; // 将时间缩放设为0,实现暂停
}
}
/// <summary>
/// 逐帧回溯
/// </summary>
public void RewindTimeFrameByFrame()
{
if (currentRewindFrame >= 0)
{
foreach (Transform objTransform in objectTimeRecords.Keys)
{
TimeRecord record = objectTimeRecords[objTransform][currentRewindFrame];
objTransform.position = record.position;
objTransform.rotation = record.rotation;
// 根据当前帧还原其他状态
}
currentRewindFrame--;
}
else
{
StopRewindTime();
}
}
/// <summary>
/// 停止回溯
/// </summary>
public void StopRewindTime()
{
isRewinding = false;
currentRewindFrame = 0;
Time.timeScale = 1f; // 将时间缩放恢复为正常值
}
/// <summary>
/// 注册需要记录时间状态的对象
/// </summary>
/// <param name="objTransform"></param>
public void RegisterObjectForTimeRecord(Transform objTransform)
{
if (!objectTimeRecords.ContainsKey(objTransform))
{
objectTimeRecords[objTransform] = new List<TimeRecord>();
}
}
/// <summary>
/// 取消注册对象
/// </summary>
/// <param name="objTransform"></param>
public void UnregisterObjectForTimeRecord(Transform objTransform)
{
if (objectTimeRecords.ContainsKey(objTransform))
{
objectTimeRecords.Remove(objTransform);
}
}
}