unity-ScriptableObject的详细讲解

ScriptableObject是什么

是Unity提供的一个数据配置存储的基类。可以用来保存大量数据的数据容器,
是一个可以自定义数据的资源文件,一般用来当关卡配置,或者其他的配置一类的文件,一般用于只用不改的公共数据。
可以发现,这个能做的和单例类也能做,单例类也是共享一份数据,有什么区别?
首先,当我们在编辑模式下修改了继承自 ScriptableObject 对象的数据文件内容时,修改的数据将被保存到磁盘上。但是在发布运行后,即使在游戏中修改了 ScriptableObject 的数据,改后的数据并不会保存在本地,重新打开运行时数据并还是配置的初始数据。

unity-ScriptableObject的详细讲解_第1张图片

主要作用

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

数据复用

unity-ScriptableObject的详细讲解_第2张图片
unity-ScriptableObject的详细讲解_第3张图片
unity-ScriptableObject的详细讲解_第4张图片
unity-ScriptableObject的详细讲解_第5张图片

创建的方法

1、

这种比较简便,在project文件内,点击加号就能看到,这种方法需要继承ScripteableObject类
unity-ScriptableObject的详细讲解_第6张图片

2、

在顶部菜单栏创建,自主传入类名,指定生成位置
unity-ScriptableObject的详细讲解_第7张图片

使用方法

1、直接声明一个资源类,然后在inspector,直接将asset拖上去
2、放到能加载到的文件夹内,直接加载
3、通过单例模式去加载,这样不用去动态加载和手动拖取
3、数据只会在编辑模式才会被更改,也可以在编辑模式下,运行时候用代码更改,但是不能再打包出去后更改

    void Start()
    {
        #region 知识点一 ScriptableObject数据文件的使用
        //1.通过Inspector中的public变量进行关联
        //1-1.创建一个数据文件
        //1-2.在继承MonoBehaviour类中申明数据容器类型的成员
        //    在Inspector窗口进行关联
        //data.PrintInfo();

        //2.通过资源加载的信息关联
        //加载数据文件资源
        //注意:Resources、AB包、Addressables都支持加载继承ScriptableObject的数据文件
        data = Resources.Load("MyDataTest");
        data.PrintInfo();

        //注意:如果多个对象关联同一个数据容器文件,他们共享的是一个对象
        //     因为是引用对象,所以在其中任何地方修改后,其它地方也会发生改变
        #endregion

        #region 知识点二 ScriptableObject的生命周期函数
        //ScriptableObject和MonoBehavior很类似
        //它也存在生命周期函数
        //但是生命周期函数的数量更少
        //主要做了解,一般我们使用较少

        //Awake 数据文件创建时调用

        //OnDestroy ScriptableObject 对象将被销毁时调用
        //OnDisable ScriptableObject 对象销毁时、即将重新加载脚本程序集时 调用
        //OnEnable ScriptableObject 创建或者加载对象时调用

        //OnValidate 编辑器才会调用的函数,Unity在加载脚本或者Inspector窗口中更改值时调用
        #endregion

        #region 知识点三 ScriptableObject好处的体现
        //1.编辑器中的数据持久化
        //通过代码修改数据对象中内容,会影响数据文件
        //相当于达到了编辑器中数据持久化的目的
        //(该数据持久化 只是在编辑模式下的持久,发布运行时并不会保存数据)
        data.i = 9999;
        data.f = 5.5f;
        data.b = false;

        //2.复用数据
        //如果多个对象关联同一个数据文件
        //相当于他们复用了一组数据,内存上更加节约空间
        #endregion

        #region 总结
        //其实创建出来的数据资源文件,你可以把它理解成一种记录数据的资源
        //它的使用方式,和我们以前使用Unity当中的其它资源规则是一样的
        //比如:预设体、音频文件、视频文件、动画控制器文件、材质球等等
        //只不过通过继承ScriptableObject类生成的数据资源文件,它主要是和数据相关的
        #endregion
    }

非持久化数据

 public MyData data;
    // Start is called before the first frame update
    void Start()
    {
        #region 知识点一 ScriptableObject的非持久化数据指的是什么
        //指的是不管在编辑器模式还是在发布后都 不会持久化的数据
        //我们可以根据自己的需求随时创建对应数据对象进行使用
        //就好像直接new一个数据结构类对象
        #endregion

        #region 知识点二 如何利用ScriptableObject生成非持久化的数据
        //利用ScriptableObject中的静态方法 CreateInstance<>()
        //该方法可以在运行时创建出指定继承ScriptableObject的对象
        //该对象只存在于内存当中,可以被GC
        //调用一次就创建一次

        //通过这种方式创建出来的数据对象 它里面的默认值 不会受到脚本中设置的影响
        //data = ScriptableObject.CreateInstance("MyData") as MyData;
        data = ScriptableObject.CreateInstance();

        data.PrintInfo();
        #endregion

        #region 知识点三 ScriptableObject的非持久化数据存在的意义
        //只是希望在运行时能有一组唯一的数据可以使用
        //但是这个数据又不太希望保存为数据资源文件浪费硬盘空间
        //那么ScriptableObject的非持久化数据就有了存在的意义
        //它的特点是
        //只在运行时使用,在编辑器模式下也不会保存在本地
        #endregion
    }

非持久化数据变成持久化

