关于Unity的回放功能
最近公司的仿真项目需要用到回放的功能,但是在unity中的回放功能,无论是插件(EZRecordManager)还是其他人的代码,感觉都不太好,网上有两个博客讲的回放,在这里链接给大家看一下https://blog.csdn.net/qq_32225289/article/details/87986158(我是小白,可能对他的这个代码记录的有些看不懂),还有一个,网址暂时找不到了,等以后找到之后再贴上吧。第二个是因为他的内存储存量太大了,觉得性能不是特别好。
所以我自己搭建了一个简单的关于物体回放以及按钮事件的一些回放,话不多说,开始写。
注:本人数据结构不是特别好,所以写的代码可能很垃圾,但是功能确实实现了,不过代码跟大牛没法比(命令模式,不知道命令模式的可以去看一下设计模式)
首先讲一下思路:这里贴出的代码是关于物体的移动和旋转方面的回放
1.这里的位置回放采用的是unity按键WASD的Down和Up方法,当按下WASD的某个键位的时候,记录物体当前的位置,当抬起WASD某个键位的时候,再记录一次当前物体的位置。同样的,当按下鼠标右键的时候,记录当前物体的旋转角度(四元数),当抬起鼠标右键时再次记录当前物体的旋转角度)
下面代码是主方法的代码
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using UnityEngine;
using UnityEngine.UI;
using DG.Tweening;
public class MoveDemo : MonoBehaviour
{
public SimpleMouseRotator MouseRotate;
public float MoveSpeed;
public Transform player;
public Data data;
//按下按键时间
List<float> pressTimeW;
List<float> liftUpTimeW;
List<float> pressTimeA;
List<float> liftUpTimeA;
List<float> pressTimeS;
List<float> liftUpTimeS;
List<float> pressTimeD;
List<float> liftUpTimeD;
List<float> mouseDownTime;
List<float> mouseUpTime;
bool hasData = false;
public float timer;
void Start()
{
pressTimeW = new List<float>();
pressTimeA = new List<float>();
pressTimeS = new List<float>();
pressTimeD = new List<float>();
liftUpTimeW = new List<float>();
liftUpTimeA = new List<float>();
liftUpTimeS = new List<float>();
liftUpTimeD = new List<float>();
mouseDownTime = new List<float>();
mouseUpTime = new List<float>();
data = new Data();
}
float Wtime;//这里用于判断键位(抬起-按下)之后的时差跟time.DeltaTime对比
float Atime;
float Stime;
float Dtime;
float rotationTime;
void Update()
{
timer += Time.deltaTime;
if (Input.GetKeyDown(KeyCode.Space))
{
//按下空格键,得到之前保存的物体的行进路线和旋转角度并且开始播放
data = ReadIO();
Move(data);
}
if (Input.GetKeyDown(KeyCode.P))
{
//按下P键,保存player物体的行进路线和旋转角度
FileIO();
}
if (hasData)//如果得到序列化之后的数据,开始遍历这个Data里面的内容
{
for (int i = 0; i < pressTimeA.Count; i++)
{
if (pressTimeA[i] < timer)//当前时间如果大于按下的时间的话,那么就调用人物移动的方法
{
if (pressTimeA[i] < liftUpTimeA[i])
{
Atime += Time.deltaTime;
if (Atime < liftUpTimeA[i] - pressTimeA[i])
{
player.Translate(-data.AoriginPlayerTimeAndSpeedDic[pressTimeA[i]], 0, 0, Space.Self);
}
else
{
pressTimeA.Remove(pressTimeA[i]);
liftUpTimeA.Remove(liftUpTimeA[i]);
Atime = 0;
return;
}
}
}
}
for (int i = 0; i < pressTimeW.Count; i++)
{
if (pressTimeW[i] < timer)//当前时间如果大于按下的时间的话,那么就调用人物移动的方法
{
if (pressTimeW[i] < liftUpTimeW[i])
{
Wtime += Time.deltaTime;
if (Wtime < liftUpTimeW[i] - pressTimeW[i])
{
player.Translate(0, 0, data.WoriginPlayerTimeAndSpeedDic[pressTimeW[i]], Space.Self);
}
else
{
pressTimeW.Remove(pressTimeW[i]);
liftUpTimeW.Remove(liftUpTimeW[i]);
Wtime = 0;
return;
}
}
}
}
for (int i = 0; i < pressTimeS.Count; i++)
{
if (pressTimeS[i] < timer)//当前时间如果大于按下的时间的话,那么就调用人物移动的方法
{
if (pressTimeS[i] < liftUpTimeS[i])
{
Stime += Time.deltaTime;
if (Stime < liftUpTimeS[i] - pressTimeS[i])
{
player.Translate(0, 0, -data.SoriginPlayerTimeAndSpeedDic[pressTimeS[i]], Space.Self);
}
else
{
pressTimeS.Remove(pressTimeS[i]);
liftUpTimeS.Remove(liftUpTimeS[i]);
Stime = 0;
return;
}
}
}
}
for (int i = 0; i < pressTimeD.Count; i++)
{
if (pressTimeD[i] < timer)//当前时间如果大于按下的时间的话,那么就调用人物移动的方法
{
if (pressTimeD[i] < liftUpTimeD[i])
{
Dtime += Time.deltaTime;
if (Dtime < liftUpTimeD[i] - pressTimeD[i])
{
player.Translate(data.DoriginPlayerTimeAndSpeedDic[pressTimeD[i]], 0, 0, Space.Self);
}
else
{
pressTimeD.Remove(pressTimeD[i]);
liftUpTimeD.Remove(liftUpTimeD[i]);
Dtime = 0;
return;
}
}
}
}
for (int i = 0; i < mouseUpTime.Count; i++)
{
if (mouseUpTime[i] < timer)//当前时间如果大于按下的时间的话,那么就调用人物旋转的方法
{
if ((i+1)<mouseUpTime.Count)
{
rotationTime = mouseUpTime[i+1] - mouseUpTime[i];
player.DORotate(new Vector3(data.playerUpRotX[mouseUpTime[i]],data.playerUpRotY[mouseUpTime[i]],data.playerUpRotZ[mouseUpTime[i]],rotationTime);
data.playerUpRotX.Remove(mouseUpTime[i]];
data.playerUpRotY.Remove(mouseUpTime[i]];
data.playerUpRotZ.Remove(mouseUpTime[i]];
mouseUpTime.Remove(mouseUpTime[i]);
return;
}
}
}
}
if (Input.GetKey(KeyCode.W))
{
player.Translate(0, 0, MoveSpeed, Space.Self);
}
if (Input.GetKeyDown(KeyCode.W))
{
data.WoriginPlayerTimeAndSpeedDic.Add(timer, MoveSpeed);
data.WplayerPosX.Add(timer, player.position.x);
data.WplayerPosY.Add(timer, player.position.y);
data.WplayerPosZ.Add(timer, player.position.z);
}
if (Input.GetKeyUp(KeyCode.W))
{
data.WendPlayerTimeAndSpeedDic.Add(timer, MoveSpeed);
data.WplayerPosX.Add(timer, player.position.x);
data.WplayerPosY.Add(timer, player.position.y);
data.WplayerPosZ.Add(timer, player.position.z);
}
if (Input.GetKey(KeyCode.S))
{
player.Translate(0, 0, -MoveSpeed, Space.Self);
}
if (Input.GetKeyDown(KeyCode.S))
{
data.SoriginPlayerTimeAndSpeedDic.Add(timer, MoveSpeed);
data.SplayerPosX.Add(timer, player.position.x);
data.SplayerPosY.Add(timer, player.position.y);
data.SplayerPosZ.Add(timer, player.position.z);
}
if (Input.GetKeyUp(KeyCode.S))
{
data.SendPlayerTimeAndSpeedDic.Add(timer, MoveSpeed);
data.SplayerPosX.Add(timer, player.position.x);
data.SplayerPosY.Add(timer, player.position.y);
data.SplayerPosZ.Add(timer, player.position.z);
}
if (Input.GetKey(KeyCode.A))
{
player.Translate(-MoveSpeed, 0, 0, Space.Self);
}
if (Input.GetKeyDown(KeyCode.A))
{
data.AoriginPlayerTimeAndSpeedDic.Add(timer, MoveSpeed);
data.AplayerPosX.Add(timer, player.position.x);
data.AplayerPosY.Add(timer, player.position.y);
data.AplayerPosZ.Add(timer, player.position.z);
}
if (Input.GetKeyUp(KeyCode.A))
{
data.AendPlayerTimeAndSpeedDic.Add(timer, MoveSpeed);
data.AplayerPosX.Add(timer, player.position.x);
data.AplayerPosY.Add(timer, player.position.y);
data.AplayerPosZ.Add(timer, player.position.z);
}
if (Input.GetKey(KeyCode.D))
{
player.Translate(MoveSpeed, 0, 0, Space.Self);
}
if (Input.GetKeyDown(KeyCode.D))
{
data.DoriginPlayerTimeAndSpeedDic.Add(timer, MoveSpeed);
data.DplayerPosX.Add(timer, player.position.x);
data.DplayerPosY.Add(timer, player.position.y);
data.DplayerPosZ.Add(timer, player.position.z);
}
if (Input.GetKeyUp(KeyCode.D))
{
data.DendPlayerTimeAndSpeedDic.Add(timer, MoveSpeed);
data.DplayerPosX.Add(timer, player.position.x);
data.DplayerPosY.Add(timer, player.position.y);
data.DplayerPosZ.Add(timer, player.position.z);
}
if (Input.GetKey(KeyCode.Mouse1))
{
MouseRotate.enabled = true;
if(!hasData)
{
data.playerUpRotX.Add(timer,transform.eulerAngles.x);
data.playerUpRotY.Add(timer,transform.eulerAngles.y);
data.playerUpRotZ.Add(timer,transform.eulerAngles.z);
}
}
if (Input.GetKeyUp(KeyCode.Mouse1))
{
MouseRotate.enabled = false;
}
}
private void Move(Data data)
{
timer = 0;
//得到记录的信息 所有按下时的位置信息
//得到所有按下和抬起键位时的时间
foreach (var item in data.WoriginPlayerTimeAndSpeedDic.Keys)
{
pressTimeW.Add(item);
}
foreach (var item in data.WendPlayerTimeAndSpeedDic.Keys)
{
liftUpTimeW.Add(item);
}
foreach (var item in data.AoriginPlayerTimeAndSpeedDic.Keys)
{
pressTimeA.Add(item);
}
foreach (var item in data.AendPlayerTimeAndSpeedDic.Keys)
{
liftUpTimeA.Add(item);
}
foreach (var item in data.SoriginPlayerTimeAndSpeedDic.Keys)
{
pressTimeS.Add(item);
}
foreach (var item in data.SendPlayerTimeAndSpeedDic.Keys)
{
liftUpTimeS.Add(item);
}
foreach (var item in data.DoriginPlayerTimeAndSpeedDic.Keys)
{
pressTimeD.Add(item);
}
foreach (var item in data.DendPlayerTimeAndSpeedDic.Keys)
{
liftUpTimeD.Add(item);
}
foreach (var item in data.playerUpRotX.Keys)
{
mouseUpTime.Add(item);
}
hasData = true;
}
private Data ReadIO()
{
data.buttonDic.Clear();
string path = Application.streamingAssetsPath + "/Test.rep";
BinaryFormatter bf = new BinaryFormatter();
using (FileStream fs = File.Open(path, FileMode.Open, FileAccess.Read))
{
data = bf.Deserialize(fs) as Data;
}
Debug.Log("得到文件成功");
return data;
}
private void FileIO()
{
string path = Application.streamingAssetsPath + "/Test.rep";
BinaryFormatter bf = new BinaryFormatter();
using (FileStream fs = File.Create(path))
{
bf.Serialize(fs, data);
}
Debug.Log("生成文件成功");
}
}
接下来是Data类
Data类里面基本都是以字典形式存放,存放的是每个按键按下和抬起时的时间以及对应的轴向或者位置
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[Serializable]
public class Data
{
public Data()
{
WoriginPlayerTimeAndSpeedDic = new Dictionary<float, float>();
WendPlayerTimeAndSpeedDic = new Dictionary<float, float>();
AoriginPlayerTimeAndSpeedDic = new Dictionary<float, float>();
AendPlayerTimeAndSpeedDic = new Dictionary<float, float>();
SoriginPlayerTimeAndSpeedDic = new Dictionary<float, float>();
SendPlayerTimeAndSpeedDic = new Dictionary<float, float>();
DoriginPlayerTimeAndSpeedDic = new Dictionary<float, float>();
DendPlayerTimeAndSpeedDic = new Dictionary<float, float>();
AplayerPosX = new Dictionary<float, float>();
AplayerPosY = new Dictionary<float, float>();
AplayerPosZ = new Dictionary<float, float>();
WplayerPosX = new Dictionary<float, float>();
WplayerPosY = new Dictionary<float, float>();
WplayerPosZ = new Dictionary<float, float>();
SplayerPosX = new Dictionary<float, float>();
SplayerPosY = new Dictionary<float, float>();
SplayerPosZ = new Dictionary<float, float>();
DplayerPosX = new Dictionary<float, float>();
DplayerPosY = new Dictionary<float, float>();
DplayerPosZ = new Dictionary<float, float>();
playerDownRotX = new Dictionary<float, float>();
playerDownRotY = new Dictionary<float, float>();
playerDownRotZ = new Dictionary<float, float>();
playerDownRotW = new Dictionary<float, float>();
playerUpRotX = new Dictionary<float, float>();
playerUpRotY = new Dictionary<float, float>();
playerUpRotZ = new Dictionary<float, float>();
playerUpRotW = new Dictionary<float, float>();
}
public Dictionary<float, string> buttonDic;
public Dictionary<float, string> toggleDic;
public Dictionary<float, float> WoriginPlayerTimeAndSpeedDic;//按下键位的时间、速度
public Dictionary<float, float> WendPlayerTimeAndSpeedDic; //松开键位的时间、速度
public Dictionary<float, float> AoriginPlayerTimeAndSpeedDic;//按下键位的时间、速度
public Dictionary<float, float> AendPlayerTimeAndSpeedDic; //松开键位的时间、速度
public Dictionary<float, float> SoriginPlayerTimeAndSpeedDic;//按下键位的时间、速度
public Dictionary<float, float> SendPlayerTimeAndSpeedDic; //松开键位的时间、速度
public Dictionary<float, float> DoriginPlayerTimeAndSpeedDic;//按下键位的时间、速度
public Dictionary<float, float> DendPlayerTimeAndSpeedDic; //松开键位的时间、速度
public Dictionary<float, float> AplayerPosX;//当前时间、当前玩家位置X坐标
public Dictionary<float, float> AplayerPosY;//当前时间、当前玩家位置Y坐标
public Dictionary<float, float> AplayerPosZ;
public Dictionary<float, float> WplayerPosX;//当前时间,当前玩家位置X坐标
public Dictionary<float, float> WplayerPosY;//当前时间、当前玩家位置Y坐标
public Dictionary<float, float> WplayerPosZ;
public Dictionary<float, float> SplayerPosX;//当前时间、当前玩家位置X坐标
public Dictionary<float, float> SplayerPosY;//当前时间、当前玩家位置Y坐标
public Dictionary<float, float> SplayerPosZ;
public Dictionary<float, float> DplayerPosX;//当前时间、当前玩家位置X坐标
public Dictionary<float, float> DplayerPosY;//当前时间、当前玩家位置Y坐标
public Dictionary<float, float> DplayerPosZ;
public Dictionary<float, float> playerDownRotX;//当前时间、当前玩家旋转X坐标
public Dictionary<float, float> playerDownRotY;
public Dictionary<float, float> playerDownRotZ;
public Dictionary<float, float> playerDownRotW;
public Dictionary<float, float> playerUpRotX;//当前时间、当前玩家旋转X坐标
public Dictionary<float, float> playerUpRotY;
public Dictionary<float, float> playerUpRotZ;
public Dictionary<float, float> playerUpRotW;
}
接下来是控制物体旋转的类
using System;
using UnityEngine;
public class SimpleMouseRotator : MonoBehaviour
{
// A mouselook behaviour with constraints which operate relative to
// this gameobject's initial rotation.
// Only rotates around local X and Y.
// Works in local coordinates, so if this object is parented
// to another moving gameobject, its local constraints will
// operate correctly
// (Think: looking out the side window of a car, or a gun turret
// on a moving spaceship with a limited angular range)
// to have no constraints on an axis, set the rotationRange to 360 or greater.
public Vector2 rotationRange = new Vector3(70, 70);
public float rotationSpeed = 10;
public float dampingTime = 0.2f;
public bool autoZeroVerticalOnMobile = true;
public bool autoZeroHorizontalOnMobile = false;
public bool relative = true;
public bool dDD = false;
private Vector3 m_TargetAngles;
private Vector3 m_FollowAngles;
private Vector3 m_FollowVelocity;
private Quaternion m_OriginalRotation;
private void Start()
{
m_OriginalRotation = transform.localRotation;
Rigidbody body = GetComponent<Rigidbody>(); if (body != null) body.freezeRotation = true;
}
void OnEnable()
{
// transform.eulerAngles =new Vector3(0, transform.eulerAngles.y, 0);
m_OriginalRotation = transform.localRotation;
}
public enum RotationAxes { MouseXAndY = 0, MouseX = 1, MouseY = 2 };
public RotationAxes axes = RotationAxes.MouseXAndY; public float speedV = 9.0f; public float speedH = 9.0f; public float maxhead = 45.0f; public float minhead = -45.0f; private float _rotationX = 0.0f;
private void Update()
{
if (axes == RotationAxes.MouseX)
{
transform.Rotate(0, Input.GetAxis("Mouse X") * speedH, 0);
}
if (axes == RotationAxes.MouseY)
{
_rotationX -= Input.GetAxis("Mouse Y") * speedV; _rotationX = Mathf.Clamp(_rotationX, minhead, maxhead);//限仰视俯视角度范围
float _rotationY = transform.localEulerAngles.y;
transform.localEulerAngles = new Vector3(_rotationX, _rotationY, 0);//设置旋转角度 }
// we make initial calculations from the original local rotation
}
if (axes == RotationAxes.MouseXAndY)
{
_rotationX -= Input.GetAxis("Mouse Y") * speedV;//注意是-=
_rotationX = Mathf.Clamp(_rotationX, minhead, maxhead);//水平Verital
float _rotationY = transform.localEulerAngles.y + Input.GetAxis("Mouse X") * speedH;
transform.localEulerAngles = new Vector3(_rotationX, _rotationY, 0);
}
}
}
总结:本人数据结构不好,只能用笨方法,通过字典的键值对来获取想要的信息,之前有朋友说可以用json来存储,如果读者有更好的方法的话可以加我QQ:770187276,一起讨论关于回放的功能~
对于场景内UI,比如button或者toggle值类的回放功能,因为时间关系先不写了,之后用空再写,思路是一样的,根据键值对,key是当前场景内时间,value是button的名称或者toggle的名称,到时候根据名称得到他们的OnClick或者是OnvalueChanged然后存储就好了