ScripableObject概述
ScripableObject是Unity提供的一个数据配置存储基类。用来保存大量数据的数据容器。
它的主要作用:
数据复用(多个对象用同一个数据)
比如一个子弹对象,以前我们通过面向对象的思想去做的话,会写一个继承MonoBehavior脚本,相关的属性都会在这里声明,然后挂载到子弹模型预设体上,这样每次实例一个子弹,如果数据不变,那么对内存来说,是有有一定浪费的。因为每一个子弹预设体上都有该脚本,那么所有的属性都会分配一次内存。通过使用ScriptableObject可以有效避免内存的浪费。
配置文件(配置游戏中的数据)
编辑模式下的数据持久化
注意的是,在发布运行时ScriptableObject并不具备持久化特性(修改数据对象,并不会保存本地)
ScriptableObject数据文件的创建
如果自定义ScriptableObject数据容器模板
继承ScriptableObject类
在该类中声明成员(变量、方法等)
注意:声明后,我们就可以在Inspector窗口中看到变化
我们可以在其中进行设置,但是这些设置都是默认数据,并没有真正使用他们
这些关联信息都是通过脚本文本对应的Unity配置文件meta进行记录的
而这个数据只是一个数据容器模板,有了它我们之后才能根据它的信息创建对应的数据资源文件
using UnityEngine;
namespace Core
{
public class MyData : ScriptableObject
{
public int _int;
public float _float;
public Vector2 _vector2;
public Vector3 _vector3;
public Vector4 _vector4;
public Color _color;
public Material _material;
public GameObject _gameObject;
}
}
根据数据容器模板创建数据文件
方法一:在挂载继承了ScriptableObject类上添加代码[CreateAssetMenu(fileName = "New Core Data", menuName = "Core Data")]
方法二:制作一个生成工具类
ScriptableObject数据文件的使用
使用ScriptableObject
方法一:通过Inspector中的public变量进行关联
创建一个数据文件,然后在继承MonoBehavior类中声明数据容器类型的成员,在Inspector窗口进行管理
using System;
using UnityEngine;
namespace Core
{
public class Test : MonoBehaviour
{
// 在Inspector面板拖拽数据文件
public MyData data;
private void Start()
{
print(data._int);
print(data._float);
print(data._vector2);
print(data._vector3);
print(data._color);
}
}
}
方法二:通过资源加载获取
using UnityEngine;
namespace Core
{
public class Test : MonoBehaviour
{
private MyData _data;
private void Start()
{
_data = Resources.Load<MyData>("ScriptableObjects/my");
print(_data._int);
print(_data._float);
print(_data._vector2);
print(_data._vector3);
print(_data._color);
}
}
}
注意:如果多个对象关联同一个数据容器文件,他们共享的是一个对象(因为是引用对象),在其他任何地方修改后,其他地方也会发生改变。
ScriptableObject的生命周期函数
using System;
using UnityEngine;
namespace Core
{
[CreateAssetMenu(fileName = "New Core Data", menuName = "Core Data")]
public class MyData : ScriptableObject
{
public int _int;
public float _float;
public Vector2 _vector2;
public Vector3 _vector3;
public Vector4 _vector4;
public Color _color;
public Material _material;
public GameObject _gameObject;
private void Awake()
{
Debug.Log("数据文件创建时调用");
}
private void OnDestroy()
{
Debug.Log("对象将被销毁时调用");
}
private void OnDisable()
{
Debug.Log("对象销毁时,即将重新加载及哦啊本程序集时调用");
}
private void OnValidate()
{
Debug.Log("编辑器才会调用的函数,Unity在加载脚本或者Inspector窗口中更改值时调用");
}
}
}
ScriptableObject好处的体现
编辑器中的数据持久化:通过代码修改数据对象中内容,会影响数据文件
复用数据:如果多个对象关联一个数据文件,修改这个数据文件,所有对象获得的值也会改变。
ScriptableObject非持久化数据
如果我们想要不管在编辑器模式上还是发布后都不会持久化数据(相当于在内存中创建,程序结束后会被GC回收),我们可以利用代码动态生成数据。
using UnityEngine;
namespace Core
{
public class Test : MonoBehaviour
{
private MyData _data;
private void Start()
{
_data = ScriptableObject.CreateInstance<MyData>();
}
}
}
ScriptableObject让其真正意义的持久
使用Json结合ScriptableObject存储数据
在这里我使用LitJson来读取和存储数据,所以需要引入LitJson包(可以看我之前写的持久化数据)
using UnityEngine;
namespace Core
{
public class Test : MonoBehaviour
{
private MyData _data;
private void Start()
{
_data = Resources.Load<MyData>("ScriptableObjects/my");
JsonManager.Instance.SaveData(_data, "mydata");
Debug.Log(Application.persistentDataPath);
}
}
}
对应的JSON数据
{
"name": "my",
"hideFlags": 0,
"_int": 40,
"_float": 2,
"_bool": true,
"_string": "1321",
"_floats": [
0,
1,
2,
3
]
}
使用Json结合ScriptableObject读取数据
using UnityEngine;
namespace Core
{
public class Test : MonoBehaviour
{
private MyData _data;
private void Start()
{
_data = JsonManager.Instance.LoadData<MyData>("mydata");
print(_data._bool);
print(_data._int);
print(_data._float);
print(_data._string);
foreach (var item in _data._floats)
{
print(item);
}
}
}
}
ScriptableObject的应用
配置文件
配置文件的数据在游戏发布之前定规则,而且在游戏运行时只会读出来使用,不会改变内容。且在Inspector窗口中进行配置更加的方便。
如何制作?接下来我们模拟物品的配置文件
using System;
using System.Collections.Generic;
using UnityEngine;
namespace Core
{
[CreateAssetMenu(fileName = "Items", menuName = "ScriptableObject/创建物品", order = 1)]
public class Items : ScriptableObject
{
[Serializable]
public class Item
{
public int Id;
public string Name;
public string Description;
public string IconRes;
public bool CanUse;
public bool CanFall;
}
public List<Item> items;
}
}
创建完,我们就可以使用了
using UnityEngine;
namespace Core
{
public class Test : MonoBehaviour
{
private Items _items;
private void Start()
{
_items = Resources.Load<Items>("ScriptableObjects/Items");
foreach (Items.Item item in _items.items)
{
print(item.Id);
print(item.Name);
print(item.Description);
print(item.CanFall);
print(item.CanUse);
print(item.IconRes);
}
}
}
}
注意:
配置数据是只用不改
数据复用
以一个子弹为例,通常我们会制作一个子弹预设体,子弹下面会挂载一个脚本,每个子弹都会有攻击力和移动速度,而且不会修改这些数据,就会造成内存占用。
using System;
using UnityEngine;
namespace Core
{
public class Bullet : MonoBehaviour
{
public int atk;
public float moveSpeed;
private void Update()
{
this.transform.Translate(transform.forward * moveSpeed * Time.deltaTime);
}
}
}
为了让多个子弹共用一个攻击力和移动速度,就可以使用我们的ScriptableObject
首先我们定义一下共用的脚本逻辑
using UnityEngine;
[CreateAssetMenu(fileName = "bulletInfo", menuName = "ScriptableObject/Bullet Info")]
public class BulletInfo : ScriptableObject
{
public int atk;
public float moveSpeed;
}
接着子弹脚本
using System;
using UnityEngine;
namespace Core
{
public class Bullet : MonoBehaviour
{
public BulletInfo info;
private void Update()
{
this.transform.Translate(transform.forward * (info.moveSpeed * Time.deltaTime));
}
}
}
这样我们就可以是实现复用。如果修改BulletInfo 里面的值,那么所有的子弹的攻击力和移动速度都会跟着改变。
数据带来的多态行为
某些行为的变化时因为数据的不同带来的
我们可以利用面向对象的特性和原则,以及设计模式相关知识点结合ScriptableObject做出更加方便的功能
比如:物体拾取后给玩家带来不同的效果。
我们准备一个物品拾取效果的基类
public abstract class ItemEffect : ScriptableObject
{
public abstract void AddEffect(GameObject player);
}
然后制作一个加血的类和加攻击力的类
[CreateAssetMenu]
public class AddHPEffect : ItemEffect
{
public int num;
public override void AddEffect(GameObject player)
{
// 模拟玩家加血
}
}
[CreateAssetMenu]
public class AddAtkEffect : ItemEffect
{
public int atk;
public override void AddEffect(GameObject player)
{
// 模拟玩家加攻击力
}
}
接下来我们就需要手动创建资源文件,然后使用它。什么物品触发什么效果,直接拖拽就可以了。
public class ItemInfo : MonoBehaviour
{
public ItemEffect itemEffect;
private void OnTriggerEnter(Collider other)
{
itemEffect.AddEffect(other.gameObject);
}
}
以上就是多态带来的好处。
单例模式化获取数据
我们之前获取资源文件是通过public关联(拖拽)或者动态加载,如果在多处使用,会存在很多重复代码,效率低下,如果我们将此类数据通过单例模式化去获取,可以提高效率,减少代码量。
所以我们可以实现一个ScriptableObject数据单例模式基类,让我们只需要让子类继承该基类,就可以直接获取到数据,而不再需要通过public关联和资源动态加载 了
using UnityEngine;
namespace Core.Utility
{
public abstract class SingleScriptableObject<T> : ScriptableObject where T : ScriptableObject
{
private static T _instance;
public static T Instance
{
get
{
if (_instance == null)
{
// 自定义规则(如何获取)
// 1. ScriptableObject资源文件放在Resource/ScriptableObject文件下
// 2. 类名作为资源文件名
_instance = Resources.Load<T>("ScriptableObjects/" + typeof(T).Name);
if (_instance == null)
{
_instance = CreateInstance<T>();
}
}
return _instance;
}
}
}
}
那么我们如何使用呢?我们可以修改上文中物品拾取效果类来作为例子
public abstract class ItemEffect : SingleScriptableObject0<ItemEffect>
{
public abstract void AddEffect(GameObject player);
}
[CreateAssetMenu]
public class AddHPEffect : ItemEffect
{
public int num;
public override void AddEffect(GameObject player)
{
// 模拟玩家加血
}
}
[CreateAssetMenu]
public class AddAtkEffect : ItemEffect
{
public int atk;
public override void AddEffect(GameObject player)
{
// 模拟玩家加攻击力
}
}
public class ItemInfo : MonoBehaviour
{
// 删除public关联的,直接通过单例模式去获取
// public ItemEffect itemEffect;
private void OnTriggerEnter(Collider other)
{
AddHPEffect.Instance.AddEffect(other.gameObject);
}
}
评论区