目录
- ScriptableObject 概述
- ScriptableObject数据文件的创建
- 数据文件的使用
- 非持久数据
- 让其真正意义上的持久
- ScriptableObject的应用
- 配置数据
- 复用数据
- 数据带来的多态行为
- 单例模式化的获取数据
ScriptableObject 概述
ScriptableObject是什么
ScriptableObject是Unity提供的一个数据配置存储基类
它是一个可以用来保存大量数据的数据容器
就像是可以自定义的数据资源文件
它是一个类似MonoBehavior的基类
需要我们继承它来进行使用
它的主要作用是
1.数据复用(多个对象用同一个数据)
2.配置文件(配置游戏中的数据)
3.编辑模式下的数据持久化
数据复用
比如一个子弹对象
以前我们通过面向对象的思想去做的话
会写一个继承MonoBehaviour的脚本
相关的属性都会在这里声明
然后挂载到子弹模型预设体上
这样每次实例化一个子弹,如果数据不变
那么对内存来说,是有一定浪费的
因为每一个子弹预设体上都有该脚本
那么所有的属性都会分配一次内存
通过使用ScriptableObject可以有效避免内存的浪费
配置文件
以前我们要通过配置文件配置游戏相关的数据,比如玩家数据、怪物数据等等
是通过数据持久化相关知识来完成的,我们一般会通过xml、json、excel等方式来配置游戏数据
相对来说都是在Unity外部通过其它格式的文件对数据进行配置
而通过ScriptableObject我们可以直接在Unity内部
在Inspector窗口中就可以进行数据的配置
编辑模式下的数据持久化
当我们在编辑模式下修改了继承ScriptableObject对象的数据文件内容时
这些修改将被记录下来
因此在编辑模式下ScriptableObject是具有数据持久化特性的
但是需要注意的是,在发布运行时
ScriptableObject并不具备持久化特性(修改数据对象,并不会保存在本地)
ScriptableObject的好处
1.我们可以直接在Inspector窗口编辑配置数据,可以利用它来做配置文件
2.处理重复数据,减少数据拷贝时造成的内存占用,可以利用它来做公共数据
3.可以更方便的处理数据带来的多态行为
ScriptableObject数据文件的创建
自定义ScriptableObject数据容器
1.继承ScriptableObject类
2.在该类中声明成员(变量、方法等)
注意:声明后,我们边可以在Inspector窗口中看到变化
我们可以在其中进行设置,但是这些设置都是默认数据,并没有真正使用他们
这些关联信息都是通过脚本文件对应的Unity配置文件meta进行记录的
目前该数据只是一个数据容器模板
有了它我们之后才能根据它的信息创建对应的数据资源文件
根据自定义的ScriptableObject数据容器创建数据文件
注意:
该创建功能,其实就是根据自定义数据容器类创建了一个配置文件
该文件中记录了对应的数据容器类信息,以及其中变量关联的信息
之后我们在使用它时,本质上也是通过反射创建对象进行使用
具体的方法有两种:
//1.为类添加CreateAssetMenu通过菜单创建资源特性
[CreateAssetMenu(fileName = “默认文件名”, menuName = “在Asset/Create菜单中显示的名字”, order = 再Asset/Create菜单中的位置(多个时可以通过它来调整顺序))]
2.利用ScriptableObject的静态方法创建数据对象
然后将数据对象保存在工程目录下
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当中配置数据
2.项目之间的复用,我们可以拷贝继承ScriptableObject的脚本到任何工程中
数据文件的使用
ScriptableObject数据文件的使用
1.通过Inspector中的public变量进行关联
1-1.创建一个数据文件
1-2.在继承MonoBehaviour类中申明数据容器类型的成员
在Inspector窗口进行关联
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.编辑器中的数据持久化
通过代码修改数据对象中内容,会影响数据文件
相当于达到了编辑器中数据持久化的目的
(该数据持久化 只是在编辑模式下的持久,发布运行时并不会保存数据)
2.复用数据
如果多个对象关联同一个数据文件
相当于他们复用了一组数据,内存上更加节约空间
非持久数据
ScriptableObject的非持久化数据指的是什么
指的是不管在编辑器模式还是在发布后都 不会持久化的数据
我们可以根据自己的需求随时创建对应数据对象进行使用
就好像直接new一个数据结构类对象
如何利用ScriptableObject生成非持久化的数据
利用ScriptableObject中的静态方法 CreateInstance<>()
该方法可以在运行时创建出指定继承ScriptableObject的对象
该对象只存在于内存当中,可以被GC
调用一次就创建一次
通过这种方式创建出来的数据对象 它里面的默认值 不会受到脚本中设置的影响
ScriptableObject的非持久化数据存在的意义
只是希望在运行时能有一组唯一的数据可以使用
但是这个数据又不太希望保存为数据资源文件浪费硬盘空间
那么ScriptableObject的非持久化数据就有了存在的意义
它的特点是
只在运行时使用,在编辑器模式下也不会保存在本地
让其真正意义上的持久
利用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);
利用Json结合ScriptableObject读取数据
//从本地读取 Json字符串
string str = File.ReadAllText(Application.persistentDataPath + "/testJson.json");
//根据json字符串反序列化出数据 将内容覆盖到数据对象中
JsonUtility.FromJsonOverwrite(str, data);
data.PrintInfo();
ScriptableObject的应用
配置数据
ScriptableObject数据文件为什么非常适合用来做配置文件?
1.配置文件的数据在游戏发布之前定规则
2.配置文件的数据在游戏运行时只会读出来使用,不会改变内容
3.在Unity的Inspector窗口进行配置更加的方便
举例制作
以前我们的常规配置方式
都是利用之前学习过的 数据持久化四部曲当中的内容进行配置的
比如 xml配置 json配 置 excel配置
1、创建RoleInfo.cs脚本
[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;
}
2、使用数据
public RoleInfo info;
for (int i = 0; i < info.roleList.Count; i++)
{info.roleList[i].Print();
}
我们可以利用ScriptableObject数据文件 来制作编辑器相关功能
比如:Unity内置的技能编辑器、关卡编辑器等等
我们不需要把编辑器生成的数据生成别的数据文件,而是直接通过ScriptableObject进行存储
因为内置编辑器只会在编辑模式下运行,编辑模式下ScriptableObject具备数据持久化的特性
复用数据
使用预设体对象可能存在的内存浪费问题
对于只用不变的数据
以面向对象的思想去声明对象类是可能存在内存浪费的问题的
举例说明 利用ScriptableObject数据对象 更加节约内存
1、创建BulletInfo.cs脚本
[CreateAssetMenu()]
public class BulletInfo : ScriptableObject
{public float speed;public int atk;
}
2、创建Bullet脚本
public class Bullet : MonoBehaviour
{public BulletInfo info;// Start is called before the first frame updatevoid Start(){}// Update is called once per framevoid Update(){this.transform.Translate(Vector3.forward * info.speed * Time.deltaTime);}
}
数据带来的多态行为
某些行为的变化是因为数据的不同带来的
我们可以利用面向对象的特性和原则,以及设计模式相关知识点
结合ScriptableObject做出更加方便的功能
比如随机音效,物品拾取,AI等等等
随机音效(里氏替换原则和依赖倒转原则)
播放音乐时,可能会随机播放多个音效当中的一种
物品拾取(里氏替换原则和依赖倒转原则)
比如拾取一个物品,物品给玩家带来不同的效果
AI
不同数据带来的不同行为模式
为了方便我们使用,我们可以利用ScriptableObject的可配置性来制作这些功能
举例讲解
单例模式化的获取数据
为什么要单例模式化的获取数据
对于只用不变并且要复用的数据
比如配置文件中的数据
我们往往需要在很多地方获取他们
如果我们直接通过在脚本中 public关联 或者 动态加载
如果在多处使用,会存在很多重复代码,效率较低
如果我们将此类数据通过单例模式化的去获取
可以提升效率,减少代码量
实现单例模式化获取数据
知识点
面向对象、单例模式、泛型等等
我们可以实现一个ScriptableObject数据单例模式基类
让我们只需要让子类继承该基类
就可以直接获取到数据
而不再需要去通过 public关联 和 资源动态加载
1、创建SingleScriptableObject.cs脚本
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;}}
}
2、使用:将RoleInfo继承该类