一、基础撤销实现:
参考内容:unity推箱子合成类游戏的回退撤销操作_哔哩哔哩_bilibili
首先创建一个Gamemanger类,用于控制所有的移动物品,挂载到一个空物体上
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameManager : MonoBehaviour
{
public static GameManager Instance;
//List存储所有可移动的物体
List<GameObject> Moveobjs = new List<GameObject>();
//栈存放每一步移动的数据
Stack<List<GameObject>> stack = new Stack<List<GameObject>>();
//单例模式
private void Awake()
{
if (Instance)
{
Destroy(gameObject);
}
else
{
Instance = this;
}
}
private void Update()
{
//测试返回上一步
if(Input.GetKeyUp(KeyCode.O))
{
UnDo();
}
}
#region Undo
//将移动物体添加到List
public void Rigist(GameObject obj)
{
Moveobjs.Add(obj);
}
//删除物体
public void Remove(GameObject obj)
{
Moveobjs.Remove(obj);
}
//保存这一步数据状态
public void Save()
{
List<GameObject> temp = new List<GameObject>();
foreach (GameObject item in Moveobjs)
{
//实例化这一步的状态备用
GameObject tp = Instantiate(item.gameObject,item.transform.position,Quaternion.identity);
tp.SetActive(false);
//加入临时List
temp.Add(tp);
}
//将liist压入栈内
stack.Push(temp);
}
//撤销操作,返回上一步
public void UnDo()
{
//判断栈是否是空的
if (stack.Count == 0) return;
//删除当前物体
foreach(var item in Moveobjs)
{
Destroy(item);
}
//取出栈里的最新一步存储,启用
List<GameObject> temp = stack.Pop();
foreach(var item in temp)
{
item.SetActive(true);
}
}
#endregion
}
再创建一个UnDo脚本,挂载到每一个需要移动的物体上,脚本内容为:
void Start()
{
//开始时将可移动物体加入list
GameManager.Instance.Rigist(gameObject);
}
private void OnDestroy()
{
//移除list中的物体
GameManager.Instance.Remove(gameObject);
}
在每次对物体移动等操作之前,先调用Gamemanager里面的存储方法
GameManager.Instance.Save();
保存之后再操作,想要撤销时,调用Gamemanager里面的UnDo方法即可。
二、优化版本:
由于基础功能版本不断地实例化和销毁物体,性能消耗过大,所以优化后的情况如下
首先新建拖拽脚本 Drag
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 拖拽移动、角度旋转回撤测试
/// </summary>
public class Drag : MonoBehaviour
{
//拖拽测试
private IEnumerator OnMouseDown()
{
//将三维物体坐标转换成屏幕坐标
Vector3 screenPosition = Camera.main.WorldToScreenPoint(transform.position);
//鼠标屏幕坐标
Vector3 currentScreenSpace = new Vector3(Input.mousePosition.x, Input.mousePosition.y, screenPosition.z);
//将鼠标屏幕坐标转换成三维坐标
Vector3 mouseWorldPosition = Camera.main.ScreenToWorldPoint(currentScreenSpace);
//计算物体位置与鼠标之间的距离
Vector3 offset = transform.position - mouseWorldPosition;
//提前定义好返回值
var cs = new WaitForFixedUpdate();
//当按下鼠标左键时
if (Input.GetMouseButtonDown(0))
{
UndoRedoManager.Instance.RecordUndo(gameObject);
}
while (Input.GetMouseButton(0))
{
//更新鼠标屏幕坐标
currentScreenSpace = new Vector3(Input.mousePosition.x, Input.mousePosition.y, screenPosition.z);
//将鼠标屏幕坐标转换成三维坐标
mouseWorldPosition = Camera.main.ScreenToWorldPoint(currentScreenSpace);
//移动物体坐标
Vector3 currentPosition = mouseWorldPosition + offset;
//将物体坐标设置成移动后的坐标
transform.position = currentPosition;
//返回 (只有当下一次fixedUpdate开始时再执行后续代码)
yield return cs;
}
}
//角度旋转测试
private void Update()
{
if(Input.GetKeyDown(KeyCode.P))
{
UndoRedoManager.Instance.RecordUndo(gameObject);
transform.RotateAround(transform.position,transform.right,30);
}
}
}
将 Drag 脚本放置在需要拖拽的物体身上,创建脚本 TransformUndoData
using UnityEngine;
using System.Collections.Generic;
// 用于存储物体变换信息的类
public class TransformUndoData
{
public GameObject GameObject { get; private set; }
public Vector3 OriginalPosition { get; private set; }
public Quaternion OriginalRotation { get; private set; }
public Vector3 OriginalScale { get; private set; }
public TransformUndoData(GameObject gameObject)
{
GameObject = gameObject;
OriginalPosition = gameObject.transform.position;
OriginalRotation = gameObject.transform.rotation;
OriginalScale = gameObject.transform.localScale;
}
// 恢复物体的原始变换
public void RestoreOriginal()
{
GameObject.transform.position = OriginalPosition;
GameObject.transform.rotation = OriginalRotation;
GameObject.transform.localScale = OriginalScale;
}
}
然后再创建一个脚本 UndoRedoManager
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 声明栈,定义撤销前存储、撤销功能
/// </summary>
public class UndoRedoManager : MonoBehaviour
{
public static UndoRedoManager Instance { get; private set; }
private Stack<List<TransformUndoData>> undoStack = new Stack<List<TransformUndoData>>();
void Awake()
{
if (Instance == null)
{
Instance = this;
DontDestroyOnLoad(gameObject);
}
else
{
Destroy(gameObject);
}
}
private void Update()
{
if(Input.GetKeyUp(KeyCode.Z))
{
Undo();
}
}
private void OnDestroy()
{
ClearStack();
}
// 执行可撤销的操作
public void RecordUndo(params GameObject[] gameObjects)
{
List<TransformUndoData> dataList = new List<TransformUndoData>();
foreach (GameObject obj in gameObjects)
{
dataList.Add(new TransformUndoData(obj));
}
undoStack.Push(dataList);
}
// 撤销操作
public void Undo()
{
if (undoStack.Count > 0)
{
List<TransformUndoData> dataList = undoStack.Pop();
foreach (TransformUndoData data in dataList)
{
data.RestoreOriginal();
}
}
}
//清空栈
public void ClearStack()
{
undoStack.Clear();
}
}
脚本挂载到camera或者自定义空物体上,我在里面直接使用了,方法调用,如果需要可以自己再封装。