ScriptableObject的优点介绍——
Prefabs也做不到——
- 会有额外的一些Component,但其实我们只是想要存储数据而已,这些没有任何意义
- Prefab的确可以在项目和场景之间贡献,但很容易被搞得乱七八糟,我们只需要实例化一个prefab,然后就可以随意更改数据了
- 仍然在概念上不能更好的fit
1.在内部实现上它仍然继承自MonoBehaviour,但它不必附着在某个对象上作为一个Component
2.我们也不能(当然初衷就是不愿意)把它赋给Gameobject或Prefab
3.可以被serialised,而且可以自动有类似MonoBehavior的面板,很方便
4.可以被放到.asset文件中,也就是说我们可以自定义asset的类型。Unity内置的asset资源有材质、贴图、音频等等,现在依靠ScriptableObject我们可以自定义新的资源类型,来存储我们自己的数据
5.可以解决某些多态问题
ScriptableObject是如何解决我们的问题的:
没有其他多余的东西,例如多余的Component
ScriptableObject的缺点——
很少的回调函数
总结:其实说明白点,ScriptableObject的优点和缺点都是因为它表现起来就像一个类似材质、纹理等类型的资源,存在于Assets文件夹下,只有唯一实例
只需要把平时的继承自MonoBehaviour改成ScriptableObject即可
using UnityEngine;
[CreateAssetMenu(menuName="MySubMenue/Create MyScriptableObject ")]
public class MyScriptableObject : ScriptableObject
{
public int someVariable;
}
CreateAssetMenu可以让我们在资源创建菜单中添加创建这个ScriptableObject的选项,类似创建脚本、材质等其他资源。
我们也可以在脚本中动态创建一个ScriptableObject:
ScriptableObject.CreateInstance<MyScriptableObject >();
create可以是从脚本中被创建,当有其他对象引用该ScriptableObject时它会被load。
1.当它是被绑定到.asset文件或者AssetBundle等资源文件中的时候,它就是persistent的,这意味着
它可以通过Resources.UnloadUnusedAssets来被unload出内存
可以通过脚本引用或其他需要的时候被再次load到内存
2.如果是通过CreateInstance<>来创建的,它就是非persistent的,这意味着
它可以通过GC被直接destroy掉(如果没有任何引用的话)
如果不想被GC的话,可以使用HideFlags.HideAndDontSave
第一种最常见的就是数据对象和表格数据,我们可以在Assets下创建一个.asset文件,并在编辑器面板中编辑修改它,再提交这个唯一的一份资源给版本控制器。例如,本地化数据、清单目录、表格、敌人配置等(这些真的非常常见,目前我接触过的大部分都是通过json、xml文件或是Monobehaviour来实现的,json和xml文件对策划并不友好,Monobehaviour的问题前面就说过了)。
class EnemyInfo : ScriptableObject {
public int MaximumHealth;
public int DamagePerMeleeHit;
}
ScriptableObject的目的是只有一份,因此这里面不应该包括一些会根据实例不同而变化的数值。例如,我们在这个例子里没有声明敌人的生命值等变量,这是因为不同的敌人的生命值可能是不同的,这些属性应该在相应的MonoBehaviour里定义。
然后,我们就可以在真正的MonoBehaviour脚本中声明对ScriptableObject的引用:
class Enemy : MonoBehaviour {
public EnemyInfo info;
}
这保证所有的Enemy都会引用到同一个ScriptableObject对象。
使用ScriptableObject的一个好处是你不需要考虑序列化的问题,但是我们也可以和Json这些进行配合(使用JsonUtility),既支持直接在编辑器里创建ScriptableObject,也支持在运行时刻通过读取Json文件来创建。例子是,内置 + 用户自定义的场景文件,我们可以在编辑器里设计一些场景存储成.asset文件,而在运行时刻玩家可以自己设计关卡存储在Json文件里,然后可以据此生成相应的ScriptableObject。
我们经常会需要一个可以在场景间共享的Singleton对象,有时候我们就可以使用ScriptableObject + static instance variable的方法来解决,当场景变换的时候,我们可以使用Resources.FindObjectsOfTypeAll<>来找到已有的instance(当然这需要在实例化第一个instance的时候把它标识为instance.hideFlags = HideFlags.HideAndDontSave)。一个例子就是游戏状态和游戏设置。
class GameState : ScriptableObject {
public int lives, score;
private static GameState _instance;
public static GameState Instance {
get {
if (!_instance) {
// 如果为空,先试着从Resource中找到该对象
_instance = Resources.FindObjectOfType();
}
if (!_instance) {
// 如果仍然没有,就从默认状态中创建一个新的
// CreateDefaultGameState函数可以是从JSON文件中读取,并且在实例化完后指明_instance.hideFlags = HideFlags.HideAndDontSave
_instance = CreateDefaultGameState();
}
return _instance;
}
}
}
ScriptableObject除了可以存储数据外,我们还可以在ScriptableObject中定义一些方法,MonoBehaviour会把自身传递给ScriptableObject中的方法,然后ScriptableObject再进行一些工作。这类似于插槽设计模式,ScriptableObject提供一些槽,MonoBehaviour可以把自己插进去。适用于AI类型、加能量的buff或debuffs等