目录

ScriptableObject概述

定义 ScriptableObject 数据容器

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

ScriptableObject数据文件的使用

ScriptableObject 的生命周期函数

音效设置面板练习

ScriptableObject非持久化数据

让ScriptableObject真正意义上的持久

利用Json结合ScriptableObject存储数据

配置数据

复用数据

数据带来的多态行为

单例模式化获取数据


ScriptableObject 是 Unity 提供的一个数据配置存储基类,它是一个可以用来保存大量数据的数据容器,就像是可以自定义的数据资源文件

它是一个类似 MonoBehavior 的基类,需要我们继承它来进行使用

它的主要作用是

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

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

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

ScriptableObject概述
定义 ScriptableObject 数据容器
  1. 继承 ScriptableObject 类

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

注意:声明后,我们可以在 Inspector 窗口中看到变化,我们可以在其中进行设置,但是这些设置都是默认数据,并没有真正使用他们,这些关联信息都是通过脚本文件对应的 Unity 配置文件 meta 进行记录的,目前该数据只是一个数据容器模板, 有了它我们之后才能根据它的信息创建对应的数据资源文件

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

public class MyData : ScriptableObject
{
    //可以声明任何类型的成员变量
    //但是如果需要在Inspector窗口可以编辑它,那声明规则需要和MonoBehavior当中public变量的规则一样
    public int i;
    public float f;
    public bool b;

    public GameObject gameObject;
    public Material material;
    public AudioClip clip;
    public VideoClip videoClip;

    public void Init()
    { }
}
根据自定义的ScriptableObject数据容器创建数据文件

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

该文件中记录了对应的数据容器类信息,以及其中变量关联的信息,之后我们在使用它时,本质上也是通过反射创建对象进行使用

具体的方法有两种:

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

