在下拉菜单中显示ScriptableObjects,然后在Unity中选择项目

时间:2019-06-16 05:02:53

标签: unity3d drop-down-menu dropdown unity-editor

我应该如何创建PropertyAttributePropertyDrawer以便在Inspector的下拉菜单中显示ScriptableObject,以便在它们之间进行选择?

1 个答案:

答案 0 :(得分:1)

我已经发布了a repository on Github来解决此问题。用于在Inspector的下拉菜单中进行选择。

在Github链接中,您可以访问发行版页面中的示例文件夹和unitypackage,但如果您不想转到这些链接或链接发生任何问题,则可以按照以下说明进行操作:

ScriptableObject下拉列表:

ScriptableObjectDropdown是Unity Inspector的属性。它用于在下拉菜单中显示在项目中创建的ScriptableObject,并在Inspector中进行选择。

ScriptableObject Dropdown

代码:

ScriptableObjectDropdownAttribute.cs

// Copyright (c) ATHellboy (Alireza Tarahomi) Limited. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root.

using System;
using UnityEngine;

namespace ScriptableObjectDropdown
{
    /// <summary>
    /// Indicates how selectable scriptableObjects should be collated in drop-down menu.
    /// </summary>
    public enum ScriptableObjectGrouping
    {
        /// <summary>
        /// No grouping, just show type names in a list; for instance, "MainFolder > NestedFolder > SpecialScriptableObject".
        /// </summary>
        None,
        /// <summary>
        /// Group classes by namespace and show foldout menus for nested namespaces; for
        /// instance, "MainFolder >> NestedFolder >> SpecialScriptableObject".
        /// </summary>
        ByFolder,
        /// <summary>
        /// Group scriptableObjects by folder; for instance, "MainFolder > NestedFolder >> SpecialScriptableObject".
        /// </summary>
        ByFolderFlat
    }

    /// <example>
    /// <para>Usage Examples</para>
    /// <code language="csharp"><![CDATA[
    /// using UnityEngine;
    /// using ScriptableObjectDropdown;
    /// 
    /// [CreateAssetMenu(menuName = "Create Block")]
    /// public class Block : ScriptableObject
    /// {
    ///     // Some fields
    /// }
    /// 
    /// public class BlockManager : MonoBehaviour
    /// {
    ///     [ScriptableObjectDropdown] public Block targetBlock;
    ///     
    ///     // or
    ///     
    ///     [ScriptableObjectDropdown(grouping = ScriptableObjectGrouping.ByFolder)] public Block targetBlock;
    /// }
    /// 
    /// // or
    /// 
    /// [CreateAssetMenu(menuName = "Create Block Manager Settings")]
    /// public class BlockManagerSetting : ScriptableObject
    /// {
    ///     [ScriptableObjectDropdown] public Block targetBlock;
    ///     
    ///     // or
    ///     
    ///     [ScriptableObjectDropdown(grouping = ScriptableObjectGrouping.ByFolder)] public Block targetBlock;
    /// }
    /// ]]></code>
    /// </example>
    [AttributeUsage(AttributeTargets.Field, AllowMultiple = false)]
    public class ScriptableObjectDropdownAttribute : PropertyAttribute
    {
        public ScriptableObjectGrouping grouping = ScriptableObjectGrouping.None;

        public ScriptableObjectDropdownAttribute() { }
    }
}

将此内容放入“编辑器”文件夹中

ScriptableObjectDropdownDrawer.cs

// Copyright (c) ATHellboy (Alireza Tarahomi) Limited. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root.

using UnityEngine;
using UnityEditor;
using System.Reflection;
using System;
using System.Collections.Generic;

namespace ScriptableObjectDropdown.Editor
{
    // TODO: Mixed value (-) for selecting multi objects
    [CustomPropertyDrawer(typeof(ScriptableObjectDropdownAttribute))]
    public class ScriptableObjectDropdownDrawer : PropertyDrawer
    {
        private static List<ScriptableObject> _scriptableObjects = new List<ScriptableObject>();
        private static ScriptableObject _selectedScriptableObject;
        private static readonly int _controlHint = typeof(ScriptableObjectDropdownAttribute).GetHashCode();
        private static GUIContent _popupContent = new GUIContent();
        private static int _selectedControlID;
        private static readonly GenericMenu.MenuFunction2 _onSelectedScriptableObject = OnSelectedScriptableObject;
        private static bool isChanged;

