Unity Undo(撤销)详解
发表于2018-04-18
Ctrl + z这对按键组合应该为广大计算机使用者所熟知,即用来做撤销操作。在没有热键屏蔽的情况下(搜狗输入法,QQ等软件可能会抢占某些常用按键组合的优先使用权从而导致我们按下按钮发现没有反应),Unity也可以通过Ctrl+z的组合来进行很多撤销。
比如我们在场景中新建一个Cube,按下Ctrl+ z(或者菜单栏的Edit->Undo),新建的这个Cube就从场景中消失了。
Undo的机制
Unity中Undo使用的数据结构基于Stack,采用LIFO(Last In First Out)的策略,越晚进栈的越早被弹出。


撤销创建一个对象
创建一个新的空白场景。
创建一个用于Undo的对象
下面的代码是用于生成立方码。Example/Create Cube您可以生成由执行多维数据集。
using UnityEngine; using UnityEditor; public class Example { [MenuItem("Example/Create Cube")] static void CreateCube () { GameObject.CreatePrimitive (PrimitiveType.Cube); } }

发现Ctrl+z 无法撤销我们的创建操作。由于我们单纯地用了代码来创建这么一个Cube,记录操作的栈并没有记录之前的状态,所以这个操作无法撤销。
针对创建新GameObject的操作,我们使用Undo.RegisterCreatedObjectUndo来记录之前的状态。
让我们重新创建一个新的场景(为了避免使用之前的Undo栈)
using UnityEngine; using UnityEditor; public class Example { [MenuItem("Example/Create Cube")] static void CreateCube () { var cube = GameObject.CreatePrimitive (PrimitiveType.Cube); Undo.RegisterCreatedObjectUndo (cube, "Create Cube"); } }
我们使用上面的代码来创建新的Cube,这次使用Undo.RegisterCreatedObjectUndo (cube, "Create Cube");记录了状态,所以可以撤销创建新Cube的操作了。
撤消游戏体属性的变化
下面给出一段旋转GameObject的代码,Example/Random Rotate可以让物体旋转一个随机的角度
using UnityEngine; using UnityEditor; public class Example { [MenuItem("Example/Random Rotate")] static void RandomRotate () { var transform = Selection.activeTransform; if (transform) { transform.rotation = Random.rotation; } } }

上面的代码里没有Undo相关的代码,所以并不能撤销随机角度旋转的操作。
这里我们使用Undo.RecordObject来记录Object的属性。
using UnityEngine; using UnityEditor; public class Example { [MenuItem("Example/Random Rotate")] static void RandomRotate () { var transform = Selection.activeTransform; if (transform) { Undo.RecordObject (transform, "Rotate " + transform.name); transform.rotation = Random.rotation; } } }
旋转之后Ctrl+z,bingo!Cube的rotation重置了!
撤销UnityEngine.Object
下面来讲讲继承自UnityEngine.Object对象的撤销(继承自UnityEngine.Object的对象都是可序列化的)
经常Undo的对象包括下面三种:
- 游戏对象
- Component(也包括MonoBehaviour)
- ScriptableObject
当要Undo用System.Serializable修饰的属性的时候,如下操作,Example/Change PlayerInfo后撤销就回到之前的状态了。
[System.Serializable] public class PlayerInfo { public string name; public int hp; }
示例Player
using UnityEngine; public class Player : MonoBehaviour { [SerializeField] PlayerInfo info; }
using UnityEngine; using UnityEditor; public class Example { [MenuItem("Example/Change PlayerInfo")] static void ChangePlayerInfo () { var player = Selection.activeGameObject.GetComponent<Player> (); if (player) { Undo.RecordObject (player, "Change PlayerInfo"); player.info = new PlayerInfo{ name = "New PlayerName", hp = Random.Range(0,10) }; } } }
Undo的类型
- Undo.RecordObject(s)记录Object的状态
- Undo.AddComponent 要添加Component的时候使用,可以撤销掉新加的Component
- Undo.RegisterCreatedObjectUndo 新建Object的时候使用,可以撤销新建的物体
- Undo.DestroyObjectImmediate 要删除Object的时候使用,可以撤销删除操作
- Undo.SetTransformParent 修改Transform的Parent的时候使用,可以恢复层次结构
Revert
Revert是和Record相反的操作,相当于Ctrl+z

Undo中有Group的概念,通过Undo.IncrementCurrentGroup()来增大groupID,一个group可以包含多个记录并可以同时操作。
- Undo.RevertAllInCurrentGroup() 撤回当前groupID的所有操作
- Undo.IncrementCurrentGroup() 把记录的groupId加1
- Undo.RevertAllDownToGroup(int groupID) 回退到groupID的状态
- Undo.CollapseUndoOperations (int groupID) 把groupID组中的记录折叠,一次Ctrl+z即可回退该组所有记录。来自:https://blog.csdn.net/rickshaozhiheng/article/details/52769156