---
title: "ScriptableObject"
author: "Perrin Yong"
author_profile: https://www.pystone.net/profile/
published_by: "Perrin Yong"
canonical: https://www.pystone.net/notes/unity-scriptable-object/
type: note
content_role: unspecified
visibility: public
id_stability: rename-stable
source_path: "10-计算机、信息技术与工程/05-游戏图形与运行时/Unity/ScriptableObject.md"
content_hash: 2ce8f146b359dca7d8fd054aa2b4cf5531db99a8f7ef4f49fbb3afe0afbb1623
knowledge_version: 224c990773de.5fa8af6e39fa
site_commit: 224c990773de166d23a886306577dd90379529ce
notes_commit: 5fa8af6e39fa3891d1b9b4832bfa6c4e0ecaaf0a
---
# ScriptableObject
> 创建时间：2020/6/9 17:02

## ScriptableObject

  * ScriptableObject
    * 作用
    * 特点
    * 优点
    * Method
    * 使用方法
      * MenuItem
      * CreateAssetMenu
      * 动态创建
      * CreateAssets & FindAssets & ScriptableObject
    * 生命周期
    * 其他高级应用Demo
      * 保存信息
      * 用作配置文件
    * Ref:

> 文档：<https://docs.unity3d.com/ScriptReference/ScriptableObject.html>
>  <https://docs.unity3d.com/Manual/class-ScriptableObject.html>

ScriptableObject类直接继承自Object类；它和MonoBehaviour是并列的，都继承自Object（但MonoBehaviour并不是直接继承自Object）

是一个 **数据容器** ，可以用来存储大量的数据，它是可序列化的。

## 作用

  1. 存储数据：在游戏运行时创建脚本化对象实例，然后可以将数据保存到本地（如果不保存，它会在游戏结束后销毁）；

  2. 减少内存：将数据存储在ScriptableObject对象中，在代码中可以进行引用，来减少工程以及游戏运行时因拷贝值所造成的内存占用；

> **ScriptableObject与预制体** ：当你有一个预制体，它附加了一些mono脚本，包含了一些数据，每次我们实例化预制体的时候它都会拷贝assets下原预制体的值生成一份自己的拷贝，然后我们可以修改场景内预制体的值而并不影响assets下预制体的值，这是prefab的特性，对于我们从一个prefab模板生成属性不同的游戏对象是很有用的，但是如果prefab里的脚本数据是不需要修改的，它就会造成很大的资源浪费，尤其在数据很多的时候；为了避免这种问题，我们可以在不需要修改prefab里的脚本数据时，考虑使用ScriptableObject来存储这些重复的数据，然后其它所有预制体都可以使用引用的方式来访问这份数据，这就意味着不管场景中实例了多少预制体，在内存中就只需要有一份数据；它所带给我们的启示就是， **当预制体中的脚本里有大量重复数据时，我们要想着将数据抽离，单独保存在本地**

## 特点

  1. **Unity编辑器外不可操作** ：仅在编辑器中才可以保存修改的数据（因为ScriptableObject对象虽然声明在UnityEngine中，但是它的Scriptable是通过UnityEditor命名空间下的类例如Editor类等来实现的），所以在部署构建的时候不可以用于存储游戏运行时更改的数据，但是可以使用之前存储好的数据。也就是ScriptableObject生成的数据资源文件 **在Editor外具有只读属性** ，如果你需要在游戏中修改数据并存储下来，就不推荐使用ScriptableObject了；

> 就像我们不可以在游戏运行时修改一个shader资源的代码、不可以修改一个纹理资源的像素内容一样，而在Unity Editor里可以修改ScriptableObject是因为Unity的编辑器对它格式的支持

  2. 继承自UnityEngine.Object，不必附着在对象上也无需/不能赋给Gameobject或Prefab

  3. 可以被serialize，可以在资源面板创建一个对应的.asset文件，把数据存储在资源文件中，通过属性面板可修改数值。该文件表示一个ScriptableObject的对象实例的序列化文件，退出之后也不会丢失。

  4. 本身是个类，可以引用，在项目之间共享

  5. 回调少 ：OnEnable 、OnDisable、OnDestroy

  6. persistent：当它被绑定到.asset文件或者AssetBundle等资源文件中的时候（它可以通过Resources.UnloadUnusedAssets来被unload出内存。可以通过脚本引用或其他需要的时候被再次load到内存）
非persistent：通过CreateInstance<>来创建的时候（它可以通过GC被直接destroy掉（如果没有任何引用的话）。如果不想被GC的话，可以使用HideFlags.HideAndDontSave）

