我应该如何创建PropertyAttribute
和PropertyDrawer
以便在Inspector的下拉菜单中显示ScriptableObject
,以便在它们之间进行选择?
答案 0 :(得分:1)
我已经发布了a repository on Github来解决此问题。用于在Inspector的下拉菜单中进行选择。
在Github链接中,您可以访问发行版页面中的示例文件夹和unitypackage,但如果您不想转到这些链接或链接发生任何问题,则可以按照以下说明进行操作:
ScriptableObject下拉列表:
ScriptableObjectDropdown是Unity Inspector的属性。它用于在下拉菜单中显示在项目中创建的ScriptableObject,并在Inspector中进行选择。
代码:
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;
}
}
}
}
用法示例:
using UnityEngine;
[CreateAssetMenu(menuName = "Create Block")]
public class Block : ScriptableObject
{
// Some fields
}
ScriptableObjectDropdown
或MonoBeahviour
派生类中设置可选分组(默认分组为“无”)来使用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;
}
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;
}