参考
【Unity 编辑器】扩展总结八:EditorPrefs、ScriptableObject、Undo
Unity Editor扩展:ScriptableObject简记
冯乐乐【Unity】ScriptableObject的介绍
Unity ScriptableObject详解
一、示例
1.创建ScriptableObject
using UnityEngine;
[CreateAssetMenu(fileName = "MyData", menuName = "Custom/MyDataAsset", order = 1)]
public class MyData : ScriptableObject
{
public int id;
public string objName;
public float value;
public bool isUsed;
}
也可以通过方法调用来创建资源,最终得到的资源是一样的,该脚本需要放在Editor文件夹中。
using UnityEngine;
using UnityEditor;
public class ExampleAsset : ScriptableObject
{
//爆炸时间
public float boomTime = 0.4f;
//爆炸范围
public float boomRange = 200f;
[MenuItem("Example/Create ExampleAsset Instance")]
static void CreateExampleAssetInstance()
{
var exampleAsset = CreateInstance();
AssetDatabase.CreateAsset(exampleAsset, "Assets/Resources/ExampleAsset.asset");
AssetDatabase.Refresh();
}
}
不过这样做,从使用的角度来看不方便,因为无法引入Editor文件夹下的ExampleAsset。
2.使用ScriptableObject
可以将创建的资源放在Resources文件夹中,通过动态的方式加载。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class UseScriptableObject : MonoBehaviour
{
private MyData myData;
void Start()
{
myData = Resources.Load("MyData");
Debug.Log(myData.id);
Debug.Log(myData.objName);
Debug.Log(myData.value);
Debug.Log(myData.isUsed);
}
}
也可以直接拖入:
private MyData myData;
public MyData myData2;
void Start()
{
myData = Resources.Load("MyData");
Debug.Log(myData.id);
Debug.Log(myData.objName);
Debug.Log(myData.value);
Debug.Log(myData.isUsed);
Debug.Log(myData2.id);
}
3.数据不能保存的情况
如果遇到ScriptableObject数据不能正常保存的情况,可以尝试使用EditorUtility.SetDirty方法,标记该ScriptableObject为“脏”,然后就能正常保存了。
...
EditorUtility.SetDirty (obj);
AssetDatabase.SaveAssets ();
AssetDatabase.Refresh ();
二、ScriptableObject简介
ScriptableObject是一个用于生成单独Asset的结构。同时,它也能被称为是Unity中用于处理序列化的结构。在Unity里面有单独的序列化结构,所有的Object(UnityEngine.Object)都能够通过这个方法进行数据的序列化与反序列化。文件和Unity编辑器都能够方便的获取其中的数据。Unity内部的Asset(Material或者AnimationClip等)都是从UnityEngine.Object衍生出来的。为了制作单独的Asset,需要制作UnityEngine.Object的子类。不过对于用户而言是不允许制作UnityEngine.Object的子类。所以用户如果要利用Unity中的序列化结构、生成单独的Asset,就必须借助ScriptableObject。
ScriptableObject类直接继承自Object类;它和MonoBehaviour是并列的,都继承自Object(但MonoBehaviour并不是直接继承自Object),会在后面介绍ScriptableObject和MonoBehavior的比较;
脚本化对象就是一个数据容器,可以用来存储大量的数据,它是可序列化的,这个特点也正决定了它的主要用途;一个主要用处就是通过将数据存储在ScriptableObject对象中来减少工程以及游戏运行时因拷贝值所造成的内存占用;
1.ScriptableObject与预制体
当你有一个预制体,它附加了一些mono脚本,包含了一些数据,每次我们实例化预制体的时候它都会拷贝assets下原预制体的值生成一份自己的拷贝,然后我们可以修改场景内预制体的值而并不影响assets下预制体的值,这是prefab的特性,对于我们从一个prefab模板生成属性不同的游戏对象是很有用的,但是如果prefab里的脚本数据是不需要修改的,它就会造成很大的资源浪费,尤其在数据很多的时候;为了避免这种问题,我们可以在不需要修改prefab里的脚本数据时,考虑使用ScriptableObject来存储这些重复的数据,然后其它所有预制体都可以使用引用的方式来访问这份数据,这就意味着不管场景中实例了多少预制体,在内存中就只需要有一份数据;它所带给我们的启示就是,当预制体中的脚本里有大量重复数据时,我们要想着将数据抽离,单独保存在本地;
举一个子弹的例子,这是一个比较典型的例子,因为一个子弹会包含很多属性,而且在场景中需要大量的实例化,我们要先做一个子弹的预制体,为了体现它的属性,我们会写一个派生自MonoBehaviour的脚本Bullet,在里面添加一些属性,然后将其附加到一个子弹游戏对象上,完成了一个子弹预制体的制作;之后我们在场景中实例化新的子弹的时候,这个实例也会有一个Bullet实例,而且最重要的是这些子弹都会有这样一份Bullet而且它们的数据是相同的,这就是之前讲到的因为拷贝值产生新的实例导致了大量的内存占用;这个时候我们就可以把这个派生自MonoBehaviour的脚本里的数据放置在派生自ScriptableObject的脚本,然后就可以在创建Assets下创建一份数据文件,设置完数据后,在Bullet中我们就定义一个指向ScriptableOjbect对象的引用,这样它就由原来的拷贝大量值并存储重复的值变成了拷贝一个引用并存储一个引用;
2.应用场景及方式
当使用编辑器运行游戏的时候,可以将数据保存到ScriptableObject里(当创建一个脚本化对象实例后使用AssetDatabase.CreateAsset()保存该资源),退出之后也不会丢失,因为它是作为Assets下的资源存在的;它是仅在编辑器中才可以保存修改的数据(因为ScriptableObject对象虽然声明在UnityEngine中,但是它的Scriptable是通过UnityEditor命名空间下的类例如Editor类等来实现的),所以在部署构建的时候不可以用于存储游戏运行时更改的数据,但是可以使用之前存储好的数据,也就是ScriptableObject生成的数据资源文件在Editor外具有只读属性,这是非常需要注意的一点,如果你需要在游戏中修改数据并存储下来,就不推荐使用ScriptableObject了;
总结:它就是用来在编辑器模式下保存和存储数据到本地Assets下的,数据保存以后是可以共享的,就像纹理、shader等资源一样,是可以共享于当前整个工程和其它工程的;这个ScriptableObject在真机上不可修改的,就像我们不可以在游戏运行时修改一个shader资源的代码、不可以修改一个纹理资源的像素内容一样,而在Unity Editor里可以修改ScriptableObject是因为Unity的编辑器对它格式的支持,就像使用vs code修改shader和使用ps修改一张纹理一样;