Unity3D Editor Undo回退效果实现1
Posted 暗光之痕
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Unity3D Editor Undo回退效果实现1相关的知识,希望对你有一定的参考价值。
环境:Unity2021.1.14 Odin3.0.4 语言:C#
面向:Editor开发人员
文章参考:
http://49.233.81.186/undo.html
总起
因为Unity在用于中小型游戏开发的时候并不太需要对Editor进行深度扩展,而现阶段使用Unity进行大型游戏的开发比较少,所以Editor相关的资料相对匮乏。
安藤圭吾的《エディター拡張入門》作为为数不多的Unity Editor入门经典,内容丰富,并且深入讲述了很多原理性的知识,非常值得一看。
本文主要基于其书的第12章,并结合自己的开发经验,针对Undo进行讲解。
体验Undo操作
在Hierarchy视图中右键,并选择3D Object/Cube,创建一个Cube:
接下来按下Ctrl + Z或点击菜单栏中Edit/Undo Create Cube撤销操作:
可以看到Cube创建的操作被撤销了,当然我们可以按下Ctrl + Y或点击菜单栏中Edit/Redo Create Cube重做创建Cube的操作。
撤销实现的原理实际是维护了一个堆栈,当有新的更改产生之前,将之前状态存起来,等到需要重做时再恢复之前的状态。
实现Undo操作
创建一个Cube的一般代码:
using UnityEditor;
using UnityEngine;
public class Test2 : MonoBehaviour
{
[MenuItem("Test/Test2/Create Cube")]
static void CreateCube()
{
GameObject.CreatePrimitive(PrimitiveType.Cube);
}
}
使用上述代码直接进行创建Cube是无法进行撤销的,这里最关键的原因是没有在Undo的堆栈中进行注册。
让我们在创建cube之后注册一下:
using UnityEditor;
using UnityEngine;
public class Test2 : MonoBehaviour
{
[MenuItem("Test/Test2/Create Cube")]
static void CreateCube()
{
var cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
Undo.RegisterCreatedObjectUndo(cube, "Create Cube");
}
}
这样Undo效果就顺利实现了,我们再来看一个Undo改变Transform属性的例子:
[MenuItem("Test/Test2/Random Rotate")]
static void RandomRotate()
{
var tr = Selection.activeTransform;
if (tr != null)
{
Undo.RecordObject(tr, "Rotate " + tr.name);
tr.rotation = Random.rotation;
}
}
注意:Undo.RecordObject是将之前的属性保存到堆栈中,必须在属性改变之前调用。(注意一下调用顺序,我已经被坑了好几次了)
Profiler中的一些细节
Profiler中的Undo截图:
PropertyDiffUndoRecorder函数执行顺序:
- RecordObjec;
- 更改值;
- 运行Flush,Undo.willFlushUndoRecord在Flush之前执行;
- Modification输出,Undo.postprocessModifications在输出之后执行。
我们在使用Undo.RecordObject时,内存中只会记录属性的差别,因此内存占用比较少,而Undo. RegisterCompleteObjectUndo会记录整个对象,在属性数量多的情况下导致内存暴增。
关于以上的内存问题,我这边做了一些测试:
public class Test2SerializedData : ScriptableObject
{
public Test2SerializedData()
{
SetStrValue("");
}
public void SetStrValue(string lastRow)
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 100; i++)
{
for (int j = 0; j < 10; j++)
{
sb.Append("a");
}
sb.Append("\\n");
}
sb.Append(lastRow);
strValue = sb.ToString();
}
public string strValue;
public int intValue;
}
public class Test2 : MonoBehaviour
{
public Test2SerializedData data = null;
[Button]
public void CreateValue()
{
data = ScriptableObject.CreateInstance<Test2SerializedData>();
}
[Button]
public void ChangeValue()
{
Undo.RecordObject(data, "Test2SerializedData Changed");
data.intValue++;
Debug.LogError("change to " + data.intValue);
}
}
Test2SerializedData是一个需要Undo功能的数据类,它保存了一个巨大的字符串变量strValue和一个intValue。
(其中Button的Attribute是Odin的功能,方便创建按钮,这边就直接使用了)
点击CreateValue,我们在Profiler中就能看到5.8KB的内存占用:
然后点击ChangeValue 100次,105.5KB的占用:
接下来我分别测试了使用RegisterCompleteObjectUndo替代RecordObject、改变strValue的最后一行内容、strValue从string替换成string[]存储每一行的内容。
结果如下:
RecordObject 105.5KB
RegisterCompleteObjectUndo 222.6KB
RecordObject 改strValue最后一行 322.8KB
RegisterCompleteObjectUndo改strValue最后一行 218.2KB
RecordObject 改strValue最后一行 strValue替换string[]类型 126.9KB
RegisterCompleteObjectUndo改strValue最后一行 strValue替换string[]类型 278.9KB
结论:
- 在成员变量都占用内存不大的情况下,使用RecordObject能最有效优化内存;
- 在成员变量占用内存巨大的情况下,RecordObject反而内存会暴增;
- 使用string[]可以改善内存暴增的情况;
- 但string[]如果数量发生变化,会回退到内存会暴增的情况,因此处理占用内存巨大的成员变量需要根据实际情况来进行定夺。
以上是关于Unity3D Editor Undo回退效果实现1的主要内容,如果未能解决你的问题,请参考以下文章
Unity3D Editor Undo回退效果实现3 Odin相关