ScriptableObject 对象化的运用

http://www.cnblogs.com/oldman/articles/2409554.html

using UnityEngine;
using UnityEditor;
using System.IO;

public static class CustomAssetUtility
{
    public static void CreateAsset<T> () where T : ScriptableObject
    {
        T asset = ScriptableObject.CreateInstance<T> ();
        
        string path = AssetDatabase.GetAssetPath (Selection.activeObject);
        if (path == "") 
        {
            path = "Assets";
        } 
        else if (Path.GetExtension (path) != "") 
        {
            path = path.Replace (Path.GetFileName (AssetDatabase.GetAssetPath (Selection.activeObject)), "");
        }
        
        string assetPathAndName = AssetDatabase.GenerateUniqueAssetPath (path + "/New " + typeof(T).ToString() + ".asset");
        
        AssetDatabase.CreateAsset (asset, assetPathAndName);
        
        AssetDatabase.SaveAssets ();
        EditorUtility.FocusProjectWindow ();
        Selection.activeObject = asset;
    }
}

ScriptableObject 是Unity3D整个引擎的设计中,最为出彩的地方。通过他我们将数据保存,数据和编辑器的交互以及数据在runtime的使用三部分很方便的联系在一起。这是一个容易被Unity3D的初学者们容易忽略的领域。

简单的说,你可以把ScriptableObject当作Unity3D下的xml。但是其存储格式为二进制。让我们从一个实际例子出发,来理解ScriptableObject在实际项目中的整个运用。

假设我们有AbilityInfo需要设置和保存,我们可以将AbilityInfo制作成ScriptableObject,如下:

public class AbilityInfo : ScriptableObject {
 
    public enum Type {
        Unknown,
        FireBall,
        FireWall,
        IceBall,
        DarkForce,
        Heal,
        Poison,
    }
 
    public Type type = Type.Unknown;
    public float point = 10.0f;
    public float affectRange = 100.0f;
}

创建ScriptableObject

ScriptableObject 可以通过 ScriptableObject.CreateInstance<T>() 动态创建。然而将它保存为Unity3D Asset是ScriptableObject的设计本意。一般的,我们通过这个函数来完成整个创建过程:

public static class AbilityInfoUtility {
    public static AbilityInfo Create ( string _path, string _name ) {
        //
        if ( new DirectoryInfo(_path).Exists == false ) {
            Debug.LogError ( "can't create asset, path not found" );
            return null;
        }
        if ( string.IsNullOrEmpty(_name) ) {
            Debug.LogError ( "can't create asset, the name is empty" );
            return null;
        }
        string assetPath = Path.Combine( _path, _name + ".asset" );
 
        //
        AbilityInfo newAbilityInfo = ScriptableObject.CreateInstance<AbilityInfo>();
        AssetDatabase.CreateAsset(newAbilityInfo, assetPath);
        Selection.activeObject = newAbilityInfo;
        return newAbilityInfo;
    }
}
  • :!:注意1: 这是一个在Editor下使用的函数,所以这份代码需要放在项目中名为 “Editor” 的文件夹中 (目录层次无所谓),并且需要在代码开头处引入: using UnityEditor;
  • :!:注意2: ScriptableObject的脚本文件名必须和他的类名一致,并且一个脚本文件只能对应一个ScriptableObject定义。
  • :!:注意3: ScriptableObject只能保存为后缀名为”.asset”的文件,其他格式Unity3D将不能正确读取。

将Asset的创建方式提供给用户

对于编辑器的使用者,自然需要有UI或者方便的执行脚本方法来帮助创建数据。我们使用[MenuItem]来完成操作。以下代码会将Asset的创建加入到Project Window的右键菜单中:

class AbilityInfoUtility {
    ...
 