原理就是将类保存到其他持久化方式中,类如json xml、playerprefs、2进制等等,其实无意义,

 void Start()
    {
        #region 知识点一 回顾通过ScriptableObject创建非持久化数据
        MyData data = ScriptableObject.CreateInstance();
        #endregion

        #region 知识点二 回顾数据持久化
        //硬盘<=>内存
        //使用数据时从硬盘中读取
        //数据改变后保存到硬盘上
        //游戏退出程序关闭后,数据信息会被存储到硬盘上,达到持久化的目的

        //我们讲授过的数据持久化相关知识
        //PlayerPrefs
        //XML
        //Json
        //2进制

        //ScriptableObject并不适合用来做数据持久化功能
        //但是我们可以利用我们学过的数据持久化方案 让其持久化
        #endregion

        #region 知识点三 利用Json结合ScriptableObject存储数据
        data.PrintInfo();

        //data.i = 9999;
        //data.f = 6.6f;
        //data.b = true;
        //将数据对象 序列化为 json字符串
        //string str = JsonUtility.ToJson(data);
        //print(str);
        把数据序列化后的结果 存入指定路径当中
        //File.WriteAllText(Application.persistentDataPath + "/testJson.json", str);
        //print(Application.persistentDataPath);
        #endregion

        #region 知识点四 利用Json结合ScriptableObject读取数据
        //从本地读取 Json字符串
        string str = File.ReadAllText(Application.persistentDataPath + "/testJson.json");
        //根据json字符串反序列化出数据 将内容覆盖到数据对象中
        JsonUtility.FromJsonOverwrite(str, data);
        data.PrintInfo();
        #endregion

        #region 总结
        //对于ScriptableObject的数据
        //由于它在游戏发布运行过程中无法被持久化
        //我们可以利用 PlayerPrefs、XML、Json、2进制等等方式
        //让其可以达到被真正持久化的目的

        //但是我个人并不建议大家利用ScriptableObject来做数据持久化
        //有点画蛇添足的意思了
        #endregion
    }

复用数据

例如 发射子弹,每个子弹上都有速度、攻击力等等(不变的,固定的),那么创建n个子弹就要开辟n份数据,这有点浪费。这样可以用通用的,然后只去改通用的,这些子弹就会都变化了。要是用单例去做,还要哪边都要去通过GetInstance去访问单例的对象,然后去写代码去更改,这里直接去更改数据更方便

public class Bullet : MonoBehaviour
{
    public BulletInfo info;

    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        this.transform.Translate(Vector3.forward * info.speed * Time.deltaTime);
    }
}

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[CreateAssetMenu()]
public class BulletInfo : ScriptableObject
{
    public float speed;
    public int atk;
}

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Lesson6 : MonoBehaviour
{
    public BulletInfo info;
    // Start is called before the first frame update
    void Start()
    {
        #region 知识点一 使用预设体对象可能存在的内存浪费问题
        //对于只用不变的数据
        //以面向对象的思想去声明对象类是可能存在内存浪费的问题的

        //我们以子弹对象为例
        #endregion

        #region 知识点二 举例说明 利用ScriptableObject数据对象 更加节约内存

        #endregion

        #region 总结
        //对于不同对象,使用相同数据时
        //我们可以使用ScriptableObject来节约内存
        #endregion
    }

    // Update is called once per frame
    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
            info.speed += 1;
    }
}

单例模式的应用

用单例模式可以避免很多代码,例如,以前要手动拖取或者动态加载,浪费事,

//继承自这个类的子类,去创建子类的ScriptableObject 
public class SingleScriptableObject :ScriptableObject where T:ScriptableObject
{
    private static T instance;

    public static T Instance
    {
        get
        {
            //如果为空 首先应该去资源路劲下加载 对应的 数据资源文件
            if (instance == null)
            {
                //我们定两个规则
                //1.所有的 数据资源文件都放在 Resources文件夹下的ScriptableObject中
                //2.需要复用的 唯一的数据资源文件名 我们定一个规则:和类名是一样的
                instance = Resources.Load("ScriptableObject/" + typeof(T).Name);
            }
            //如果没有这个文件 为了安全起见 我们可以在这直接创建一个数据
            if(instance==null)
            {
                instance = CreateInstance();
            }
            //甚至可以在这里 从json当中读取数据,但是我不建议用ScriptableObject来做数据持久化

            return instance;
        }
    }
}
//这个TestData,去创建出一个名为TestData的asset,然后可以直接在其他类里点出来TestData.GetInstance().i
[CreateAssetMenu]
public class TestData : SingleScriptableObject
{
    public int i;
    public bool b;
}

void Start()
    {
        #region 知识点一 为什么要单例模式化的获取数据
        //对于只用不变并且要复用的数据
        //比如配置文件中的数据
        //我们往往需要在很多地方获取他们
        //如果我们直接通过在脚本中 public关联 或者 动态加载
        //如果在多处使用,会存在很多重复代码,效率较低
        //如果我们将此类数据通过单例模式化的去获取
        //可以提升效率,减少代码量
        #endregion

        #region 知识点二 实现单例模式化获取数据
        //知识点
        //面向对象、单例模式、泛型等等

        //我们可以实现一个ScriptableObject数据单例模式基类
        //让我们只需要让子类继承该基类
        //就可以直接获取到数据
        //而不再需要去通过 public关联 和 资源动态加载

        print(RoleInfo.Instance.roleList[0].id);
        print(RoleInfo.Instance.roleList[1].tips);

        print(TestData.Instance.i);
        print(TestData.Instance.b);
        #endregion

        #region 总结
        //这种基类比较适合 配置数据 的管理和获取
        //当我们的数据是 只用不变,并且是唯一的时候,可以使用该方式提高我们的开发效率
        //在此基础上你也可以根据自己的需求进行变形
        //比如添加数据持久化的功能,将数据从json中读取,并提供保存数据的方法
        //但是不建议大家用ScriptableObject来制作数据持久化功能
        //除非你有这方面的特殊需求
        #endregion
    }

    // Update is called once per frame
    void Update()
    {
        
    }

参考:

https://blog.csdn.net/qq_46044366/article/details/124310241

你可能感兴趣的:(unity,unity,单例模式,Scriptable)