关于Unity的回放功能(二)
前言上次因为公司项目没有完全展开,所以上次写的回放功能不是最终的版本,现在有时间,可以将回放功能所用到的内容从新整理记录一下。用于以后做参考。在上一篇文章中写了一部分了,具体可以查看上一篇文章,这次是对上一篇文章进行更加细化的整理和编写。其中有一部分涉及到了公司项目,所以有报错的地方,但是思路的话还是比较清晰的。
直接上代码
using Common; using DG.Tweening; //using Net.Share; using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Runtime.Serialization.Formatters.Binary; using UI; using UnityEngine; using UnityEngine.UI; //用于控制回放时各个UI按钮及玩家移动的类 using Common; using DG.Tweening; //using Net.Share; using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Runtime.Serialization.Formatters.Binary; using UI; using UnityEngine; using UnityEngine.UI; public class ReplayManager : MonoSingleton{ public ReplayPanel replayPanel; public ReplayData data;//首次所用data数据 public bool rePlay; public float timer; //按下按键时间,用于玩家按下、抬起WASD按键 public List pressTimeW, liftUpTimeW; public List pressTimeA, liftUpTimeA; public List pressTimeS, liftUpTimeS; public List pressTimeD, liftUpTimeD; public List mouseDownTime, mouseUpTime; //这个时间的话是判断在按下、抬起WASD按键的过程中间隔是多久,通过时间差来回放玩家的移动 float Wtime; float Atime; float Stime; float Dtime; float rotationTime; /// /// 玩家移动类 /// public PlayerMove playerMove; public float replaySpeed = 1;//回放速度 public float totalTimer = 0;//回放的总时长 ////// 提示框 /// public GameObject message; protected override void Initialize() { data = new ReplayData(); pressTimeW = new List(); liftUpTimeW = new List (); pressTimeA = new List (); liftUpTimeA = new List (); pressTimeS = new List (); liftUpTimeS = new List (); pressTimeD = new List (); liftUpTimeD = new List (); mouseDownTime = new List (); mouseUpTime = new List (); playerMove = GameObject.FindObjectOfType (); } public float replayTimer; protected override void Awake() { base.Awake(); } /// /// 判断回放是否完成 /// public bool isFinish = false; private void Update() { if (!rePlay) { timer += Time.deltaTime * replaySpeed; } if (rePlay) { if (replayTimer >= data.totalTimer) { isFinish = true; if (isFinish == true) { message.SetActive(true); replayPanel.totalTime = ""; message.transform.Find("GameObject/Content").GetComponent().text = "流程回放已结束,请退出该任务流程回放"; //在这里要设置一下 replayPanel.transform.Find("Buttons").gameObject.SetActive(false); } rePlay = false; } replayTimer += Time.deltaTime * replaySpeed; StartReplayProgress(); } } /// /// 这里是在回放过程中,对按钮的事件进行复现的功能,复现完成后,删除对应的按钮事件 /// private void StartReplayProgress() { for (int i = 0; i < UIManager.Instance.buttonEventTime.Count; i++) { if (UIManager.Instance.buttonEventTime[i] < replayTimer) { //如果小于的话,直接删除这个元素 foreach (var button in UIManager.Instance.buttons) { try { if (button == null) continue; if (button.name == data.buttonDic[UIManager.Instance.buttonEventTime[i]]) { button.onClick.Invoke(); UIManager.Instance.buttonEventTime.Remove(UIManager.Instance.buttonEventTime[i]); return; } } catch (Exception e) { Debug.Log(button.name); Debug.Log(e.ToString()); } } } } for (int i = 0; i < UIManager.Instance.inputFieldTime.Count; i++) { if (UIManager.Instance.inputFieldTime[i] < replayTimer) { //如果小于的话,直接删除这个元素 foreach (var input in UIManager.Instance.inputFields) { if (input == null) continue; foreach (var name in data.inputFieldDic[UIManager.Instance.inputFieldTime[i]].Keys) { if (input.name == name) { try { input.onValueChanged.Invoke(data.inputFieldDic[UIManager.Instance.inputFieldTime[i]][UIManager.Instance.inputFieldName[i]]); UIManager.Instance.inputFieldName.Remove(name); UIManager.Instance.inputFieldTime.Remove(UIManager.Instance.inputFieldTime[i]); return; } catch (Exception e) { Debug.Log(e.ToString()); } } } } } } //对回放的InputToggle进行复现 for (int i = 0; i < UIManager.Instance.inputToggleTime.Count; i++) { if (UIManager.Instance.inputToggleTime[i] < replayTimer) { //如果小于的话,直接删除这个元素 foreach (var toggle in UIManager.Instance.toggles) { if (toggle == null) continue; foreach (var name in data.toggleDic[UIManager.Instance.inputToggleTime[i]].Keys) { //如果InputToggle的名字重复的话,有时候就会判断错误,所以需要给他再添加一个判定方式 //这里用增加一个父物体来进行判定 if (toggle.transform.parent.name + toggle.name == name) { try { Debug.Log("当前Toggle的IsOn为:" + data.toggleDic[UIManager.Instance.inputToggleTime[i]][UIManager.Instance.togglesName[i]]); toggle.isOn = data.toggleDic[UIManager.Instance.inputToggleTime[i]][UIManager.Instance.togglesName[i]]; toggle.onValueChanged.Invoke(data.toggleDic[UIManager.Instance.inputToggleTime[i]][UIManager.Instance.togglesName[i]]); UIManager.Instance.inputToggleTime.Remove(UIManager.Instance.inputToggleTime[i]); UIManager.Instance.togglesName.Remove(UIManager.Instance.togglesName[i]); return; } catch (Exception e) { Debug.Log(e.ToString()); } } } } } } //对回放的DropDown的按钮进行复现 for (int i = 0; i < UIManager.Instance.dropDownTime.Count; i++) { if (UIManager.Instance.dropDownTime[i] < replayTimer) { //如果小于的话,直接删除这个元素 foreach (var dropdown in UIManager.Instance.dropdowns) { if (dropdown == null) continue; foreach (var name in data.dropDownDic[UIManager.Instance.dropDownTime[i]].Keys) { //如果Toggle的名字重复的话,有时候就会判断错误,所以需要给他再添加一个判定方式 //这里用增加一个父物体来进行判定 if (dropdown.transform.parent.name + dropdown.name == name) { try { dropdown.value = data.dropDownDic[UIManager.Instance.dropDownTime[i]][name]; dropdown.onValueChanged.Invoke(data.dropDownDic[UIManager.Instance.dropDownTime[i]][name]); UIManager.Instance.dropDownTime.Remove(UIManager.Instance.dropDownTime[i]); UIManager.Instance.dropDownName.Remove(UIManager.Instance.dropDownName[i]); return; } catch (Exception e) { Debug.Log(e.ToString()); } } } } } } for (int i = 0; i < pressTimeA.Count; i++) { if (pressTimeA[i] < replayTimer)//当前时间如果大于按下的时间的话,那么就调用人物移动的方法 { if (pressTimeA[i] < liftUpTimeA[i]) { Atime += Time.deltaTime * replaySpeed; if (Atime < liftUpTimeA[i] - pressTimeA[i]) { playerMove.transform.Translate(-data.AoriginPlayerTimeAndSpeedDic[pressTimeA[i]] * replaySpeed, 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] < replayTimer)//当前时间如果大于按下的时间的话,那么就调用人物移动的方法 { if (pressTimeW[i] < liftUpTimeW[i]) { Wtime += Time.deltaTime * replaySpeed; if (Wtime < liftUpTimeW[i] - pressTimeW[i]) { playerMove.transform.Translate(0, 0, data.WoriginPlayerTimeAndSpeedDic[pressTimeW[i]] * replaySpeed, 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] < replayTimer)//当前时间如果大于按下的时间的话,那么就调用人物移动的方法 { if (pressTimeS[i] < liftUpTimeS[i]) { Stime += Time.deltaTime * replaySpeed; if (Stime < liftUpTimeS[i] - pressTimeS[i]) { playerMove.transform.Translate(0, 0, -data.SoriginPlayerTimeAndSpeedDic[pressTimeS[i]] * replaySpeed, 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] < replayTimer)//当前时间如果大于按下的时间的话,那么就调用人物移动的方法 { if (pressTimeD[i] < liftUpTimeD[i]) { Dtime += Time.deltaTime * replaySpeed; if (Dtime < liftUpTimeD[i] - pressTimeD[i]) { playerMove.transform.Translate(data.DoriginPlayerTimeAndSpeedDic[pressTimeD[i]] * replaySpeed, 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] < replayTimer)//当前时间如果大于按下的时间的话,那么就调用人物旋转的方法 { if ((i + 1) < mouseUpTime.Count) { rotationTime = mouseUpTime[i + 1] - mouseUpTime[i]; playerMove.transform.DORotate(new Vector3(data.playerUpRotX[mouseUpTime[i]], data.playerUpRotY[mouseUpTime[i]], data.playerUpRotZ[mouseUpTime[i]]), rotationTime / (replaySpeed)); data.playerUpRotX.Remove(mouseUpTime[i]); data.playerUpRotY.Remove(mouseUpTime[i]); data.playerUpRotZ.Remove(mouseUpTime[i]); mouseUpTime.Remove(mouseUpTime[i]); } } } } //重新回放时清除之前所有的数据 public void ClearData() { UIManager.Instance.buttonEventTime.Clear(); UIManager.Instance.inputFieldTime.Clear(); UIManager.Instance.inputFieldName.Clear(); UIManager.Instance.inputToggleTime.Clear(); UIManager.Instance.dropDownTime.Clear(); UIManager.Instance.togglesName.Clear(); UIManager.Instance.dropDownName.Clear(); UIManager.Instance.goChangeTime.Clear(); pressTimeW.Clear(); liftUpTimeW.Clear(); pressTimeA.Clear(); liftUpTimeA.Clear(); pressTimeS.Clear(); liftUpTimeS.Clear(); pressTimeD.Clear(); liftUpTimeD.Clear(); mouseUpTime.Clear(); //在这里首先判断出来是否有任务准备和射后恢复阶段 string Processcontent = ""; } public void GetData(ReplayData data) { //根据得到的所有命令,在这里开始播放 foreach (var item in data.buttonDic.Keys) { UIManager.Instance.buttonEventTime.Add(item); } foreach (var item in data.inputFieldDic.Keys) { UIManager.Instance.inputFieldTime.Add(item); } foreach (var item in data.inputFieldDic.Keys) { foreach (var name in data.inputFieldDic[item].Keys) { UIManager.Instance.inputFieldName.Add(name); } } foreach (var item in data.toggleDic.Keys) { UIManager.Instance.inputToggleTime.Add(item); } foreach (var item in data.toggleDic.Keys) { foreach (var name in data.toggleDic[item].Keys) { UIManager.Instance.togglesName.Add(name); } } foreach (var item in data.dropDownDic.Keys) { foreach (var name in data.dropDownDic[item].Keys) { UIManager.Instance.dropDownName.Add(name); } } foreach (var item in data.goNameDic.Keys) { UIManager.Instance.goChangeTime.Add(item); } foreach (var item in data.dropDownDic.Keys) { UIManager.Instance.dropDownTime.Add(item); } //得到所有移动和旋转按键按下时的时间 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); } //在这里让replayPanel出现 replayPanel.gameObject.SetActive(true); this.data = data; } //开始回放 public void StartProcess() { replayTimer = 0; rePlay = true; } //获取所有的玩家位置 public void GetCurrentPos(DictionaryplayerPosX, Dictionary playerPosY, Dictionary playerPosZ) { playerPosX.Add(timer, transform.position.x); playerPosY.Add(timer, transform.position.y); playerPosZ.Add(timer, transform.position.z); } //从服务器得到之前保存的回放数据 //[Rpc] public void GetFirstRePlayDataResult(ReplayData data) { this.data = data; Debug.Log("得到文件成功"); GetData(this.data); //获取之前保存的回放时的总时长 replayPanel.totalTime = FormatNumberDateTime(((int)data.totalTimer).ToString()); } /// /// 秒转换成时分秒(分的) /// /// ///public string FormatNumberDateTime(string secondTime) { if (string.IsNullOrEmpty(secondTime)) { return ""; } else { long mss = long.Parse(secondTime); string DateTimes = null; long days = mss / (60 * 60 * 24); long hours = (mss % (60 * 60 * 24)) / (60 * 60); long minutes = (mss % (60 * 60)) / 60; long seconds = mss % 60; if (minutes > 0) { DateTimes = minutes + ":" + seconds; } else { DateTimes = "00:" + seconds; } return DateTimes; } } }
接下来是UI面板控制回放
using MVC;
//using Net.Share;
using System;
using System.Collections;
using System.Collections.Generic;
using UI;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Video;
public class ReplayPanel : BasePanel
{ // 开始流程 重新开始流程
private Button startProcessBtn, reStartProcess;
private Button returnProcessBtn;//退出回放流程
private Text speedText, totalTimeText;
protected override void Awake()
{
base.Awake();
InitComponent();
AddListener();
}
public override void InitComponent()
{
base.InitComponent();
startProcessBtn = transform.Find("StartProcessBtn").GetComponent
接下来是通用单例类,如果项目中有单例类的话就不用写这个类了。
using System.Collections; using System.Collections.Generic; using UnityEngine; ////// 脚本单例类 /// public abstract class MonoSingleton: MonoBehaviour where T : MonoSingleton { //public static T Instance { get; private set; } //private void Awake() //{ // Instance = this as T; //} //何时何地都可以正确访问Instance? //按需加载 private static T instance; public static T Instance { get { if (instance == null) { //instance = this as T; instance = FindObjectOfType (); //如果在场景中没有找到该对象 if (instance == null) { //创建脚本对象,立即执行Awake,为instance赋值。 new GameObject("Singleton of " + typeof(T)).AddComponent (); } else { instance.Initialize(); } } return instance; } } public static T I { get { return Instance; } } protected virtual void Initialize() { } protected virtual void Awake() { if (instance == null) { instance = this as T; Initialize(); } } }
接下来就是保存的数据类了
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
[Serializable]
public class ReplayData
{
public ReplayData()
{
buttonDic = new Dictionary();
toggleDic = new Dictionary>();
dropDownDic = new Dictionary>();
inputFieldDic = new Dictionary>();
WoriginPlayerTimeAndSpeedDic = new Dictionary();
WendPlayerTimeAndSpeedDic = new Dictionary();
AoriginPlayerTimeAndSpeedDic = new Dictionary();
AendPlayerTimeAndSpeedDic = new Dictionary();
SoriginPlayerTimeAndSpeedDic = new Dictionary();
SendPlayerTimeAndSpeedDic = new Dictionary();
DoriginPlayerTimeAndSpeedDic = new Dictionary();
DendPlayerTimeAndSpeedDic = new Dictionary();
AplayerPosX = new Dictionary();
AplayerPosY = new Dictionary();
AplayerPosZ = new Dictionary();
WplayerPosX = new Dictionary();
WplayerPosY = new Dictionary();
WplayerPosZ = new Dictionary();
SplayerPosX = new Dictionary();
SplayerPosY = new Dictionary();
SplayerPosZ = new Dictionary();
DplayerPosX = new Dictionary();
DplayerPosY = new Dictionary();
DplayerPosZ = new Dictionary();
playerDownRotX = new Dictionary();
playerDownRotY = new Dictionary();
playerDownRotZ = new Dictionary();
playerDownRotW = new Dictionary();
playerUpRotX = new Dictionary();
playerUpRotY = new Dictionary();
playerUpRotZ = new Dictionary();
playerUpRotW = new Dictionary();
}
public float totalTimer = 0;
public Dictionary buttonDic;//时间、按钮名称
public Dictionary> toggleDic;//时间、toggle名称、按钮的bool值
public Dictionary> dropDownDic;//时间、DropDown名称、DropDown的index值
public Dictionary> inputFieldDic;//时间、input的名字,input里面的内容
public Dictionary WoriginPlayerTimeAndSpeedDic;//按下W键位的时间、速度
public Dictionary WendPlayerTimeAndSpeedDic; //松开W键位的时间、速度
public Dictionary AoriginPlayerTimeAndSpeedDic;//按下A键位的时间、速度
public Dictionary AendPlayerTimeAndSpeedDic; //松开A键位的时间、速度
public Dictionary SoriginPlayerTimeAndSpeedDic;//按下S键位的时间、速度
public Dictionary SendPlayerTimeAndSpeedDic; //松开S键位的时间、速度
public Dictionary DoriginPlayerTimeAndSpeedDic;//按下D键位的时间、速度
public Dictionary DendPlayerTimeAndSpeedDic; //松开D键位的时间、速度
public Dictionary AplayerPosX;//按下A键位时当前时间、当前玩家位置X坐标
public Dictionary AplayerPosY;//按下A键位时当前时间、当前玩家位置Y坐标
public Dictionary AplayerPosZ;
public Dictionary WplayerPosX;//当前时间,当前玩家位置X坐标
public Dictionary WplayerPosY;//当前时间、当前玩家位置Y坐标
public Dictionary WplayerPosZ;
public Dictionary SplayerPosX;//当前时间、当前玩家位置X坐标
public Dictionary SplayerPosY;//当前时间、当前玩家位置Y坐标
public Dictionary SplayerPosZ;
public Dictionary DplayerPosX;//当前时间、当前玩家位置X坐标
public Dictionary DplayerPosY;//当前时间、当前玩家位置Y坐标
public Dictionary DplayerPosZ;
public Dictionary playerDownRotX;//当前时间、当前玩家旋转X坐标
public Dictionary playerDownRotY;
public Dictionary playerDownRotZ;
public Dictionary playerDownRotW;
public Dictionary playerUpRotX;//当前时间、当前玩家旋转X坐标
public Dictionary playerUpRotY;
public Dictionary playerUpRotZ;
public Dictionary playerUpRotW;
}
在程序中按下UI(比如按钮、toggle、在InputField)时,将此时按下的时间记录下来,保存到程序中。例如在按下按钮、Toggle、InputField触发事件时,同时也将数据进行记录。
//按下按钮
if (!ReplayManager.Instance.rePlay)
ReplayManager.Instance.data.buttonDic.Add(ReplayManager.Instance.timer, submitButton.name);
//按下Toggle开关
if (!ReplayManager.Instance.rePlay)
{
ReplayManager.Instance.data.toggleDic.Add(ReplayManager.Instance.timer, new Dictionary());
ReplayManager.Instance.data.toggleDic[ReplayManager.Instance.timer].Add(item.parent.name + item.GetComponent().name, isOn);
}
//在InputField中输入文字
if (!ReplayManager.Instance.rePlay)
{
ReplayManager.Instance.data.inputFieldDic.Add(ReplayManager.Instance.timer, new Dictionary());
ReplayManager.Instance.data.inputFieldDic[ReplayManager.Instance.timer].Add(input.gameObject.name, content);
}
在最后保存的时候,将ReplayData类发送给服务器,将ReplayData中的数据转化为二进制文件进行保存,保存的代码为
////// 保存回放记录 /// /// 玩家用户名 /// 任务名称 /// 数据类 [Rpc] public void SetRePlayData(string playerAcc, string taskName, ReplayData data) { //首先设置数据库信息 db = new SQLServerDatabase(0); db.UpdateValues("Student_TaskPlan", new string[] { "hasReplay" }, new string[] { "true" }, "playerAcc", playerAcc, "TaskName", taskName); int numberOfReplayTime = serverManager.player.numberOfTask + 1; string rootPath = Environment.CurrentDirectory.ToString(); string path = rootPath + "\" + "ReplayFile" + "\" + playerAcc;//通过人物名生成第一个文件夹 string path2 = path + "\" + taskName; string path3 = path2 + "\" + "第" + numberOfReplayTime.ToString() + "次回放.rep";//保存第几次考核的回放 DirectoryInfo d = Directory.CreateDirectory(path);//创建根目录 返回值是一个文件夹 DirectoryInfo d2 = Directory.CreateDirectory(path2); File.Create(path3).Close(); ReplayData replayData = new ReplayData(); replayData = data; BinaryFormatter bf = new BinaryFormatter(); using (FileStream fs = File.Create(path3)) { bf.Serialize(fs, replayData); } //发送给玩家,告诉玩家回放已经保存成功 serverManager.Send(serverManager.player, "SetRePlayDataResult", "保存回放成功"); }
//在这里是读取之前保存的回放数据
///用户名、任务名称、想要查看第几次回放
public void GetRePlayData(string playerAcc, string taskName, string numberOfTask, bool isFirst)
{
string rootPath = Environment.CurrentDirectory.ToString();
string path = rootPath + "\" + "ReplayFile" + "\" + playerAcc + "\" + taskName + "\" + numberOfTask + ".rep";
BinaryFormatter bf = new BinaryFormatter();
ReplayData data = new ReplayData();
using (FileStream fs = File.Open(path, FileMode.Open, FileAccess.Read))
{
//创建data类
data = bf.Deserialize(fs) as ReplayData;
}
if (isFirst)
serverManager.SendRT(serverManager.player, "GetFirstRePlayDataResult", data);
else
{
serverManager.SendRT(serverManager.player, "GetRePlayDataResult", data);
}
}
总结
写到这里差不多就将回放功能的大致框架完成了,中间可能有些乱,但是写这篇文章的初衷就是给自己以后看的,大致内容还是比较清楚的。如果有看到这篇文章后想要了解更多、或者有什么不懂的地方的朋友可以加我一起讨论。附带Q:770187276



