引言

在游戏开发过程中,我们会经常与数据打交道。在游戏开发过程中,我们会经常与数据打交道。假设现在要做一个肉鸽割草类游戏——吸血鬼幸存者,玩家操控的主角在获取了不同武器后会产生不同类型的攻击,这些攻击武器的速度和伤害各不相同。那么显然我要为每一种武器配置好它的属性值。

实现方式有很多,比如我为每种武器创建预制体,然后写脚本去定义代表子弹各个属性的成员变量,再把脚本挂载到武器预制体上。为了方便在开发时调试我们可以将这些变量声明为 public ,以便我们在编辑器面板中对数据进行改动。

这么做其实是有缺点的:

1) 每生成一个武器就会对原来的预制体进行拷贝,其挂载的脚本同样会被拷贝,因此同样的数据会被拷贝多次。而我们规定同一种武器的数据是相同的,也就是同一种武器,不管生成了多少个,它们都共用一套数据。因此,这种方法会创建多余的数据脚本,造成内存浪费。
2) 如果预制体上的脚本丢失,之前在 Inspector 面板中配置的数据也会消失。

不过解决方法有很多,比如:

  1. 我们可以创建一个全局的数据管理中心脚本,通过静态变量去调用每种武器的数据。不过这种数据配置方式不能实现数据的持久化,而且必须要打开代码文件进行修改,面对茫茫代码可能不是那么直观。
  2. 我们还可以用上像 excel,Json,xml 等持久化数据存储的方法,结合 Unity 对准备好的数据文件进行数据读写。这么做的好处是可以实现数据的持久化,比如在游戏过程中修改了数据,退出游戏后下一次打开游戏使用的就是之前修改过的数据。

ScriptableObject。它也能弥补通过挂载继承自 MonoBehaviour 的脚本来配置数据的一些不足。相较于 excel,Json,xml 这类持久化数据存储的方法,它有些额外的优点,但是也存在一些局限性。我将会在接下来的部分详细说明。

什么是ScriptableObject

  • ScriptableObject 是 Unity 提供的一个数据配置存储基类,它是一个可以用来保存大量数据的数据容器,我们可以将它保存为自定义的数据资源文件。

  • ScriptableObject 是一个类似 MonoBehaviour 的基类,继承自 UnityEngine.Object 。要想使用它,需要我们写个脚本去继承 ScriptableObject 。需要注意的是,继承自 SctiptableObject 的脚本无法挂载到游戏物体上,毕竟它不是继承自 MonoBehaviour。

  • ScriptableObject 类的实例会被保存成资源文件(.asset文件),和预制体,材质球,音频文件等类似,都是一种资源文件,存放在 Assets 文件夹下,创建出来的实例也是唯一存在的(单例)。

主要作用

  • 数据复用(多个对象用同一个数据,节省性能,避免了每个类都去申请空间,浪费)
  • 配置文件(配置游戏中的数据)
  • 编辑模式下的数据持久化

img

应用——吸血鬼幸存者

步骤一、自定义so数据容器

比如现在我们要创建一个角色数据类,用序列化方法使得可在inspecter面板上关联或者编辑成员变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
using UnityEngine;

public class CharacterData : ScriptableObject
{
[SerializeField] Sprite sprite;
[SerializeField] RuntimeAnimatorController controller;
[SerializeField] CharacterType characterType;
[SerializeField] int healthPoint;
[SerializeField] int attackPower;
[SerializeField] int defencePower;
[SerializeField] int speed;

}

这只是一个类,我们还没有创建数据资源文件

步骤二、创建数据资源文件

方法有两种

1. 为类添加CreateAssetMenu特性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
using UnityEngine;

[CreateAssetMenu(fileName = "Character Data", menuName = "Scriptable Object/Character Data"
public class MyData : ScriptableObject
{
[SerializeField] Sprite sprite;
[SerializeField] RuntimeAnimatorController controller;
[SerializeField] CharacterType characterType;
[SerializeField] int healthPoint;
[SerializeField] int attackPower;
[SerializeField] int defencePower;
[SerializeField] int speed;

}

2. 用静态方法创建数据对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
using UnityEditor;
using UnityEngine;

namespace SO
{
public class ScriptableObjectTool
{
[MenuItem("ScriptableObject/CreateCharacterData")]
public static void CreateCharacterData()
{
CharacterData asset = ScriptableObject.CreateInstance<CharacterData>();

AssetDatabase.CreateAsset(asset,"Assets/Resources/CharacterDataTest.asset");

AssetDatabase.SaveAssets();

AssetDatabase.Refresh();
}
}
}

在顶部工具栏点击“so”按钮,即可创建文件

数据文件的使用

直接在MonoBehaviour类中作为成员使用

  1. 通过Inspector关联使用
  2. 通过Resource.Load取用

SO的生命周期函数

Awake:文件创建时调用

OnDestroy:对象被销毁时调用

OnEnable:创建或加载对象时调用

OnDisable:销毁对象或即将重新加载脚本程序集时调用

OnValidata:编辑器才会调用的函数,unity在加载脚本或者Inspector窗口中更改值时调用

非持久数据

非持久数据指的是不管在编辑器内还是发布后都不会持久化的数据

我们可以根据自己的需求随时创建对应数据对象进行使用

使用SO生成非持久化数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
using UnityEngine;

namespace SO
{
public class Test : MonoBehaviour
{
private CharacterData data;

private void Start()
{
data = ScriptableObject.CreateInstance<CharacteryData>();

Debug.Log(data.atk);
}
}
}

非持久数据的优点

节约硬盘空间,仅在内存中使用

ScriptableObject数据持久化

Json结合SO存储数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
using System.IO;
using UnityEngine;

namespace SO
{
public class SaveManager : MonoBehaviour
{
[SerializeField]private CharacterData data;

private void Start()
{
var str = JsonUtility.ToJson(data);

File.WriteAllText(Application.persistentDataPath + "/testJson.json",str);

}
}
}
JSON
{"obj":{"instanceID":22550},"speed":5.0,"atk":5,"vc":{"instanceID":0}}

Json结合SO读取数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
using System.IO;
using UnityEngine;

namespace SO
{
public class SaveManager : MonoBehaviour
{
[SerializeField]private CharacterData data;

private void Start()
{
var str =File.ReadAllText(Application.persistentDataPath + "/testJson.json");
JsonUtility.FromJsonOverwrite(str,data);
Debug.Log(data.atk);
}
}
}

SO应用

  • 配置数据

SO适合做配置文件,因为配置文件一般是只读的,游戏不会修改配置文件

SO不需要第三方软件,比如Excel,Json编辑器等

  • 复用数据

当我们使用大量细粒度的对象时,比如子弹,许多子弹可以共享同一份数据,而不用为每个子弹都分配内存

  • 数据带来的多态行为
  • 使用单例获取数据