首页 > 游戏开发 >  Unity中ScriptableObject的使用正文

Unity中ScriptableObject的使用

前言

    ScriptableObject在Unity中具有重要的作用和广泛的应用前景。通过合理使用ScriptableObject,开发者可以更有效地管理游戏数据、提高开发效率、优化游戏性能并增强游戏的可扩展性。


一、ScriptableObject是什么?

    ScriptableObject是Unity提供的一个数据配置存储基类。它是一个可以用来保存大量数据的数据容器,就像是可以自定义的数据资源文件。它是一个类似MonoBehavior的基类,需要我们继承它来进行使用它的主要作用是

1.数据复用(多个对象用同一个数据)

2.配置文件(配置游戏中的数据)

3.编辑模式下的数据持久化


1.数据复用

    比如一个子弹对象,以前我们通过面向对象的思想去做的话会写一个继承MonoBehaviour的脚本相关的属性都会在这里声明,然后挂载到子弹模型预设体上。这样每次实例化一个子弹,如果数据不变那么对内存来说,是有一定浪费的。因为每一个子弹预设体上都有该脚本,那么所有的属性都会分配一次内存,通过使用ScriptableObject可以有效避免内存的浪费。


2.配置文件

    以前我们要通过配置文件配置游戏相关的数据,比如玩家数据、怪物数据等等。是通过数据持久化相关知识来完成的,我们一般会通过xml、ison、excel等方式来配置游戏数据。相对来说都是在Unity外部通过其它格式的文件对数据进行配置。

而通过ScriptableObject我们可以直接在Unity内部在Inspector窗口中就可以进行数据的配置。


3.编辑模式下的数据持久化

    当我们在编辑模式下修改了继承ScriptableObject对象的数据文件内容时,这些修改将被记录下来。因此在编辑模式下ScriptableObject是具有数据持久化特性的。

但是需要注意的是,在发布运行时ScriptableObject并不具备持久化特性(修改数据对象,并不会保存在本地)。


二、ScriptableObject的好处

1.我们可以直接在Inspector窗口编辑配置数据,可以利用它来做配置文件

2.处理重复数据,减少数据拷贝时造成的内存占用,可以利用它来做公共数据

3.可以更方便的处理数据带来的多态行为


三、自定义ScriptableObject数据容器

1.继承ScriptableObject类

2.在该类中声明成员(变量、方法等)


注意:声明后,我们边可以在Inspector窗口中看到变化

     我们可以在其中进行设置,但是这些设置都是默认数据,并没有真正使用他们

     这些关联信息都是通过脚本文件对应的Unity配置文件meta进行记录的

     目前该数据只是一个数据容器模板

     有了它我们之后才能根据它的信息创建对应的数据资源文件

Unity中ScriptableObject的使用

using UnityEngine;
using UnityEngine.Video;
 
[CreateAssetMenu(fileName ="GaoData",menuName = "ScriptableObject/我的数据", order =0)]
public class MyData : ScriptableObject
{
    //声明成员时需要注意
    //我们可以声明任何类型的成员变量
    //但是需要注意:如果希望之后在Inspector窗口中能够编辑它
    //那你在这里声明的变量规则 要和 MonoBehavior当中public变量的规则是一样的
 
    public int i;
    public float f;
    public bool b;
 
    public GameObject obj;
    public Material m;
    public AudioClip audioClip;
    public VideoClip videoClip;
 
    private void Awake()
    {
        Debug.Log("数据文件创建时会调用");
    }
 
    private void OnEnable()
    {
 
    }
 
    private void OnDisable()
    {
 
    }
 
    private void OnDestroy()
    {
 
    }
 
 
    private void OnValidate()
    {
        //Debug.Log("123");
    }
 
    public void PrintInfo()
    {
        Debug.Log(i);
        Debug.Log(f);
        Debug.Log(b);
    }
}


四、根据自定义的ScriptableObject数据容器创建数据文件

注意:

该创建功能,其实就是根据自定义数据容器类创建了一个配置文件

该文件中记录了对应的数据容器类信息,以及其中变量关联的信息

之后我们在使用它时,本质上也是通过反射创建对象进行使用


具体的方法有两种:

1.为类添加CreateAssetMenu通过菜单创建资源特性

[CreateAssetMenu(fileName = "默认文件名", menuName = "在Asset/Create菜单中显示的名字", order = 再Asset / Create菜单中的位置(多个时可以通过它来调整顺序))]

Unity中ScriptableObject的使用

Unity中ScriptableObject的使用


