前言
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进行记录的
目前该数据只是一个数据容器模板
有了它我们之后才能根据它的信息创建对应的数据资源文件
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菜单中的位置(多个时可以通过它来调整顺序))]
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; }
using UnityEngine; public class Use : MonoBehaviour { public RoleInfo info; void Start() { for (int i = 0; i < info.roleList.Count; i++) { info.roleList[i].Print(); } } }
只用不改
并且经常会进行配置的数据
非常适合使用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) { //具体加多少攻击力的逻辑 } }
为物品添加效果脚本
十二、单例模式获取数据
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也能够通过别的方式制作出来,所以不要“为了用而用,一定要根据自己的实际情况来选择使用。记住它的优点(配置方便、节约内存等等),当遇到可以利用其优点更好的完成需求时再使用它。