Unity中ScriptableObject数据容器详解
目录
根据自定义的ScriptableObject数据容器创建数据文件
ScriptableObject 是 Unity 提供的一个数据配置存储基类,它是一个可以用来保存大量数据的数据容器,就像是可以自定义的数据资源文件
它是一个类似 MonoBehavior 的基类,需要我们继承它来进行使用
它的主要作用是
-
数据复用(多个对象用同一个数据)
-
配置文件(配置游戏中的数据)
-
编辑模式下的数据持久化
ScriptableObject概述
定义 ScriptableObject 数据容器
-
继承 ScriptableObject 类
-
在该类中声明成员(变量、方法等)
注意:声明后,我们可以在 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 数据文件为什么非常适合用来做配置文件?
-
配置文件的数据在游戏发布之前定规则
-
配置文件的数据在游戏运行时只会读出来使用,不会改变内容
-
在 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()
{
}
}
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐


所有评论(0)