2.利用ScriptableObject的静态方法创建数据对象,然后将数据对象保存在工程目录下。

using UnityEditor;
using UnityEngine;
 
public class ScriptableObjectTool
{
    [MenuItem("ScriptableObject/CreateMyData")]
    public static void CreateMyData()
    {
        //书写创建数据资源文件的代码
        MyData asset = ScriptableObject.CreateInstance<MyData>();
 
        //通过编辑器API 根据数据创建一个数据资源文件
        AssetDatabase.CreateAsset(asset, "Assets/Resources/MyDataTest.asset");
        //保存创建的资源
        AssetDatabase.SaveAssets();
        //刷新界面
        AssetDatabase.Refresh();
    }
}


五、ScriptableObject数据文件的使用

1.通过Inspector中的public变量进行关联

    1-1.创建一个数据文件

    1-2.在继承MonoBehaviour类中申明数据容器类型的成员

在Inspector窗口进行关联

data.PrintInfo();


2.通过资源加载的信息关联

    加载数据文件资源

    注意:Resources、AB包、Addressables都支持加载继承ScriptableObject的数据文件

data = Resources.Load<MyData>("MyDataTest");

data.PrintInfo();

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


六、ScriptableObject的生命周期函数

ScriptableObject和MonoBehavior很类似

它也存在生命周期函数

但是生命周期函数的数量更少

主要做了解,一般我们使用较少

Awake 数据文件创建时调用

OnDestroy ScriptableObject 对象将被销毁时调用

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

OnEnable ScriptableObject 创建或者加载对象时调用

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


七、ScriptableObject好处的体现

1.编辑器中的数据持久化

通过代码修改数据对象中内容,会影响数据文件

相当于达到了编辑器中数据持久化的目的

(该数据持久化 只是在编辑模式下的持久,发布运行时并不会保存数据)

data.i = 9999;

data.f = 5.5f;

data.b = false;


2.复用数据

如果多个对象关联同一个数据文件

相当于他们复用了一组数据,内存上更加节约空间


八、ScriptableObject的非持久化数据

1.ScriptableObject的非持久化数据指的是什么

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

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

    就好像直接new一个数据结构类对象


2.如何利用ScriptableObject生成非持久化的数据

    利用ScriptableObject中的静态方法 CreateInstance<>()

    该方法可以在运行时创建出指定继承ScriptableObject的对象

    该对象只存在于内存当中,可以被GC

    调用一次就创建一次

    通过这种方式创建出来的数据对象 它里面的默认值 不会受到脚本中设置的影响

//data = ScriptableObject.CreateInstance("MyData") as MyData;

data = ScriptableObject.CreateInstance<MyData>();

data.PrintInfo();


3.ScriptableObject的非持久化数据存在的意义

    只是希望在运行时能有一组唯一的数据可以使用

    但是这个数据又不太希望保存为数据资源文件浪费硬盘空间

    那么ScriptableObject的非持久化数据就有了存在的意义

    它的特点是

    只在运行时使用,在编辑器模式下也不会保存在本地


九、数据持久化

    硬盘 <=> 内存

    使用数据时从硬盘中读取

    数据改变后保存到硬盘上

    游戏退出程序关闭后,数据信息会被存储到硬盘上,达到持久化的目的


    数据持久化相关知识

PlayerPrefs

XML

Json

2进制


ScriptableObject并不适合用来做数据持久化功能

但是可以利用数据持久化方案 让其持久化


1.利用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);

2.利用Json结合ScriptableObject读取数据

//从本地读取 Json字符串

string str = File.ReadAllText(Application.persistentDataPath + "/testJson.json");

//根据json字符串反序列化出数据 将内容覆盖到数据对象中

JsonUtility.FromJsonOverwrite(str, data);

data.PrintInfo();


十、ScriptableObject的应用

1.配置数据

(1).ScriptableObject数据文件为什么非常适合用来做配置文件?

    1.配置文件的数据在游戏发布之前定规则

    2.配置文件的数据在游戏运行时只会读出来使用,不会改变内容

    3.在Unity的Inspector窗口进行配置更加的方便


(2).举例制作

using System.Collections.Generic;
using UnityEngine;
 
[CreateAssetMenu(fileName ="RoleInfo", menuName = "ScriptableObject/角色信息")]
public class RoleInfo : ScriptableObject
{
    [System.Serializable]
    public class RoleData
    {
        public int id;
        public string res;
        public int atk;
        public string tips;
        public int lockMoney;
        public int type;
        public string hitEff;
 