        static ScriptableObjectDropdownDrawer()
        {
            EditorApplication.projectChanged += ClearCache;
        }

        public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
        {
            if (_scriptableObjects.Count == 0)
            {
                GetScriptableObjects(property);
            }

            Draw(position, label, property, attribute as ScriptableObjectDropdownAttribute);
        }

        public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
        {
            return EditorStyles.popup.CalcHeight(GUIContent.none, 0);
        }

        /// <summary>
        /// How you can get type of field which it uses PropertyAttribute
        /// </summary>
        private static Type GetPropertyType(SerializedProperty property)
        {
            Type parentType = property.serializedObject.targetObject.GetType();
            FieldInfo fieldInfo = parentType.GetField(property.propertyPath);
            if (fieldInfo != null)
            {
                return fieldInfo.FieldType;
            }
            return null;
        }

        private static bool ValidateProperty(SerializedProperty property)
        {
            Type propertyType = GetPropertyType(property);
            if (propertyType == null)
            {
                return false;
            }
            if (!propertyType.IsSubclassOf(typeof(ScriptableObject)) && propertyType != typeof(ScriptableObject))
            {
                return false;
            }
            return true;
        }

        /// <summary>
        /// When new ScriptableObject added to the project
        /// </summary>
        private static void ClearCache()
        {
            _scriptableObjects.Clear();
        }

        /// <summary>
        /// Gets ScriptableObjects just when it is a first time or new ScriptableObject added to the project
        /// </summary>
        private static ScriptableObject[] GetScriptableObjects(SerializedProperty property)
        {
            Type propertyType = GetPropertyType(property);
            string[] guids = AssetDatabase.FindAssets(String.Format("t:{0}", propertyType));
            for (int i = 0; i < guids.Length; i++)
            {
                _scriptableObjects.Add(AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(guids[i]), propertyType) as ScriptableObject);
            }

            return _scriptableObjects.ToArray();
        }

        private void Draw(Rect position, GUIContent label,
            SerializedProperty property, ScriptableObjectDropdownAttribute attribute)
        {
            if (label != null && label != GUIContent.none)
                position = EditorGUI.PrefixLabel(position, label);

            if (ValidateProperty(property))
            {
                if (_scriptableObjects.Count != 0)
                {
                    UpdateScriptableObjectSelectionControl(position, label, property, attribute);
                }
                else
                {
                    EditorGUI.LabelField(position, "There is no this type asset in the project");
                }
            }
            else
            {
                EditorGUI.LabelField(position, "Use it with non-array ScriptableObject or derived class of ScriptableObject");
            }
        }

        private static void UpdateScriptableObjectSelectionControl(Rect position, GUIContent label,
            SerializedProperty property, ScriptableObjectDropdownAttribute attribute)
        {
            ScriptableObject output = DrawScriptableObjectSelectionControl(position, label, property.objectReferenceValue as ScriptableObject, property, attribute);

            if (isChanged)
            {
                isChanged = false;
                property.objectReferenceValue = output;
            }
        }

        private static ScriptableObject DrawScriptableObjectSelectionControl(Rect position, GUIContent label,
            ScriptableObject scriptableObject, SerializedProperty property, ScriptableObjectDropdownAttribute attribute)
        {
            bool triggerDropDown = false;
            int controlID = GUIUtility.GetControlID(_controlHint, FocusType.Keyboard, position);

            switch (Event.current.GetTypeForControl(controlID))
            {
                case EventType.ExecuteCommand:
                    if (Event.current.commandName == "ScriptableObjectReferenceUpdated")
                    {
                        if (_selectedControlID == controlID)
                        {
                            if (scriptableObject != _selectedScriptableObject)
                            {
                                scriptableObject = _selectedScriptableObject;
                                isChanged = true;
                            }

                            _selectedControlID = 0;
                            _selectedScriptableObject = null;
                        }
                    }
                    break;

                case EventType.MouseDown:
                    if (GUI.enabled && position.Contains(Event.current.mousePosition))
                    {
                        GUIUtility.keyboardControl = controlID;
                        triggerDropDown = true;
                        Event.current.Use();
                    }
                    break;

                case EventType.KeyDown:
                    if (GUI.enabled && GUIUtility.keyboardControl == controlID)
                    {
                        if (Event.current.keyCode == KeyCode.Return || Event.current.keyCode == KeyCode.Space)
                        {
                            triggerDropDown = true;
                            Event.current.Use();
                        }
                    }
                    break;

                case EventType.Repaint:
                    if (scriptableObject == null)
                    {
                        _popupContent.text = "Nothing";
                    }
                    else
                    {
                        _popupContent.text = scriptableObject.name;
                    }
                    EditorStyles.popup.Draw(position, _popupContent, controlID);
                    break;
            }

            if (_scriptableObjects.Count != 0 && triggerDropDown)
            {
                _selectedControlID = controlID;
                _selectedScriptableObject = scriptableObject;

                DisplayDropDown(position, scriptableObject, attribute.grouping);
            }

            return scriptableObject;
        }

