赞
踩
Translated by Xdestiny
2018/3/17
日文原文地址:http://anchan828.github.io/editor-manual/web/scriptableobject.html。
渣翻,有些东西相当不确定,禁止转载
ScriptableObject
是一个用于生成单独Asset的结构。同时,它也能被称为是Unity中用于处理序列化的结构。
在Unity里面有单独的序列化结构,所有的Object(UnityEngine.Object
)都能够通过这个方法进行数据的序列化与反序列化。文件和Unity编辑器都能够方便的获取其中的数据。关于序列化请参考第五章《关于SerializedObject》
Unity内部的Asset(Material或者AnimationClip等)都是从UnityEngine.Object
衍生出来的。为了制作单独的Asset,需要制作UnityEngine.Object
的子类。不过对于用户而言是不允许制作UnityEngine.Object
的子类。所以用户如果要利用Unity中的序列化结构、生成单独的Asset,就必须借助ScriptableObject
。
只要有Unity编辑器的地方就会使用到ScriptableObject
。比如,SceneView或者是GameView之类的编辑器窗口,也是通过ScriptableObject
生成出来的。另外,Object在Inspector里面的GUI显示也要通过ScriptableObject
才行。说整个Unity编辑器都是由ScriptableObject
做成的并不为过。
为了制作ScriptableObject
,继承ScriptableObject
基类是首先要做的事情。这个时候,类名与Asset名必须要一样。另外,ScriptableObject
的限制和MonoBehaviour
是一样的。
using UnityEngine;
public class ExampleAsset : ScriptableObject
{
}
通过ScriptableObject.CreateInstance
来生成ScriptableObject
。使用new
操作符来生成是不行的。理由和MonoBehaviour
是一样的,Unity生成Object的时候必须经过序列化。
using UnityEngine;
using UnityEditor;
public class ExampleAsset : ScriptableObject
{
[MenuItem ("Example/Create ExampleAsset Instance")]
static void CreateExampleAssetInstance ()
{
var exampleAsset = CreateInstance<ExampleAsset> ();
}
}
实例化完成后是将Object作为Asset进行保存。通过AssetDatabase.CreateAsset
就能够生成Asset。
Asset的后缀名必须是.asset。如果是其他后缀名的话,Unity会无法识别。
[MenuItem ("Example/Create ExampleAsset")]
static void CreateExampleAsset ()
{
var exampleAsset = CreateInstance<ExampleAsset> ();
AssetDatabase.CreateAsset (exampleAsset, "Assets/Editor/ExampleAsset.asset");
AssetDatabase.Refresh ();
}
另外,使用CreateAssetMenu
属性的话会使得Asset的制作更加简单。
using UnityEngine;
using UnityEditor;
[CreateAssetMenu(menuName = "Example/Create ExampleAsset Instance")]
public class ExampleAsset : ScriptableObject
{
}
使用CreateAssetMenu
的情况下,会在[Assets/Create]下面生成菜单。
读取的方法很简单,只要使用AssetDatabase.LoadAssetAtPath
就可以了。
[MenuItem ("Example/Load ExampleAsset")]
static void LoadExampleAsset ()
{
var exampleAsset =
AssetDatabase.LoadAssetAtPath<ExampleAsset>
("Assets/Editor/ExampleAsset.asset");
}
和MonoBehaviour
一样,在字段上附加SerializeField
属性就能够使字段显示出来。另外,PropertyDrawer
在这里也同样适用。
using UnityEngine;
using UnityEditor;
public class ExampleAsset : ScriptableObject
{
[SerializeField]
string str;
[SerializeField, Range (0, 10)]
int number;
[MenuItem ("Example/Create ExampleAsset Instance")]
static void CreateExampleAssetInstance ()
{
var exampleAsset = CreateInstance<ExampleAsset> ();
AssetDatabase.CreateAsset (exampleAsset, "Assets/Editor/ExampleAsset.asset");
AssetDatabase.Refresh ();
}
}
首先,请想象一下父类以及持有父类变量的子类。
下面则是具体的实现代码。
父类
using UnityEngine;
public class ParentScriptableObject : ScriptableObject
{
[SerializeField]
ChildScriptableObject child;
}
子类
using UnityEngine;
public class ChildScriptableObject : ScriptableObject
{
//什么都没有的话Inpector很孤单的,所以添加一个
[SerializeField]
string str;
public ChildScriptableObject ()
{
//一开始Asset的名字
name = "New ChildScriptableObject";
}
}
译注:这里在构造函数里面设置name有可能报错,建议放在Awake或者OnEnbale中
接着,ParentScriptableObject
作为asset被保存起来。含有自变量的子类也试着实例化吧。
父类
using UnityEngine;
using UnityEditor;
public class ParentScriptableObject : ScriptableObject
{
const string PATH = "Assets/Editor/New ParentScriptableObject.asset";
[SerializeField]
ChildScriptableObject child;
[MenuItem ("Assets/Create ScriptableObject")]
static void CreateScriptableObject ()
{
//父类实例化
var parent = ScriptableObject.CreateInstance<ParentScriptableObject> ();
//子类实例化
parent.child = ScriptableObject.CreateInstance<ChildScriptableObject> ();
//把父类作为asset保存起来
AssetDatabase.CreateAsset (parent, PATH);
//使用import刷新状态
AssetDatabase.ImportAsset (PATH);
}
}
ParentScriptableObject
保存成Asset后,可以看一下它的Inspector。如图所示,字段child变成了Type mismatch。
试着双击一下显示着Type mismatch的地方,ChildScriptableObject
的信息会显示在Inspector上面。看上去并没有什么问题,很正常的切换过去了。
持有Type mismatch子类的父类生成完成后,就这样重新启动Unity。第二次查看ParentScriptableObject
的Inspector会发现child部分显示为None(null)。
这个是因为作为ScriptableObject
父类的UnityEngine.Object
被作为序列化数据处理的时候,必须要以Asset的形式保存到硬盘上。Type mismatch的状态下,虽然在Inspector上看着没问题,不过代表着硬盘上并不存在对应的Asset文件。也就是说,这个实例一旦消失(例如重启Unity等情况),那么就无法方位这个数据了。
想要避免Type mismatch很简单。只要把ScriptableObject
全部作为Asset保存下来就可以了。
不过,像现在这样存在着父类与子类关系的情况下,制作各种单独的Asset从管理角度来看并不是一个上策。如果子类的数量增加,一方面要用一个列表来进行管理,另一方面届时生成Asset后的文件管理也非常的麻烦。
Unity提供了SubAsset这样一种功能来用于整理具有继承关系的Asset。
在作为MainAsset的Asset添加UnityEngine.Object
就能作为SubAsset来处理。SubAsset中最容易理解的例子要数Model Asset。
在Model Asset里面,会含有Mesh或者Animation之类的Asset。通常来说,这些必须要单独存在。不过对于SubAsset来说,Mesh或者AniamtionClip这些Asset的信息都会被包含在Main Asset里面。
ScriptableObject
也能够使用SubAsset的功能,因而在硬盘上不容易增添的具有继承关系的ScirptableObject
也能够构建起来。 。
想要在UnityEngine.Object
上追加SubAsset,只需要在想要成为MainAsset的Asset上添加Object就可以了。
父类ScriptableObject
using UnityEngine;
using UnityEditor;
public class ParentScriptableObject : ScriptableObject
{
const string PATH = "Assets/Editor/New ParentScriptableObject.asset";
[SerializeField]
ChildScriptableObject child;
[MenuItem ("Assets/Create ScriptableObject")]
static void CreateScriptableObject ()
{
//父类实例化
var parent = ScriptableObject.CreateInstance<ParentScriptableObject> ();
//子类实例化
parent.child = ScriptableObject.CreateInstance<ChildScriptableObject> ();
//在父类上添加子类
AssetDatabase.AddObjectToAsset (parent.child, PATH);
//将父类作为Asset保存
AssetDatabase.CreateAsset (parent, PATH);
//调用Import更新状态
AssetDatabase.ImportAsset (PATH);
}
}
从下图中可以发现 ,有两个父类的ParentScriptableObject
。不过持有实际数据的则是在SubAsset中的ParentScriptableObject
。
这种状态,是Unity判断用户生成了特殊的Asset(例如生成SubAsset),因此MainAsset使用DefaultAsset进行表示。
译注:这里的信息已然过时,使用Unity5.6时并未出现该现象,而是只有一个父和一个子
还可以做到隐藏SubAsset,只有MainAsset显示出来。
[MenuItem ("Assets/Create ScriptableObject")]
static void CreateScriptableObject ()
{
var parent = ScriptableObject.CreateInstance<ParentScriptableObject> ();
parent.child = ScriptableObject.CreateInstance<ChildScriptableObject> ();
//隐藏SubAsset中的子物体
parent.child.hideFlags = HideFlags.HideInHierarchy;
AssetDatabase.AddObjectToAsset (parent.child, PATH);
AssetDatabase.CreateAsset (parent, PATH);
AssetDatabase.ImportAsset (PATH);
}
通过这种方式,Asset就不再以多层的表示,而是整理到了一个进行表示。
这种隐藏SubAsset的方法也能够用在AnimatorController
上面。让我们确认一下。
[MenuItem ("Assets/Set to HideFlags.None")]
static void SetHideFlags ()
{
//选中AnimatorController的状态下弹出菜单
var path = AssetDatabase.GetAssetPath (Selection.activeObject);
//获取SubAsset里面的所有东西
foreach (var item in AssetDatabase.LoadAllAssetsAtPath(path)) {
//把全部的标志位设置为None,解除隐藏状态
item.hideFlags = HideFlags.None;
}
//用Import进行刷新
AssetDatabase.ImportAsset (path);
}
运行上面的代码,会解除所有的HideFlags
,然后所有的SubAsset都会表示出来。
SubAsset的删除含简单。只要使用Object.DestoryImmediate
就能够删除SubAsset。
[MenuItem ("Assets/Remove ChildScriptableObject")]
static void Remove ()
{
var parent = AssetDatabase.LoadAssetAtPath<ParentScriptableObject> (PATH);
//删除子物体
Object.DestroyImmediate (parent.child, true);
//删除后会成为Missing状态,用Null取代
parent.child = null;
//用Import更新状态
AssetDatabase.ImportAsset (PATH);
}
桌面图标类似于。虽然不是特别重要,但也有修改的方法。
选中Script Asset后,再选中Icon部分,会弹出来修改Icon的面板。这里有一个[Other]按钮可以点击,然后选择想要变更的纹理。
还有一种修改Icon的方法就是在Gizmos文件夹里面放置图片文件,并取名为[ScriptableObject类名 Icon]。Gizmos文件夹是存在于assets文件夹下面,使用起来可能稍微有些麻烦。不过不会出现有三个一模一样的Icon并列的情况,从这一点来说很方便。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。