        public void Print()
        {
            Debug.Log(id);
            Debug.Log(res);
            Debug.Log(atk);
            Debug.Log(tips);
            Debug.Log(lockMoney);
            Debug.Log(type);
            Debug.Log(hitEff);
        }
    }
 
    public List<RoleData> roleList;
}

Unity中ScriptableObject的使用

using UnityEngine;
 
public class Use : MonoBehaviour
{
    public RoleInfo info;
    void Start()
    {
        for (int i = 0; i < info.roleList.Count; i++)
        {
            info.roleList[i].Print();
        }
    }
 
}

Unity中ScriptableObject的使用

只用不改

并且经常会进行配置的数据

非常适合使用ScriptableObject

我们可以利用ScriptableObject数据文件 来制作编辑器相关功能

比如:Unity内置的技能编辑器、关卡编辑器等等

      我们不需要把编辑器生成的数据生成别的数据文件,而是直接通过ScriptableObject进行存储

      因为内置编辑器只会在编辑模式下运行,编辑模式下ScriptableObject具备数据持久化的特性


2.复用数据

使用预设体对象可能存在的内存浪费问题

对于只用不变的数据

以面向对象的思想去声明对象类是可能存在内存浪费的问题的

我们以子弹对象为例

(1).面向对象思想编写脚本

using UnityEngine;
 
public class Bullet : MonoBehaviour
{
    public float speed;
    public int atk;
 
 
    // Update is called once per frame
    void Update()
    {
        this.transform.Translate(Vector3.forward * info.speed * Time.deltaTime);
    }
}

(2).ScriptableObject编写脚本

using UnityEngine;
 
public class Bullet : MonoBehaviour
{
    public BulletInfo info;
 
    // Update is called once per frame
    void Update()
    {
        this.transform.Translate(Vector3.forward * info.speed * Time.deltaTime);
    }
}
using UnityEngine;
 
[CreateAssetMenu()]
public class BulletInfo : ScriptableObject
{
    public float speed;
    public int atk;
}


十一、数据带来的多态行为

1.什么是数据带来的多态行为?

某些行为的变化是因为数据的不同带来的

我们可以利用面向对象的特性和原则,以及设计模式相关知识点

结合ScriptableObject做出更加方便的功能

比如随机音效,物品拾取,AI等等等

随机音效(里氏替换原则和依赖倒转原则)

播放音乐时,可能会随机播放多个音效当中的一种

物品拾取(里氏替换原则和依赖倒转原则)

比如拾取一个物品,物品给玩家带来不同的效果

AI

不同数据带来的不同行为模式


为了方便我们使用,我们可以利用ScriptableObject的可配置性来制作这些功能


2.物品效果代码

物品类

using UnityEngine;
 
public class ItemObj : MonoBehaviour
{
    public ItemEffect eff;
 
    private void OnTriggerEnter(Collider other)
    {
        //为和物品产生碰撞的对象加效果
        eff.AddEffect(other.gameObject);
    }
}


物品效果抽象类

using UnityEngine;
 
public abstract class ItemEffect : ScriptableObject
{
    public abstract void AddEffect(GameObject obj);
}


加血效果 

using UnityEngine;
 
[CreateAssetMenu]
public class AddHealthItemEffect : ItemEffect
{
    public int num;
    public override void AddEffect(GameObject obj)
    {
        //通过获取到的对象 让其加血了 加num的值
 
    }
}


加攻击力效果 

using UnityEngine;
 
[CreateAssetMenu]
public class AddAtkItemEffect : ItemEffect
{
    public int atk;
    public override void AddEffect(GameObject obj)
    {
       //具体加多少攻击力的逻辑
    }
}


为物品添加效果脚本

Unity中ScriptableObject的使用


十二、单例模式获取数据

1.为什么要单例模式化的获取数据?

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


2.实现单例模式化获取数据 

知识点

面向对象、单例模式、泛型等等

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

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


总结

优点

1.配置方便

2.节约内存

3.方便处理由数据带来的多态行为4.在编辑器模式下具备数据持久化特性


何时使用ScriptableObject

主要应用点:

1.配置文件

2.内置编辑器功能生成的数据文件(技能配置,关卡配置等等!次要应用点:

其它只用不改的公共数据


注意:不建议使用ScriptableObject来做数据持久化

我们想要制作的所有功能就算不使用ScriptableObject也能够通过别的方式制作出来,所以不要“为了用而用,一定要根据自己的实际情况来选择使用。记住它的优点(配置方便、节约内存等等),当遇到可以利用其优点更好的完成需求时再使用它。


下一篇: 最后一页