[CreateAssetMenu (fileName = "默认文件名", menuName = "在 Asset/Create 菜单中显示的名字", order = 再 Asset/Create 菜单中的位置

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

[CreateAssetMenu(fileName ="MyDataScriptableObject",menuName ="Scriptable/MyData",order = 0)]
public class MyData : ScriptableObject
{
    //可以声明任何类型的成员变量
    //但是如果需要在Inspector窗口可以编辑它,那声明规则需要和MonoBehavior当中public变量的规则一样
    public int i;
    public float f;
    public bool b;

    public GameObject gameObject;
    public Material material;
    public AudioClip clip;
    public VideoClip videoClip;

    public void Init()
    { }
}

就可以在Project窗口右键创建数据文件

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

using System.Collections;
using System.Collections.Generic;
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 窗口进行关联

public class lesson2 : MonoBehaviour
{
    public MusicSettingInfo musicSettingInfo;
    void Start()
    {
        musicSettingInfo.PrintfInfo();
    }
}

2.通过资源加载的信息关联,加载数据文件资源

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

public class lesson2 : MonoBehaviour
{
    private MusicSettingInfo musicSettingInfo;
    void Start()
    {
        musicSettingInfo = Resources.Load<MusicSettingInfo>("MusicSettingInfo");
        musicSettingInfo.PrintfInfo();
    }
}

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

ScriptableObject 的生命周期函数

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

Awake 数据文件创建时调用

OnDestroy ScriptableObject 对象将被销毁时调用

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

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

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

音效设置面板练习

MusicSettingInfo

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

[CreateAssetMenu(fileName = "MusicSettingInfo",menuName ="ScriptableObject/音乐音效设置",order = 0)]
public class MusicSettingInfo : ScriptableObject
{
    public bool soundIsOpen;
    public bool musicIsOpen;

    public float musicVolume;
    public float soundVolume;

    public AudioClip soundClip;
    public AudioClip musicClip;

    public void PrintfInfo()
    {
        Debug.Log("musicIsOpen = " + musicIsOpen + " , soundIsOpen = " + soundIsOpen);
        Debug.Log("musicVolume = "+musicVolume+ " , soundVolume = "+soundVolume);
    }
}

MusicSettingPanel

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

public class MusicSettingPanel : MonoBehaviour
{
    public Slider soundSlider;
    public Slider musicSlider;

    public Toggle soundToggle;
    public Toggle musicToggle;

    private MusicSettingInfo musicSettingInfo;
    void Start()
    {
        musicSettingInfo = Resources.Load<MusicSettingInfo>("MusicSettingInfo");
        soundSlider.value = musicSettingInfo.soundVolume;
        musicSlider.value = musicSettingInfo.musicVolume;
        soundToggle.isOn = musicSettingInfo.soundIsOpen;
        musicToggle.isOn = musicSettingInfo.musicIsOpen;

        soundSlider.onValueChanged.AddListener((f) => {
            musicSettingInfo.soundVolume = f;
        });
        musicSlider.onValueChanged.AddListener((f) => {
            musicSettingInfo.musicVolume = f;
        });

        soundToggle.onValueChanged.AddListener((b) => {
            musicSettingInfo.soundIsOpen = b;
        });

        musicToggle.onValueChanged.AddListener((b) => {
            musicSettingInfo.musicIsOpen = b;
        });
    }

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

编辑器模式下可以实现数据持久化

ScriptableObject非持久化数据

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

指的是不管在编辑器模式还是在发布后都 不会持久化的数据,我们可以根据自己的需求随时创建对应数据对象进行使用,就好像直接 new 一个数据结构类对象

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

利用 ScriptableObject 中的静态方法 CreateInstance<>(),该方法可以在运行时创建出指定继承 ScriptableObject 的对象,该对象只存在于内存当中,可以被 GC,调用一次就创建一次

    public MusicSettingInfo musicSetting;
    void Start()
    {
        //通过这种方式创建出来的数据对象,它里面的默认值不会收到脚本中设置的影响
        //musicSetting = ScriptableObject.CreateInstance("MusicSettingInfo") as MusicSettingInfo;
        musicSetting = ScriptableObject.CreateInstance<MusicSettingInfo>();
        musicSetting.PrintfInfo();
    }

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

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

让ScriptableObject真正意义上的持久
利用Json结合ScriptableObject存储数据
    private MusicSettingInfo musicSettingInfo;
    // Start is called before the first frame update
    void Start()
    {
        musicSettingInfo = Resources.Load<MusicSettingInfo>("MusicSettingInfo");

        string str = JsonUtility.ToJson(musicSettingInfo);
        File.WriteAllText(Application.dataPath + "/Resources/MusicSetting.json",str);
    }

利用Json结合ScriptableObject读取数据

    private MusicSettingInfo musicSettingInfo;
    // Start is called before the first frame update
    void Start()
    {
        musicSettingInfo = Resources.Load<MusicSettingInfo>("MusicSettingInfo");

        //从本地读取Json字符串
        string str = File.ReadAllText(Application.dataPath + "/Resources/MusicSetting.json");
        //根据json字符串反序列化出数据 将内容覆盖到数据对象中
        JsonUtility.FromJsonOverwrite(str, musicSettingInfo);

        musicSettingInfo.PrintfInfo();
    }
配置数据

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

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

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

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

using System.Collections;
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 name;
        public int atk;
        public string tips;
        public float lockMoney;
        public int type;
        public string hitEff;
    }

    public List<RoleData> roleList;
}

    private RoleInfo roleInfo;
    void Start()
    {
        roleInfo = Resources.Load<RoleInfo>("RoleInfo");
        print(roleInfo.roleList[0].name);
        print(roleInfo.roleList[1].name);
        print(roleInfo.roleList[2].name);
    }

只用不改,并且经常会进行配置的数据,非常适合使用 ScriptableObject

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

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

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

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

复用数据

例如当创建子弹预设体时,我们会定义一个子弹对象,用来设置子弹飞行速度,攻击力等等

类似如下

public class lesson6 : MonoBehaviour
{
    public int speed = 1;
    public int atk = 5;

    void Update()
    {
        this.transform.Translate(Vector3.forward * speed * Time.deltaTime);
    }
}

但场景中我们发射子弹会创建n个子弹对象,每个子弹对象都会有一个lesson6脚本,那相同的speed和atk变量就会创建很多份,会分配大量内存空间,如果使用ScriptableObject的话就只需要分配一份内存空间

[CreateAssetMenu(fileName = "BulletInfo", menuName ="ScriptableObject/子弹对象设置")]
public class BulletInfo : ScriptableObject
{
    public int speed;
    public int atk;
}
public class lesson6 : MonoBehaviour
{
    private BulletInfo bulletInfo;
    private void Start()
    {
        bulletInfo = Resources.Load<BulletInfo>("BulletInfo");
    }
    void Update()
    {
        this.transform.Translate(Vector3.forward * bulletInfo.speed * Time.deltaTime);
    }
}

这样就可以达到节约内存空间的目的

对于不同的对象使用相同的数据时,我们可以使用scriptableObject来节约内存

数据带来的多态行为

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

某些行为的变化是因为数据的不同带来的,我们可以利用面向对象的特性和原则,以及设计模式相关知识点,结合 ScriptableObject 做出更加方便的功能

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

随机音效(里氏替换原则和依赖倒转原则),播放音乐时,可能会随机播放多个音效当中的一种

物品拾取(里氏替换原则和依赖倒转原则),比如拾取一个物品,物品给玩家带来不同的效果

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

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

AudioPlayBase

public abstract class AudioPlayBase : ScriptableObject
{
    public abstract void Play(AudioSource audioSource);
}

子类继承重写父类抽象方法

RandomPlayAudio随机播放音效

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

[CreateAssetMenu(fileName = "RandomPlayAudio", menuName = "ScriptableObject/随机音效播放设置")]
public class RandomPlayAudio : AudioPlayBase
{
    //希望随机播放的音效文件
    public List<AudioClip> clips;
    public override void Play(AudioSource audioSource)
    {
        if (clips.Count == 0)
            return;

        audioSource.clip = clips[Random.Range(0, clips.Count)];
        audioSource.Play();
    }
}

AudioPlay按指定要求播放音效

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu(fileName = "AudioPlay", menuName = "ScriptableObject/音乐音效播放设置")]
public class AudioPlay : AudioPlayBase
{
    public AudioClip clip;
    public override void Play(AudioSource audioSource)
    {
        audioSource.clip = clip;
        audioSource.Play();
    }
}