    [MenuItem ("Assets/Create/Ability Info")]
    static void Create () {
        // get current selected directory
        string assetName = "New AbilityInfo";
        string assetPath = "Assets";
        if ( Selection.activeObject ) {
            assetPath = AssetDatabase.GetAssetPath(Selection.activeObject);
            if ( Path.GetExtension(assetPath) != "" ) {
                assetPath = Path.GetDirectoryName(assetPath);
            }
        }
 
        //
        bool doCreate = true;
        string path = Path.Combine( assetPath, assetName + ".asset" );
        FileInfo fileInfo = new FileInfo(path);
        if ( fileInfo.Exists ) {
            doCreate = EditorUtility.DisplayDialog( assetName + " already exists.",
                                                    "Do you want to overwrite the old one?",
                                                    "Yes", "No" );
        }
        if ( doCreate ) {
            AbilityInfo abilityInfo = AbilityInfoUtility.Create ( assetPath, assetName );
            Selection.activeObject = abilityInfo;
            // EditorGUIUtility.PingObject(border);
        }
    }
}

自定义编辑显示

ScriptableObject 可以通过自定义编辑器的方式进行特殊编辑。编辑器方面有EditorWindow和Editor两种方式。EditorWindow更多地用在一些复杂数 据的可视化编辑。对于一些小的行为改变,我们可以通过Editor来实现。本节我们也已介绍Editor的编辑为主。设我们需要为AbilityInfo 在Inspector中加入预览图标。我们先于提供预览图标的美术工作人员约定好图标的存放位置和名称,如Editor/Icons/Ability 目录内。这里我们通过简单的硬编码来存取这些图标 (更灵活的做法是提供一些辅助的设置gui等)。我们在AbilityInfoUtility中添加如下函数:

public static class AbilityInfoUtility {
    ...
 
    public static Texture2D GetTextureByType ( AbilityInfo.Type _type ) {
        switch ( _type ) {
        case AbilityInfo.Type.Unknown:
            return AssetDatabase.LoadAssetAtPath ( "Assets/Editor/Icons/Ability/Unknown.png", typeof(Texture2D) ) as Texture2D;
 
        case AbilityInfo.Type.FireBall:
            return AssetDatabase.LoadAssetAtPath ( "Assets/Editor/Icons/Ability/FireBall.png", typeof(Texture2D) ) as Texture2D;
 
        case AbilityInfo.Type.FireWall:
            return AssetDatabase.LoadAssetAtPath ( "Assets/Editor/Icons/Ability/FireWall.png", typeof(Texture2D) ) as Texture2D;
 
        case AbilityInfo.Type.IceBall:
            return AssetDatabase.LoadAssetAtPath ( "Assets/Editor/Icons/Ability/IceBall.png", typeof(Texture2D) ) as Texture2D;
 
        case AbilityInfo.Type.DarkForce:
            return AssetDatabase.LoadAssetAtPath ( "Assets/Editor/Icons/Ability/DarkForce.png", typeof(Texture2D) ) as Texture2D;
 
        case AbilityInfo.Type.Heal:
            return AssetDatabase.LoadAssetAtPath ( "Assets/Editor/Icons/Ability/Heal.png", typeof(Texture2D) ) as Texture2D;
 
        case AbilityInfo.Type.Poison:
            return AssetDatabase.LoadAssetAtPath ( "Assets/Editor/Icons/Ability/Poison.png", typeof(Texture2D) ) as Texture2D;
        }
 
        return null;
    }
}

要自定义Inspector显示,只需要通过在派生的Editor中标记上[CustomEditor(Type)]这个Attribute即可,这里给出一个简单的示例:

[CustomEditor(typeof(AbilityInfo))]
public class AbilityInfoEditor : Editor {
 
    public override void OnInspectorGUI () {
        AbilityInfo editInfo = target as AbilityInfo;
 
        GUI.DrawTexture ( new Rect( 20, 20, 40, 40 ),  AbilityInfoUtility.GetTextureByType(editInfo.type) );
        GUILayoutUtility.GetRect ( 40, 40 );
        GUILayout.Space (5);
        base.OnInspectorGUI();
    }
}

:!:注意: 这里的base.OnInspectorGUI()实际上是比较偷懒的做法。对于需要仔细定义每个属性用途和显示的数据,请参考Unity3D的EditorGUI, EditorGUILayout文档认真编写。

在MonoBehavior中引用数据

现在我们只需要在MonoBehavior中引用这份数据,在读取场景的时候,就会自动帮助我们完成数据的序列化操作。

public class MyBehavior : MonoBehavior {
    public AbilityInfo normalSkill1;
    public AbilityInfo normalSkill2;
    public AbilityInfo specialSkill;
}
原文地址:https://www.cnblogs.com/123ing/p/4049638.html