# 概念

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

[toc]

更新于 阅读次数

请我喝[茶]~( ̄▽ ̄)~*

Fasty 微信支付

微信支付

Fasty 支付宝

支付宝

Fasty 贝宝

贝宝