概念

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

使用场景

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

基本使用

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

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

创建资源脚本

[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}";
    }
}

指定资源使用

//使用
public ScriptableObject itemAsset;
itemAsset.title;
itemAsset.price;

单例资源脚本

在平时我们一般使用GameManager实现单例模式并设置在切换场景时不销毁来达到全局数据的管理。
在ScriptableObject中,我们同样可以达到这个效果,由于ScriptableObject是一个资源可直接存储在磁盘,不需要设置不销毁也能存储全局数据,管理同步。

创建单例资源

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}";
        }
    }
}

使用单例资源

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

临时资源

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

var item = ScriptableObject.CreateInstance<ItemAssets>();
item.title = "测试物品";
item.price = 100;
Debug.Log(item);

ScriptableObject.Destroy(item);

插槽式资源

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

创建资源

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

使用资源

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 来实现一些如游戏存档等的功能。

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中将这里的类脚本特异性的识别让其数据可视化,并在内部实现了实例化处理,让其可以直接在脚本中使用而无需实例化对象。