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函数执行顺序:

  1. RecordObjec;
  2. 更改值;
  3. 运行Flush,Undo.willFlushUndoRecord在Flush之前执行;
  4. 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

结论:

  1. 在成员变量都占用内存不大的情况下,使用RecordObject能最有效优化内存;
  2. 在成员变量占用内存巨大的情况下,RecordObject反而内存会暴增;
  3. 使用string[]可以改善内存暴增的情况;
  4. 但string[]如果数量发生变化,会回退到内存会暴增的情况,因此处理占用内存巨大的成员变量需要根据实际情况来进行定夺。

以上是关于Unity3D Editor Undo回退效果实现1的主要内容,如果未能解决你的问题,请参考以下文章

Unity3D Editor Undo回退效果实现1

Unity3D Editor Undo回退效果实现1

Unity3D Editor Undo回退效果实现3 Odin相关

Unity3D Editor Undo回退效果实现3 Odin相关

undo表空间

[Oracle]理解undo表空间