使用

    public AudioPlayBase audioPlay;
    // Start is called before the first frame update
    void Start()
    {
        //随机播放一种音效
        audioPlay = Resources.Load<RandomPlayAudio>("RandomPlayAudio");
        audioPlay.Play(this.GetComponent<AudioSource>());
        //按照ScriptableObject中的配置播放指定的音效
        audioPlay = Resources.Load<AudioPlay>("AudioPlay");
        audioPlay.Play(this.GetComponent<AudioSource>());
    }

单例模式化获取数据

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

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

SingleScriptableObject

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

public class SingleScriptableObject <T> : ScriptableObject where T : ScriptableObject
{
    private static T instance;

    public static T Instance
    {
        get
        {
            if (instance == null)
            {
                //如果为空先去资源路径下加载对应的数据资源文件
                instance = Resources.Load<T>(typeof(T).Name);
            }
            if(instance == null)
                instance = CreateInstance<T>();
            return instance;
        }
    }
}

RoleInfo

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

[CreateAssetMenu(fileName = "RoleInfo",menuName ="ScriptableObject/角色配置")]
public class RoleInfo : SingleScriptableObject<RoleInfo>
{
    [System.Serializable]
    public class RoleData
    {
        public int id;
        public string name;
        public int atk;
        public string tips;
        public float lockMoney;
        public int type;
        public string hitEff;
    }

    public List<RoleData> roleList;
}

使用

public class lesson8 : MonoBehaviour
{
    void Start()
    {
        print(RoleInfo.Instance.roleList[0].name);
    }

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

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