## 优点

  1. unity内置的一种储存容器

  2. PlayMode储存数据更改（再也不怕更改数据没保存了）
3

## Method

```csharp
string s1 = &quot;Hello &quot;;
string s2 = s1;
s1 += &quot;World&quot;;

System.Console.WriteLine(s2);
//Output: Hello

```

???
ScriptableObject内部实现上也继承自MonoBehavior，它只有四个消息函数，Awake()、OnDestroy()、OnEnable()、OnDisable()；

## 使用方法

### MenuItem

需要指定创建的目录和资源名称，如果资源已经存在，则不会创建新资源；

```csharp
string s1 = &quot;Hello &quot;;
string s2 = s1;
s1 += &quot;World&quot;;

System.Console.WriteLine(s2);
//Output: Hello

```

### CreateAssetMenu

可以在Assets下任意目录创建资源，而且可以创建多个资源；

```csharp
string s1 = &quot;Hello &quot;;
string s2 = s1;
s1 += &quot;World&quot;;

System.Console.WriteLine(s2);
//Output: Hello

```

在Assets下创建一个可编程对象资源，设置好所需数据；如果需要在其它脚本中获取该数据，是需要声明一个该类型变量，然后为其赋值或加载该数据资源；然后，就像使用用一个类中的公有变量一样使用即可；

```csharp
string s1 = &quot;Hello &quot;;
string s2 = s1;
s1 += &quot;World&quot;;

System.Console.WriteLine(s2);
//Output: Hello

```

### 动态创建

```csharp
string s1 = &quot;Hello &quot;;
string s2 = s1;
s1 += &quot;World&quot;;

System.Console.WriteLine(s2);
//Output: Hello

```

### CreateAssets & FindAssets & ScriptableObject

  1. 对于ScriptableObject，使用CreateInstance来创建Asset实例对象
`testI = (ScriptObj)ScriptableObject.CreateInstance(typeof(ScriptObj));`

  2. 对于其他资源类型，使用new来创建对象

  3. 然后调用 `AssetDatabase.CreateAsset`函数，将实例对象作为资源存储在Assets路径当中。

  4. 最后使用`AssetDatabase.FindAssets`获取相应资源的GUID

  5. 由 `AssetDatabase.GUIDToAssetPath` 获取相应资源的路径。

```csharp
string s1 = &quot;Hello &quot;;
string s2 = s1;
s1 += &quot;World&quot;;

System.Console.WriteLine(s2);
//Output: Hello

```

## 生命周期

ScriptableObject的生命周期和其他资源都是类似的：

当它是被绑定到.asset文件或者AssetBundle等资源文件中的时候，它就是persistent的，这意味着
它可以通过Resources.UnloadUnusedAssets来被unload出内存
可以通过脚本引用或其他需要的时候被再次load到内存
如果是通过CreateInstance<>来创建的，它就是非persistent的，这意味着
它可以通过GC被直接destroy掉（如果没有任何引用的话）
如果不想被GC的话，可以使用HideFlags.HideAndDontSave

## 其他高级应用Demo

<https://blog.csdn.net/candycat1992/article/details/52181814>

### 保存信息

step1.创建你用于共享数据的类（例如EnemyData）

```csharp
string s1 = &quot;Hello &quot;;
string s2 = s1;
s1 += &quot;World&quot;;

System.Console.WriteLine(s2);
//Output: Hello

```

step2.新建一个脚本扩展Editor,使编辑器能够创建自定义的ScriptableObject对象

```csharp
string s1 = &quot;Hello &quot;;
string s2 = s1;
s1 += &quot;World&quot;;

System.Console.WriteLine(s2);
//Output: Hello

```

step3. 在需要引用的脚本中引用

```csharp
string s1 = &quot;Hello &quot;;
string s2 = s1;
s1 += &quot;World&quot;;

System.Console.WriteLine(s2);
//Output: Hello

```

### 用作配置文件

```csharp
string s1 = &quot;Hello &quot;;
string s2 = s1;
s1 += &quot;World&quot;;

System.Console.WriteLine(s2);
//Output: Hello

```
```csharp
string s1 = &quot;Hello &quot;;
string s2 = s1;
s1 += &quot;World&quot;;

System.Console.WriteLine(s2);
//Output: Hello

```

## Ref:

<https://blog.csdn.net/qq_36383623/article/details/99649941>
<https://blog.csdn.net/candycat1992/article/details/52181814>
