Undo的程序设计基于以下想法:
一、所有对象从根本上来讲都是以由基本的数据类型组成的,包括int、float、bool、string等等。
二、undo从根本上是将上述基本类型的值设置回操作之前的值。
三、每次一操作都是对某些特定对象的属性修改,undo的作用是将这些属性设置为修改之前的值。
四、对象的每个属性都通过事件驱动具体的内容。
下面是一个UndoManager类,该类里面包含了一个undo操作List,而List的每一项都是一个UnityAction堆栈。
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
public class UndoManager : MonoBehaviour
{
static public UndoManager instance;
void Awake()
{
instance = this;
}
void Update()
{
if(Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl))
{
if (Input.GetKeyDown(KeyCode.Z))
{
Undo();
}
}
if(Input.GetMouseButtonUp(2)) Undo();
}
int undoLimit = 5;
public void SetUndoLimit(int undoLimit) { this.undoLimit = undoLimit; }
List<Stack<UnityAction>> listUndoOperation = new List<Stack<UnityAction>>();
Stack<UnityAction> curUndoOperation = null;
public void FinishCurrentOperation()
{
if (curUndoOperation == null) return;
listUndoOperation.Add(curUndoOperation);
while (listUndoOperation.Count > undoLimit)
{
listUndoOperation.RemoveAt(0);
}
curUndoOperation = null;
}
public void AddUndoAct(UnityAction act)
{
if (curUndoOperation == null)
{
curUndoOperation = new Stack<UnityAction>();
}
curUndoOperation.Push(act);
}
UnityAction onUndoEmpty;
public void AddActUndoEmpty(UnityAction act) { onUndoEmpty -= act; onUndoEmpty += act; }
public void RemoveActUndoEmpty(UnityAction act) { onUndoEmpty -= act; }
public void Undo()
{
if (listUndoOperation.Count > 0)
{
int index = listUndoOperation.Count - 1;
Stack<UnityAction> op = listUndoOperation[index];
if (op != null)
{
while(op.Count > 0)
{
op.Pop()?.Invoke();
}
}
listUndoOperation.RemoveAt(index);
}
else
{
onUndoEmpty?.Invoke();
}
}
}
一个Vector3驱动对象,这个对象用于驱动类似Tranform的position之类的内容。为了编程方面,里面添加了一些隐式转换,加减法操作等内容。
using UnityEngine;
using UnityEngine.Events;
public class ActVector3
{
public ActVector3(Vector3 val, UnityAction<Vector3> onChangeVal)
{
v = val;
this.onChangeVal = onChangeVal;
}
UnityAction<Vector3> onChangeVal;
public void AddActChangeVal(UnityAction<Vector3> act) { onChangeVal -= act; onChangeVal += act; }
public void RemoveActChangeVal(UnityAction<Vector3> act) { onChangeVal -= act; }
bool addUndoEnable = true;
Vector3 v;
public Vector3 val
{
get
{
return v;
}
set
{
Vector3 vPre = v;
v = value;
if (v != vPre) onChangeVal(v);
//
if (addUndoEnable)
{
UndoManager.instance.AddUndoAct(() =>
{
addUndoEnable = false;
val = vPre;
addUndoEnable = true;
});
}
}
}
public static implicit operator Vector3(ActVector3 actVector3)
{
return actVector3.val;
}
public static implicit operator ActVector3(Vector3 vector3)
{
return new ActVector3(vector3, null);
}
public static ActVector3 operator +(ActVector3 actVector3, Vector3 vector3)
{
actVector3.val += vector3;
return actVector3;
}
public static ActVector3 operator +(Vector3 vector3, ActVector3 actVector3)
{
return actVector3 + vector3;
}
public static ActVector3 operator -(ActVector3 actVector3, Vector3 vector3)
{
actVector3.val -= vector3;
return actVector3;
}
public static ActVector3 operator -(Vector3 vector3, ActVector3 actVector3)
{
return vector3 - actVector3.val;
}
}
为了验证上述内容的可行性,这是一个使用案例。
using System.Collections;
using UnityEngine;
public class OpTest : MonoBehaviour
{
[SerializeField]
Transform tran;
ActVector3 actPosition;
void Start()
{
if (tran)
{
actPosition = new ActVector3(transform.position, (val) => { tran.position = val; });
StartCoroutine(ChangePos());
}
}
IEnumerator ChangePos()
{
yield return new WaitForSeconds(1);
actPosition += Vector3.left;
actPosition += Vector3.left;
actPosition += Vector3.left;
UndoManager.instance.FinishCurrentOperation();
yield return new WaitForSeconds(1);
actPosition += Vector3.up;
actPosition += Vector3.up;
actPosition += Vector3.up;
actPosition += Vector3.up;
UndoManager.instance.FinishCurrentOperation();
yield return new WaitForSeconds(1);
actPosition += Vector3.forward;
actPosition += Vector3.forward;
UndoManager.instance.FinishCurrentOperation();
yield return new WaitForSeconds(1);
actPosition += Vector3.right;
actPosition += Vector3.right;
actPosition += Vector3.right;
UndoManager.instance.FinishCurrentOperation();
yield return new WaitForSeconds(1);
actPosition += Vector3.down;
actPosition += Vector3.down;
actPosition += Vector3.down;
actPosition += Vector3.down;
UndoManager.instance.FinishCurrentOperation();
yield return new WaitForSeconds(1);
actPosition += Vector3.back;
actPosition += Vector3.back;
UndoManager.instance.FinishCurrentOperation();
}
}
基于上述脚本对物体状态进行改变之后,可以按下鼠标中键或者Ctrl+Z来执行Undo。