![](https://i-blog.csdnimg.cn/blog_migrate/c8533e2461cc30fba4d59d81bbda967d.png)
unity序列化
So you are writing a really cool editor extension in Unity and things seem to be going really well. You get your data structures all sorted out are really happy with how the tool you have written works.
因此,您在Unity中编写了一个非常酷的编辑器扩展,一切似乎进行得很顺利。 您可以整理好所有的数据结构,这对您编写的工具的工作方式非常满意。
Then you enter and exit play mode.
然后进入和退出播放模式。
Suddenly all the data you had entered is gone and your tool is reset to the default, just initialized state. It’s very frustrating! “Why does this happen?” you ask yourself. The reason has to do with how the managed (mono) layer of Unity works. Once you understand it, then things get much easier :)
突然,您输入的所有数据都消失了,您的工具被重置为默认的初始化状态。 非常令人沮丧! “为什么会这样?” 你问自己。 原因与Unity的托管(单)层的工作方式有关。 一旦了解了,事情就会变得容易得多:)
What happens when an assembly is reloaded? When you enter / exit play mode or change a script Unity has to reload the mono assemblies, that is the dll’s associated with Unity.
重新加载装配时会发生什么? 当您进入/退出播放模式或更改脚本时,Unity必须重新加载单声道程序集,即与Unity关联的dll。
On the user side this is a 3 step process:
在用户方面,这是一个三步过程:
What this means is that for your data structures / information to survive an assembly reload you need to ensure that it can get serialized into and out of c++ memory properly. Doing this also means that (with some minor modifications) you can save this data structure to an asset file and reload it at a later date.
这意味着要使您的数据结构/信息能够在程序集重新加载后继续存在,您需要确保可以正确地将其序列化到c ++内存中以及从中取出。 这样做还意味着(稍作修改)您可以将该数据结构保存到资产文件中,并在以后重新加载。
How do I work with Unity’s serialization? The easiest way to learn about Unity serialization is by working through an example. We are going to start with a simple editor window, it contains a reference to a class which we want to make survive an assembly reload.
如何使用Unity的序列化? 学习Unity序列化的最简单方法是通过一个示例。 我们将从一个简单的编辑器窗口开始,它包含对一个类的引用,该类要在程序集重新加载后继续存在。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
using UnityEngine;
using UnityEditor;
public class MyWindow : EditorWindow
{
private SerializeMe m_SerialziedThing;
[MenuItem ("Window/Serialization")]
static void Init () {
GetWindow ();
}
void OnEnable ()
{
hideFlags = HideFlags.HideAndDontSave;
if (m_SerialziedThing == null)
m_SerialziedThing = new SerializeMe ();
}
void OnGUI () {
GUILayout.Label ("Serialized Things", EditorStyles.boldLabel);
m_SerialziedThing.OnGUI ();
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
using UnityEngine ;
using UnityEditor ;
public class MyWindow : EditorWindow
{
private SerializeMe m_SerialziedThing ;
[ MenuItem ( "Window/Serialization" ) ]
static void Init ( ) {
GetWindow ( ) ;
}
void OnEnable ( )
{
hideFlags = HideFlags . HideAndDontSave ;
if ( m_SerialziedThing == null )
m_SerialziedThing = new SerializeMe ( ) ;
}
void OnGUI ( ) {
GUILayout . Label ( "Serialized Things" , EditorStyles . boldLabel ) ;
m_SerialziedThing . OnGUI ( ) ;
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
using UnityEditor;
public struct NestedStruct
{
private float m_StructFloat;
public void OnGUI ()
{
m_StructFloat = EditorGUILayout.FloatField("Struct Float", m_StructFloat);
}
}
public class SerializeMe
{
private string m_Name;
private int m_Value;
private NestedStruct m_Struct;
public SerializeMe ()
{
m_Struct = new NestedStruct();
m_Name = "";
}
public void OnGUI ()
{
m_Name = EditorGUILayout.TextField( "Name", m_Name);
m_Value = EditorGUILayout.IntSlider ("Value", m_Value, 0, 10);
m_Struct.OnGUI ();
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
using UnityEditor ;
public struct NestedStruct
{
private float m_StructFloat ;
public void OnGUI ( )
{
m_StructFloat = EditorGUILayout . FloatField ( "Struct Float" , m_StructFloat ) ;
}
}
public class SerializeMe
{
private string m_Name ;
private int m_Value ;
private NestedStruct m_Struct ;
public SerializeMe ( )
{
m_Struct = new NestedStruct ( ) ;
m_Name = "" ;
}
public void OnGUI ( )
{
m_Name = EditorGUILayout . TextField ( "Name" , m_Name ) ;
m_Value = EditorGUILayout . IntSlider ( "Value" , m_Value , 0 , 10 ) ;
m_Struct . OnGUI ( ) ;
}
}
|
When you run this and force an assembly reload you will notice that any value in the window you have changed will not survive. This is because when the assembly is reloaded the reference to the ‘m_SerialziedThing’ is gone. It is not marked up to be serialized.
当您运行此命令并强制重新加载装配件时,您会注意到更改的窗口中的任何值都将无法保存。 这是因为重新加载程序集时,对'm_SerialziedThing'的引用已消失。 它没有标记为要序列化。
There are a few things that need to be done to make this serialization work properly: In MyWindow.cs:
要使此序列化正常工作,需要做一些事情:在MyWindow.cs中:
In SerializeMe.cs:
在SerializeMe.cs中:
After adding these flags open the window and modify the fields. You will notice that after an assembly reload that the fields retain their values; that is apart from the field that came from the struct. This brings up the first important point, structs are not very well supported for serialization. Changing ‘NestedStruct’ from a struct to a class fixes this issue.
添加这些标志后,打开窗口并修改字段。 您会注意到,在装配件重装后,这些字段将保留其值。 这与来自该结构的字段不同。 这引出了第一个重要的观点,结构没有很好地支持序列化。 将“ NestedStruct”从结构更改为类可解决此问题。
The code now looks like this:
现在的代码如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
|
using UnityEngine;
using UnityEditor;
public class MyWindow : EditorWindow
{
private SerializeMe m_SerialziedThing;
[MenuItem ("Window/Serialization")]
static void Init () {
GetWindow ();
}
void OnEnable ()
{
hideFlags = HideFlags.HideAndDontSave;
if (m_SerialziedThing == null)
m_SerialziedThing = new SerializeMe ();
}
void OnGUI () {
GUILayout.Label ("Serialized Things", EditorStyles.boldLabel);
m_SerialziedThing.OnGUI ();
}
}
using System;
using UnityEditor;
using UnityEngine;
[Serializable]
public class NestedStruct
{
[SerializeField]
private float m_StructFloat;
public void OnGUI ()
{
m_StructFloat = EditorGUILayout.FloatField("Struct Float", m_StructFloat);
}
}
[Serializable]
public class SerializeMe
{
[SerializeField]
private string m_Name;
[SerializeField]
private int m_Value;
[SerializeField]
private NestedStruct m_Struct;
public SerializeMe ()
{
m_Struct = new NestedStruct();
m_Name = "";
}
public void OnGUI ()
{
m_Name = EditorGUILayout.TextField( "Name", m_Name);
m_Value = EditorGUILayout.IntSlider ("Value", m_Value, 0, 10);
m_Struct.OnGUI ();
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
|
using UnityEngine ;
using UnityEditor ;
public class MyWindow : EditorWindow
{
private SerializeMe m_SerialziedThing ;
[ MenuItem ( "Window/Serialization" ) ]
static void Init ( ) {
GetWindow ( ) ;
}
void OnEnable ( )
{
hideFlags = HideFlags . HideAndDontSave ;
if ( m_SerialziedThing == null )
m_SerialziedThing = new SerializeMe ( ) ;
}
void OnGUI ( ) {
GUILayout . Label ( "Serialized Things" , EditorStyles . boldLabel ) ;
m_SerialziedThing . OnGUI ( ) ;
}
}
using System ;
using UnityEditor ;
using UnityEngine ;
[ Serializable ]
public class NestedStruct
{
[ SerializeField ]
private float m_StructFloat ;
public void OnGUI ( )
{
m_StructFloat = EditorGUILayout . FloatField ( "Struct Float" , m_StructFloat ) ;
}
}
[ Serializable ]
public class SerializeMe
{
[ SerializeField ]
private string m_Name ;
[ SerializeField ]
private int m_Value ;
[ SerializeField ]
private NestedStruct m_Struct ;
public SerializeMe ( )
{
m_Struct = new NestedStruct ( ) ;
m_Name = "" ;
}
public void OnGUI ( )
{
m_Name = EditorGUILayout . TextField ( "Name" , m_Name ) ;
m_Value = EditorGUILayout . IntSlider ( "Value" , m_Value , 0 , 10 ) ;
m_Struct . OnGUI ( ) ;
}
}
|
Some Serialization Rules
一些序列化规则
Scriptable Objects So far we have looked at using normal classes when it comes to serialization. Unfortunately using plain classes has some issues when it comes to serialization in Unity. Lets take a look at an example.
可编写脚本的对象到目前为止,在序列化方面,我们已经研究了使用普通类。 不幸的是,在Unity中进行序列化时,使用普通类会有一些问题。 让我们看一个例子。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
using System;
using UnityEditor;
using UnityEngine;
[Serializable]
public class NestedClass
{
[SerializeField]
private float m_StructFloat;
public void OnGUI()
{
m_StructFloat = EditorGUILayout.FloatField("Float", m_StructFloat);
}
}
[Serializable]
public class SerializeMe
{
[SerializeField]
private NestedClass m_Class1;
[SerializeField]
private NestedClass m_Class2;
public void OnGUI ()
{
if (m_Class1 == null)
m_Class1 = new NestedClass ();
if (m_Class2 == null)
m_Class2 = m_Class1;
m_Class1.OnGUI();
m_Class2.OnGUI();
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
using System ;
using UnityEditor ;
using UnityEngine ;
[ Serializable ]
public class NestedClass
{
[ SerializeField ]
private float m_StructFloat ;
public void OnGUI ( )
{
m_StructFloat = EditorGUILayout . FloatField ( "Float" , m_StructFloat ) ;
}
}
[ Serializable ]
public class SerializeMe
{
[ SerializeField ]
private NestedClass m_Class1 ;
[ SerializeField ]
private NestedClass m_Class2 ;
public void OnGUI ( )
{
if ( m_Class1 == null )
m_Class1 = new NestedClass ( ) ;
if ( m_Class2 == null )
m_Class2 = m_Class1 ;
m_Class1 . OnGUI ( ) ;
m_Class2 . OnGUI ( ) ;
}
}
|
This is a contrived example to show a very specific corner case of the Unity serialization system that can catch you if you are not careful. You will notice that we have two fields of type NestedClass. The first time the window is drawn it will show both the fields, and as m_Class1 and m_Class2 point to the same reference, modifying one will modify the other.
这是一个精心设计的示例,展示了Unity序列化系统的一个非常特殊的案例,如果您不小心,可能会抓住您。 您会注意到我们有两个NestedClass类型的字段。 第一次绘制窗口时,它将显示两个字段,并且当m_Class1和m_Class2指向相同的引用时,修改一个将修改另一个。
Now try reloading the assembly by entering and exiting play mode… The references have been decoupled. This is due to how serialization works when you mark a class as simply [Serializable]
现在尝试通过进入和退出播放模式重新加载装配件…引用已解耦。 这是由于将类简单标记为[Serializable]时序列化是如何工作的
When you are serializing standard classes Unity walks through the fields of the class and serializes each one individually, even if the reference is shared between multiple fields. This means that you could have the same object serialized multiple times, and on deserialization the system will not know they are really the same object. If you are designing a complex system this is a frustrating limitation because it means that complex interactions between classes can not be captured properly.
序列化标准类时,即使引用在多个字段之间共享,Unity也会遍历该类的字段并分别序列化每个字段。 这意味着您可以多次序列化同一对象,并且在反序列化时,系统将不知道它们确实是同一对象。 如果您要设计一个复杂的系统,这将是一个令人沮丧的限制,因为这意味着无法正确捕获类之间的复杂交互。
Enter ScriptableObjects! ScriptableObjects are a type of class that correctly serializes as references, so that they only get serialized once. This allows complex class interactions to be stored in a way that you would expect. Internally in Unity ScriptableObjects and MonoBehaviours are the same; in userland code you can have a ScriptableObject that is not attached to a GameObject; this is different to how MonoBehaviour works. They are great for general data structure serialization.
输入ScriptableObjects! ScriptableObjects是一类正确地序列化为引用的类,因此它们仅被序列化一次。 这样可以以您期望的方式存储复杂的类交互。 在Unity内部,ScriptableObjects和MonoBehaviours相同; 在用户区代码中,您可以具有未附加到GameObject的ScriptableObject; 这与MonoBehaviour的工作方式不同。 它们非常适合常规数据结构序列化。
Let’s modify the example to be able to handle serialization properly:
让我们修改示例以使其能够正确处理序列化:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
using System;
using UnityEditor;
using UnityEngine;
[Serializable]
public class NestedClass : ScriptableObject
{
[SerializeField]
private float m_StructFloat;
public void OnEnable() { hideFlags = HideFlags.HideAndDontSave; }
public void OnGUI()
{
m_StructFloat = EditorGUILayout.FloatField("Float", m_StructFloat);
}
}
[Serializable]
public class SerializeMe
{
[SerializeField]
private NestedClass m_Class1;
[SerializeField]
private NestedClass m_Class2;
public SerializeMe ()
{
m_Class1 = ScriptableObject.CreateInstance ();
m_Class2 = m_Class1;
}
public void OnGUI ()
{
m_Class1.OnGUI();
m_Class2.OnGUI();
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
using System ;
using UnityEditor ;
using UnityEngine ;
[ Serializable ]
public class NestedClass : ScriptableObject
{
[ SerializeField ]
private float m_StructFloat ;
public void OnEnable ( ) { hideFlags = HideFlags . HideAndDontSave ; }
public void OnGUI ( )
{
m_StructFloat = EditorGUILayout . FloatField ( "Float" , m_StructFloat ) ;
}
}
[ Serializable ]
public class SerializeMe
{
[ SerializeField ]
private NestedClass m_Class1 ;
[ SerializeField ]
private NestedClass m_Class2 ;
public SerializeMe ( )
{
m_Class1 = ScriptableObject . CreateInstance ( ) ;
m_Class2 = m_Class1 ;
}
public void OnGUI ( )
{
m_Class1 . OnGUI ( ) ;
m_Class2 . OnGUI ( ) ;
}
}
|
The three changes of note here are that:
这里需要注意的三个变化是:
These simple changes mean that the instance of the NestedClass will only be serialized once, with each of the references to the class pointing to the same one.
这些简单的更改意味着NestedClass的实例将仅被序列化一次,对该类的每个引用都指向同一实例。
ScriptableObject Initialization So now we know that for complex data structures where external referencing is needed it is a good idea to use ScriptableObjects. But what is the correct way to work with ScriptableObjects from user code? The first thing to examine is HOW scriptable objects are initialized, especially from the Unity serialization system.
ScriptableObject初始化因此,现在我们知道,对于需要外部引用的复杂数据结构,使用ScriptableObjects是一个好主意。 但是从用户代码使用ScriptableObjects的正确方法是什么? 首先要检查的是如何初始化可脚本编写的对象,尤其是从Unity序列化系统初始化的对象。
Working with this knowledge there are some things that we can say:
利用这些知识,我们可以说一些事情:
Lets make some changes to the ‘SerializeMe’ class so that it is a ScriptableObject. This will allow us to see the correct initialization pattern for ScriptableObjects.
让我们对'SerializeMe'类进行一些更改,使其成为ScriptableObject。 这将使我们能够看到ScriptableObjects的正确初始化模式。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
// also updated the Window to call CreateInstance instead of the constructor
using System;
using UnityEngine;
[Serializable]
public class SerializeMe : ScriptableObject
{
[SerializeField]
private NestedClass m_Class1;
[SerializeField]
private NestedClass m_Class2;
public void OnEnable ()
{
hideFlags = HideFlags.HideAndDontSave;
if (m_Class1 == null)
{
m_Class1 = CreateInstance ();
m_Class2 = m_Class1;
}
}
public void OnGUI ()
{
m_Class1.OnGUI();
m_Class2.OnGUI();
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
// also updated the Window to call CreateInstance instead of the constructor
using System ;
using UnityEngine ;
[ Serializable ]
public class SerializeMe : ScriptableObject
{
[ SerializeField ]
private NestedClass m_Class1 ;
[ SerializeField ]
private NestedClass m_Class2 ;
public void OnEnable ( )
{
hideFlags = HideFlags . HideAndDontSave ;
if ( m_Class1 == null )
{
m_Class1 = CreateInstance ( ) ;
m_Class2 = m_Class1 ;
}
}
public void OnGUI ( )
{
m_Class1 . OnGUI ( ) ;
m_Class2 . OnGUI ( ) ;
}
}
|
On the surface it seems that we have not really changed this class much, it now inherits from ScriptableObject and instead of using a constructor has an OnEnable(). The important part to take note of is slightly more subtle… OnEnable() is called AFTER serialization; because of this we can see if the [SerializedFields] are null or not. If they are null it indicates that this is the first initialization, and we need to construct the instances. If they are not null then they have been loaded into memory, and do NOT need to be constructed. It is common in OnEnable() to also call a custom Initialization function to configure any private / non serialized fields on the object, much like you would do in a constructor.
从表面上看,我们似乎并没有真正改变此类,它现在是从ScriptableObject继承的,而不是使用具有OnEnable()的构造函数。 需要注意的重要部分更加微妙……OnEnable()称为AFTER序列化; 因此,我们可以查看[SerializedFields]是否为null。 如果它们为null,则表明这是第一次初始化,我们需要构造实例。 如果它们不为null,则它们已被加载到内存中,不需要构造。 在OnEnable()中,通常也调用自定义的初始化函数来配置对象上的任何私有/非序列化字段,就像在构造函数中一样。
HideFlags In the examples using ScriptableObjects you will notice that we are setting the ‘hideFlags’ on the object to HideFlags.HideAndDontSave. This is a special setup that is required when writing custom data structures that have no root in the scene. This is to get around how scene loading works in Unity.
HideFlags在使用ScriptableObjects的示例中,您会注意到我们将对象上的“ hideFlags”设置为HideFlags.HideAndDontSave。 这是编写场景中没有根的自定义数据结构时所需的特殊设置。 这是为了解决场景加载在Unity中的工作方式。
When a scene is loaded internally unity calls Resources.UnloadUnusedAssets. If nothing is referencing an asset the garbage collector will find it. The GC uses the scene as ‘the root’ and traverses the hierarchy to see what can get GC’d. Setting the HideAndDontSave flag on a ScriptableObject tells Unity to consider that object as a root object. Because of this it will not just disappear because of an assembly reload. The object can still be destroyed by calling Destroy().
当内部加载场景时,统一调用Resources.UnloadUnusedAssets。 如果没有东西引用资产,则垃圾收集器将找到它。 GC使用场景作为“根”,并遍历层次结构以查看可以得到GC的内容。 在ScriptableObject上设置HideAndDontSave标志可以使Unity将该对象视为根对象。 因此,它不仅会因为装配件重载而消失。 仍然可以通过调用Destroy()销毁该对象。
Some ScriptableObject Rules
一些ScriptableObject规则
Concrete Array Serialization Lets have a look at a simple example that serializes a range of concrete classes.
具体数组序列化让我们看一个简单的示例,该序列化一系列具体类。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
[Serializable]
public class BaseClass
{
[SerializeField]
private int m_IntField;
public void OnGUI() {m_IntField = EditorGUILayout.IntSlider ("IntField", m_IntField, 0, 10);}
}
[Serializable]
public class SerializeMe : ScriptableObject
{
[SerializeField]
private List m_Instances;
public void OnEnable ()
{
hideFlags = HideFlags.HideAndDontSave;
if (m_Instances == null)
m_Instances = new List ();
}
public void OnGUI ()
{
foreach (var instance in m_Instances)
instance.OnGUI ();
if (GUILayout.Button ("Add Simple"))
m_Instances.Add (new BaseClass ());
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
using System ;
using System . Collections . Generic ;
using UnityEditor ;
using UnityEngine ;
[ Serializable ]
public class BaseClass
{
[ SerializeField ]
private int m_IntField ;
public void OnGUI ( ) { m_IntField = EditorGUILayout . IntSlider ( "IntField" , m_IntField , 0 , 10 ) ; }
}
[ Serializable ]
public class SerializeMe : ScriptableObject
{
[ SerializeField ]
private List m_Instances ;
public void OnEnable ( )
{
hideFlags = HideFlags . HideAndDontSave ;
if ( m_Instances == null )
m_Instances = new List ( ) ;
}
public void OnGUI ( )
{
foreach ( var instance in m_Instances )
instance . OnGUI ( ) ;
if ( GUILayout . Button ( "Add Simple" ) )
m_Instances . Add ( new BaseClass ( ) ) ;
}
}
|
This basic example has a list of BaseClasses, by clicking the ‘Add Simple’ button it creates an instance and adds it to the list. Due to the SerializeMe class being configured properly for serialization (as discussed before) it ‘just works’. Unity sees that the List is marked for serialization and serializes each of the List elements.
这个基本示例包含一个BaseClasses列表,通过单击“ Add Simple”按钮,它创建一个实例并将其添加到列表中。 由于为串行化正确配置了SerializeMe类(如前所述),它“可以正常工作”。 Unity看到列表已标记为要序列化,并序列化了每个List元素。
General Array Serialization Lets modify the example to serialize a list that contains members of a base class and child class:
常规数组序列化让我们修改示例以序列化包含基类和子类成员的列表:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
|
using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
[Serializable]
public class BaseClass
{
[SerializeField]
private int m_IntField;
public virtual void OnGUI() { m_IntField = EditorGUILayout.IntSlider("IntField", m_IntField, 0, 10); }
}
[Serializable]
public class ChildClass : BaseClass
{
[SerializeField]
private float m_FloatField;
public override void OnGUI()
{
base.OnGUI ();
m_FloatField = EditorGUILayout.Slider("FloatField", m_FloatField, 0f, 10f);
}
}
[Serializable]
public class SerializeMe : ScriptableObject
{
[SerializeField]
private List m_Instances;
public void OnEnable ()
{
if (m_Instances == null)
m_Instances = new List ();
hideFlags = HideFlags.HideAndDontSave;
}
public void OnGUI ()
{
foreach (var instance in m_Instances)
instance.OnGUI ();
if (GUILayout.Button ("Add Base"))
m_Instances.Add (new BaseClass ());
if (GUILayout.Button ("Add Child"))
m_Instances.Add (new ChildClass ());
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
|
using System ;
using System . Collections . Generic ;
using UnityEditor ;
using UnityEngine ;
[ Serializable ]
public class BaseClass
{
[ SerializeField ]
private int m_IntField ;
public virtual void OnGUI ( ) { m_IntField = EditorGUILayout . IntSlider ( "IntField" , m_IntField , 0 , 10 ) ; }
}
[ Serializable ]
public class ChildClass : BaseClass
{
[ SerializeField ]
private float m_FloatField ;
public override void OnGUI ( )
{
base . OnGUI ( ) ;
m_FloatField = EditorGUILayout . Slider ( "FloatField" , m_FloatField , 0f , 10f ) ;
}
}
[ Serializable ]
public class SerializeMe : ScriptableObject
{
[ SerializeField ]
private List m_Instances ;
public void OnEnable ( )
{
if ( m_Instances == null )
m_Instances = new List ( ) ;
hideFlags = HideFlags . HideAndDontSave ;
}
public void OnGUI ( )
{
foreach ( var instance in m_Instances )
instance . OnGUI ( ) ;
if ( GUILayout . Button ( "Add Base" ) )
m_Instances . Add ( new BaseClass ( ) ) ;
if ( GUILayout . Button ( "Add Child" ) )
m_Instances . Add ( new ChildClass ( ) ) ;
}
}
|
The example has been extended so that there is now a ChildClass, but we are serializing using the BaseClass. If you create a few instance of the ChildClass and the BaseClass they will render properly. Issues arise when they are placed through an assembly reload. After the reload completes every instance will be a BaseClass, with all the ChildClass information stripped. The instances are being sheared by the serialization system.
对该示例进行了扩展,以便现在有一个ChildClass,但是我们正在使用BaseClass进行序列化。 如果创建ChildClass和BaseClass的一些实例,它们将正确呈现。 通过装配件重装放置它们时会出现问题。 重新加载完成后,每个实例将是一个BaseClass,所有ChildClass信息都将被剥离。 实例被序列化系统剪切。
The way to work around this limitation of the serialization system is to once again use ScriptableObjects:
解决序列化系统这一限制的方法是再次使用ScriptableObjects:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
|
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
[Serializable]
public class MyBaseClass : ScriptableObject
{
[SerializeField]
protected int m_IntField;
public void OnEnable() { hideFlags = HideFlags.HideAndDontSave; }
public virtual void OnGUI ()
{
m_IntField = EditorGUILayout.IntSlider("IntField", m_IntField, 0, 10);
}
}
[Serializable]
public class ChildClass : MyBaseClass
{
[SerializeField]
private float m_FloatField;
public override void OnGUI()
{
base.OnGUI ();
m_FloatField = EditorGUILayout.Slider("FloatField", m_FloatField, 0f, 10f);
}
}
[Serializable]
public class SerializeMe : ScriptableObject
{
[SerializeField]
private List m_Instances;
public void OnEnable ()
{
if (m_Instances == null)
m_Instances = new List();
hideFlags = HideFlags.HideAndDontSave;
}
public void OnGUI ()
{
foreach (var instance in m_Instances)
instance.OnGUI ();
if (GUILayout.Button ("Add Base"))
m_Instances.Add(CreateInstance());
if (GUILayout.Button ("Add Child"))
m_Instances.Add(CreateInstance());
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
|
using System ;
using System . Collections . Generic ;
using UnityEngine ;
using UnityEditor ;
[ Serializable ]
public class MyBaseClass : ScriptableObject
{
[ SerializeField ]
protected int m_IntField ;
public void OnEnable ( ) { hideFlags = HideFlags . HideAndDontSave ; }
public virtual void OnGUI ( )
{
m_IntField = EditorGUILayout . IntSlider ( "IntField" , m_IntField , 0 , 10 ) ;
}
}
[ Serializable ]
public class ChildClass : MyBaseClass
{
[ SerializeField ]
private float m_FloatField ;
public override void OnGUI ( )
{
base . OnGUI ( ) ;
m_FloatField = EditorGUILayout . Slider ( "FloatField" , m_FloatField , 0f , 10f ) ;
}
}
[ Serializable ]
public class SerializeMe : ScriptableObject
{
[ SerializeField ]
private List m_Instances ;
public void OnEnable ( )
{
if ( m_Instances == null )
m_Instances = new List ( ) ;
hideFlags = HideFlags . HideAndDontSave ;
}
public void OnGUI ( )
{
foreach ( var instance in m_Instances )
instance . OnGUI ( ) ;
if ( GUILayout . Button ( "Add Base" ) )
m_Instances . Add ( CreateInstance ( ) ) ;
if ( GUILayout . Button ( "Add Child" ) )
m_Instances . Add ( CreateInstance ( ) ) ;
}
}
|
After running this, changing some values, and reloading assemblies you will notice that ScriptableObjects are safe to use in arrays even if you are serializing derived types. The reason is that when you serialize a standard [Serializable] class it is serialized ‘in place’, but a ScriptableObject is serialized externally and the reference inserted into the collection. The shearing occurs because the type can not be properly be serialized as the serialization system thinks it is of the base type.
运行此命令,更改某些值并重新加载程序集后,您将注意到即使在序列化派生类型时,ScriptableObject也可以在数组中安全使用。 原因是,当您序列化一个标准的[Serializable]类时,它被“就地”序列化了,但是一个ScriptableObject在外部被序列化了,并将引用插入了集合中。 发生剪切是因为无法正确序列化该类型,因为序列化系统认为该类型是基本类型。
Serializing Abstract Classes So now we have seen that it’s possible to serialize a general list (so long as the members are of type ScriptableObject). Lets see how abstract classes behave:
序列化抽象类因此,现在我们已经看到可以序列化一个常规列表(只要成员的类型为ScriptableObject)。 让我们看一下抽象类的行为:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
|
using System;
using UnityEditor;
using System.Collections.Generic;
using UnityEngine;
[Serializable]
public abstract class MyBaseClass : ScriptableObject
{
[SerializeField]
protected int m_IntField;
public void OnEnable() { hideFlags = HideFlags.HideAndDontSave; }
public abstract void OnGUI ();
}
[Serializable]
public class ChildClass : MyBaseClass
{
[SerializeField]
private float m_FloatField;
public override void OnGUI()
{
m_IntField = EditorGUILayout.IntSlider("IntField", m_IntField, 0, 10);
m_FloatField = EditorGUILayout.Slider("FloatField", m_FloatField, 0f, 10f);
}
}
[Serializable]
public class SerializeMe : ScriptableObject
{
[SerializeField]
private List m_Instances;
public void OnEnable ()
{
if (m_Instances == null)
m_Instances = new List();
hideFlags = HideFlags.HideAndDontSave;
}
public void OnGUI ()
{
foreach (var instance in m_Instances)
instance.OnGUI ();
if (GUILayout.Button ("Add Child"))
m_Instances.Add(CreateInstance());
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
|
using System ;
using UnityEditor ;
using System . Collections . Generic ;
using UnityEngine ;
[ Serializable ]
public abstract class MyBaseClass : ScriptableObject
{
[ SerializeField ]
protected int m_IntField ;
public void OnEnable ( ) { hideFlags = HideFlags . HideAndDontSave ; }
public abstract void OnGUI ( ) ;
}
[ Serializable ]
public class ChildClass : MyBaseClass
{
[ SerializeField ]
private float m_FloatField ;
public override void OnGUI ( )
{
m_IntField = EditorGUILayout . IntSlider ( "IntField" , m_IntField , 0 , 10 ) ;
m_FloatField = EditorGUILayout . Slider ( "FloatField" , m_FloatField , 0f , 10f ) ;
}
}
[ Serializable ]
public class SerializeMe : ScriptableObject
{
[ SerializeField ]
private List m_Instances ;
public void OnEnable ( )
{
if ( m_Instances == null )
m_Instances = new List ( ) ;
hideFlags = HideFlags . HideAndDontSave ;
}
public void OnGUI ( )
{
foreach ( var instance in m_Instances )
instance . OnGUI ( ) ;
if ( GUILayout . Button ( "Add Child" ) )
m_Instances . Add ( CreateInstance ( ) ) ;
}
}
|
This code much like the previous example works. But it IS dangerous. Lets see why.
此代码非常类似于前面的示例。 但这很危险。 让我们看看为什么。
The function CreateInstance<>() expects a type that inherits from ScriptableObject, the class ‘MyBaseClass’ does in fact inherit from ScriptableObject. This means that it’s possible to add an instance of the abstract class MyBaseClass to the m_Instances array. If you do this and then try and access an abstract method bad things will happen because there is no implementation of that function. In this specific case that would be the OnGUI method.
函数CreateInstance <>()期望从ScriptableObject继承的类型,而类“ MyBaseClass”实际上从ScriptableObject继承。 这意味着可以将抽象类MyBaseClass的实例添加到m_Instances数组。 如果执行此操作,然后尝试访问抽象方法,则将发生不好的事情,因为该函数没有实现。 在这种特定情况下,将是OnGUI方法。
Using abstract classes as the serialized type for lists and fields DOES work, so long as they inherit from ScriptableObject, but it is not a recommended practice. Personally I think it’s better to use concrete classes with empty virtual methods. This ensures that things will not go bad for you.
只要抽象类是从ScriptableObject继承的,就可以使用抽象类作为列表和字段的序列化类型,但是不建议这样做。 我个人认为最好将具体类与空虚方法一起使用。 这样可以确保事情不会对您不利。
When do ScriptableObjects get persisted into scene / prefab files? GameObjects and their components are saved into a scene by default. Asset types (Materials / Meshes / AnimationClip / SerializedObject’s) that are created from code are saved in the scene so long as a game object or their components in the scene references it.
何时将ScriptableObjects保留到场景/预制文件中? GameObject及其组件默认情况下保存到场景中。 从代码创建的资产类型(材料/网格/动画剪辑/序列化对象)将保存在场景中,只要游戏对象或其场景中的组件对其进行引用即可。
Asset types can also be explicitly marked as assets using AssetDatabase.CreateAsset. In that case they will not be saved in the scene but simply referenced. If an asset type or game object type is marked as HideAndDontSave it also not saved in the scene.
也可以使用AssetDatabase.CreateAsset将资产类型显式标记为资产。 在这种情况下,它们将不会保存在场景中,而只是被引用。 如果资产类型或游戏对象类型标记为HideAndDontSave,它也不会保存在场景中。
Questions?
有什么问题吗
翻译自: https://blogs.unity3d.com/2012/10/25/unity-serialization/
unity序列化