        private static void DisplayDropDown(Rect position, ScriptableObject selectedScriptableObject, ScriptableObjectGrouping grouping)
        {
            var menu = new GenericMenu();

            menu.AddItem(new GUIContent("Nothing"), selectedScriptableObject == null, _onSelectedScriptableObject, null);
            menu.AddSeparator("");

            for (int i = 0; i < _scriptableObjects.Count; ++i)
            {
                var scriptableObject = _scriptableObjects[i];

                string menuLabel = MakeDropDownGroup(scriptableObject, grouping);
                if (string.IsNullOrEmpty(menuLabel))
                    continue;

                var content = new GUIContent(menuLabel);
                menu.AddItem(content, scriptableObject == selectedScriptableObject, _onSelectedScriptableObject, scriptableObject);
            }

            menu.DropDown(position);
        }

        private static void OnSelectedScriptableObject(object userData)
        {
            _selectedScriptableObject = userData as ScriptableObject;
            var scriptableObjectReferenceUpdatedEvent = EditorGUIUtility.CommandEvent("ScriptableObjectReferenceUpdated");
            EditorWindow.focusedWindow.SendEvent(scriptableObjectReferenceUpdatedEvent);
        }

        private static string FindScriptableObjectFolderPath(ScriptableObject scriptableObject)
        {
            string path = AssetDatabase.GetAssetPath(scriptableObject);
            path = path.Replace("Assets/", "");
            path = path.Replace(".asset", "");

            return path;
        }

        private static string MakeDropDownGroup(ScriptableObject scriptableObject, ScriptableObjectGrouping grouping)
        {
            string path = FindScriptableObjectFolderPath(scriptableObject);

            switch (grouping)
            {
                default:
                case ScriptableObjectGrouping.None:
                    path = path.Replace("/", " > ");
                    return path;

                case ScriptableObjectGrouping.ByFolder:
                    return path;

                case ScriptableObjectGrouping.ByFolderFlat:
                    int last = path.LastIndexOf('/');
                    string part1 = path.Substring(0, last);
                    string part2 = path.Substring(last);
                    path = part1.Replace("/", " > ") + part2;
                    return path;
            }
        }
    }
}

用法示例:

  1. 创建要通过该脚本创建指定对象的ScriptableObject类。
using UnityEngine;

[CreateAssetMenu(menuName = "Create Block")]
public class Block : ScriptableObject
{
    // Some fields
}
  1. 在项目中创建ScriptableObjects。

Resources

  1. 通过在ScriptableObjectDropdownMonoBeahviour派生类中设置可选分组(默认分组为“无”)来使用ScriptableObject属性。

MonoBehavior:

using ScriptableObjectDropdown;
using UnityEngine;

public class BlockManager : MonoBehaviour
{
    // Without grouping (default is None)
    [ScriptableObjectDropdown] public Block firstTargetBlock;
    // By grouping
    [ScriptableObjectDropdown(grouping = ScriptableObjectGrouping.ByFolder)] public Block secondTargetBlock;
}

MonoBehaviour Default Grouping

MonoBehaviour ByFolder Grouping

ScriptableObject:

using UnityEngine;
using ScriptableObjectDropdown;

[CreateAssetMenu(menuName ="Create Block Manager Settings")]
public class BlockManagerSettings : ScriptableObject
{
    // Without grouping (default is None)
    [ScriptableObjectDropdown] public Block firstTargetBlock;
    // By grouping
    [ScriptableObjectDropdown(grouping = ScriptableObjectGrouping.ByFolderFlat)] public Block secondTargetBlock;
}

ScriptableObject Default Grouping

ScriptableObject ByFolderFlat Grouping