unity ScriptableObject的使用

概念

ScriptableObject是unity提出的针对数据存储资源的解决方案,能够将数据配置项可视化的展示在unityd属性面板中,并且极易于json进行数据交互。
个人使用过后觉得,其操作方式和继承非常类似,感觉像是对继承进一步封装使其更符合unity开发需要,让数据实体化,具现化的展示,通过拖拽式即可像拆装零件一般自由组合数据。(总而言之是对继承和多态的封装)

使用场景

用于ScriptableObject是针对unity的,在unity使用时比传统的数据格式如json,xml更加方便快捷。而且在面对诸如策划,美工等不了解程序的人员来说,上手更加快捷。
实质上,ScriptableObject主要还是对数据进行存储和处理,只要是使用数据的地方都可以使用ScriptableObject进行配置式管理,这样相比直接继承式的数据传递,耦合性更低。
更符合多组合少继承的设计理念。

基本使用

遵循步骤:

  1. 创建资源脚本,继承自UnityEngine.ScriptableObject
  2. 创建资源对象
  3. 在其他脚本中以组件形式指定资源对象

我们可以将一些诸如敌人的血量,攻击,防御等数值存储在ScriptableObject中。

创建资源脚本

1
2
3
4
5
6
7
8
9
10
11
12
[CreateAssetMenu(menuName = "My Assets/Create ItemAssets")]
public class ItemAssets : UnityEngine.ScriptableObject
{
//数据
public string title;
public int price;

public override string ToString()
{
return $"title:{title} price:{price}";
}
}

指定资源使用

1
2
3
4
//使用
public ScriptableObject itemAsset;
itemAsset.title;
itemAsset.price;

单例资源脚本

在平时我们一般使用GameManager实现单例模式并设置在切换场景时不销毁来达到全局数据的管理。
在ScriptableObject中,我们同样可以达到这个效果,由于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
using System.Linq;
using UnityEngine;

namespace Assets
{
[CreateAssetMenu(menuName = "My Assets/Create GameStateAssets")]
public class GameStateAsset : ScriptableObject
{
public int level;
public int score;

private static GameStateAsset _instance;
public static GameStateAsset Instance {
get
{
if (!_instance)
{
_instance= Resources.FindObjectsOfTypeAll<GameStateAsset>().FirstOrDefault();
}

if (!_instance)
{
_instance= CreateInstance<GameStateAsset>();
_instance.hideFlags = HideFlags.DontSave;
}

return _instance;
}
}

public override string ToString()
{
return $"level:{level} score:{score}";
}
}
}

使用单例资源

1
2
3
GameStateAsset.Instance.level = 8;
GameStateAsset.Instance.score = 255;
Debug.Log(GameStateAsset.Instance);

临时资源

有时我们需要动态的生成一些对象,这些对象的数据可能并不是静态的设置好的,并且只是临时的使用这些数据,我们可以直接创建临时的ScriptableObject来处理这种情况。

1
2
3
4
5
6
var item = ScriptableObject.CreateInstance<ItemAssets>();
item.title = "测试物品";
item.price = 100;
Debug.Log(item);

ScriptableObject.Destroy(item);

插槽式资源

有时数据是需要被再加工的,加工的过程不应该影响到数据的使用,对于使用数据的一方,不必知晓数据的具体加工过程,这些过程就好像一个个插槽,就等待着期望的数据进入即可。
从这里我们也可以看出,在ScriptableObject中是可以包含方法的。

创建资源

1
2
3
4
public abstract class PowerUpAsset : ScriptableObject
{
public abstract void Add(GameObject go);
}
1
2
3
4
5
6
7
8
9
[CreateAssetMenu(menuName = "My Assets/Create HealthUpAsset")]
public class HealthUpAsset : PowerUpAsset
{
public int value;
public override void Add(GameObject go)
{
go.GetComponent<Health>().currHealth += value;
}
}

使用资源

1
2
3
4
5
6
7
8
9
10
11
public class ItemHpUp : MonoBehaviour
{
public HealthUpAsset hpAsset;
private void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Player"))
{
hpAsset.Add(other.gameObject);
}
}
}

与Json交互

ScriptableObject和json有很高的亲和性(ScriptableObject就是基于类的延伸)
在某些情况下,我们可能需要将json数据存入ScriptableObject或将ScriptableObject写入json 来实现一些如游戏存档等的功能。

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
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Serialization;

namespace Assets.TalkJson
{
[CreateAssetMenu(menuName = "My Assets/Create GameFileAsset")]
public class GameFileAsset : ScriptableObject
{
[System.Serializable]
public class Archive
{
public string DateTime { get; set; }
public string Name { get; set; }
public int Level { get; set; }
}

private static GameFileAsset _instance;
public static GameFileAsset Instance
{
get
{
if (!_instance)
_instance = Resources.FindObjectsOfTypeAll<GameFileAsset>().FirstOrDefault();
#if UNITY_EDITOR
if (!_instance)
InitializeFromDefault(UnityEditor.AssetDatabase.LoadAssetAtPath<GameFileAsset>("Assets/Test game file.asset"));
#endif
return _instance;
}
}

public List<Archive> archives;

public Archive currArchive;

public static void InitializeFromDefault(GameFileAsset file)
{
if (_instance) DestroyImmediate(_instance);
_instance = Instantiate(file);
_instance.hideFlags = HideFlags.HideAndDontSave;
}

public static void LoadFromJson(string path)
{
if (!_instance) DestroyImmediate(_instance);
_instance = ScriptableObject.CreateInstance<GameFileAsset>();
JsonUtility.FromJsonOverwrite(System.IO.File.ReadAllText(path), _instance);
_instance.hideFlags = HideFlags.HideAndDontSave;
}

public void SaveToJson(string path)
{
Debug.LogFormat("Saving game file to {0}", path);
System.IO.File.WriteAllText(path, JsonUtility.ToJson(this, true));
}


}
}

个人理解

ScriptableObject其实就是对类的特殊处理,在unity中将这里的类脚本特异性的识别让其数据可视化,并在内部实现了实例化处理,让其可以直接在脚本中使用而无需实例化对象。