我想做的是:当布尔变量打开时,我想启用该游戏物体列表。当其关闭时,使用Unity自定义检视器方法或属性绘制器将其隐藏/灰化(我们无法向其中添加元素)。这难道很难实现吗?
最佳方法是使用自定义属性。我将从展示最终结果开始:
Using a field to hide/show another field:
public bool showHideList = false;
[ShowIf(ActionOnConditionFail.DontDraw, ConditionOperator.And, nameof(showHideList))]
public string aField = "item 1";
Using a field to enable/disable another field:
public bool enableDisableList = false;
[ShowIf(ActionOnConditionFail.JustDisable, ConditionOperator.And,
nameof(enableDisableList))]
public string anotherField = "item 2";
Using a method to get a condition value:
[ShowIf(ActionOnConditionFail.JustDisable, ConditionOperator.And,nameof(CalculateIsEnabled))]
public string yetAnotherField = "one more"; public
bool CalculateIsEnabled()
{
return true;
}
Using multiple conditions on the same field:
public bool condition1;
public bool condition2;
[ShowIf(ActionOnConditionFail.JustDisable, ConditionOperator.And, nameof(condition1),
nameof(condition2))]
public string oneLastField= "last field";
Define options for allowing mutiple conditions at once:
public enum ConditionOperator
{
// A field is visible/enabled only if all conditions are true.
And,
// A field is visible/enabled if at least ONE condition is true.
Or,
}
Define how the field is drawn if the condition fails:
public enum ActionOnConditionFail
{
// If condition(s) are false, don't draw the field at all.
DontDraw,
// If condition(s) are false, just set the field as disabled.
JustDisable,
}
Now create a custom attribute class, to hold data about the condition:
using System;
using UnityEngine;
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
public class ShowIfAttribute : PropertyAttribute
{
public ActionOnConditionFail Action {get;private set;}
public ConditionOperator Operator {get;private set;}
public string[] Conditions {get;private set;}
public ShowIfAttribute(ActionOnConditionFail action, ConditionOperator conditionOperator, params string[] conditions)
{
Action = action;
Operator = conditionOperator;
Conditions = conditions;
}
}
The meaty part where we tell unity how to handle fields with ShowIfAttribute
, this Drawer script needs to be under any 'Editor' folder:
using System.Reflection;
using UnityEditor;
using System.Collections.Generic;
using System;
using System.Linq;
using UnityEngine;
[CustomPropertyDrawer(typeof(ShowIfAttribute), true)]
public class ShowIfAttributeDrawer : PropertyDrawer
{
#region Reflection helpers.
private static MethodInfo GetMethod(object target, string methodName)
{
return GetAllMethods(target, m => m.Name.Equals(methodName,
StringComparison.InvariantCulture)).FirstOrDefault();
}
private static FieldInfo GetField(object target, string fieldName)
{
return GetAllFields(target, f => f.Name.Equals(fieldName,
StringComparison.InvariantCulture)).FirstOrDefault();
}
private static IEnumerable<FieldInfo> GetAllFields(object target, Func<FieldInfo,
bool> predicate)
{
List<Type> types = new List<Type>()
{
target.GetType()
};
while (types.Last().BaseType != null)
{
types.Add(types.Last().BaseType);
}
for (int i = types.Count - 1; i >= 0; i--)
{
IEnumerable<FieldInfo> fieldInfos = types[i]
.GetFields(BindingFlags.Instance | BindingFlags.Static |
BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.DeclaredOnly)
.Where(predicate);
foreach (var fieldInfo in fieldInfos)
{
yield return fieldInfo;
}
}
}
private static IEnumerable<MethodInfo> GetAllMethods(object target,
Func<MethodInfo, bool> predicate)
{
IEnumerable<MethodInfo> methodInfos = target.GetType()
.GetMethods(BindingFlags.Instance | BindingFlags.Static |
BindingFlags.NonPublic | BindingFlags.Public)
.Where(predicate);
return methodInfos;
}
#endregion
private bool MeetsConditions(SerializedProperty property)
{
var showIfAttribute = this.attribute as ShowIfAttribute;
var target = property.serializedObject.targetObject;
List<bool> conditionValues = new List<bool>();
foreach (var condition in showIfAttribute.Conditions)
{
FieldInfo conditionField = GetField(target, condition);
if (conditionField != null &&
conditionField.FieldType == typeof(bool))
{
conditionValues.Add((bool)conditionField.GetValue(target));
}
MethodInfo conditionMethod = GetMethod(target, condition);
if (conditionMethod != null &&
conditionMethod.ReturnType == typeof(bool) &&
conditionMethod.GetParameters().Length == 0)
{
conditionValues.Add((bool)conditionMethod.Invoke(target, null));
}
}
if (conditionValues.Count > 0)
{
bool met;
if (showIfAttribute.Operator == ConditionOperator.And)
{
met = true;
foreach (var value in conditionValues)
{
met = met && value;
}
}
else
{
met = false;
foreach (var value in conditionValues)
{
met = met || value;
}
}
return met;
}
else
{
Debug.LogError("Invalid boolean condition fields or methods used!");
return true;
}
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent
label)
{
// Calcluate the property height, if we don't meet the condition and the draw
mode is DontDraw, then height will be 0.
bool meetsCondition = MeetsConditions(property);
var showIfAttribute = this.attribute as ShowIfAttribute;
if (!meetsCondition && showIfAttribute.Action ==
ActionOnConditionFail.DontDraw)
return 0;
return base.GetPropertyHeight(property, label);
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent
label)
{
bool meetsCondition = MeetsConditions(property);
// Early out, if conditions met, draw and go.
if (meetsCondition)
{
EditorGUI.PropertyField(position, property, label, true);
return;
}
var showIfAttribute = this.attribute as ShowIfAttribute;
if(showIfAttribute.Action == ActionOnConditionFail.DontDraw)
{
return;
}
else if (showIfAttribute.Action == ActionOnConditionFail.JustDisable)
{
EditorGUI.BeginDisabledGroup(true);
EditorGUI.PropertyField(position, property, label, true);
EditorGUI.EndDisabledGroup();
}
}
}
如果不编写自定义编辑器窗口,无法将整个列表变灰。在Unity源代码中,您可以看到属性从未应用于数组。
在PropertyHandler.cs
文件中的HandleDrawnType
方法中,从第102行开始:
// Use PropertyDrawer on array elements, not on array itself.
// If there's a PropertyAttribute on an array, we want to apply it to the individual array elements instead.
// This is the only convenient way we can let the user apply PropertyDrawer attributes to elements inside an array.
if (propertyType != null && propertyType.IsArrayOrList())
return;
用于显示列表的内部类是UnityEditorInternal.ReorderableList
。本文展示了如何自定义它。我们可以使用它来强制标记有我们自定义属性的字段变灰。以下是我为任何Unity对象编写的尝试:
using UnityEditor;
using UnityEditorInternal;
using UnityEngine;
using System;
using System.Reflection;
using System.Collections.Generic;
[CustomEditor(typeof(UnityEngine.Object), true), CanEditMultipleObjects]
public class FullyDisabledListEditor : Editor
{
Dictionary<string, ReorderableList> _disabledLists;
private void OnEnable()
{
if(_disabledLists == null) _disabledLists = new();
var fields = serializedObject.targetObject.GetType().GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
foreach(FieldInfo field in fields)
{
if(!IsArrayOrList(field.FieldType)) continue;
IEnumerator<CustomAttributeData> attributes = field.CustomAttributes.GetEnumerator();
while (attributes.MoveNext())
{
if(attributes.Current.AttributeType != typeof(NotEditableAttribute)) continue;
CreateDisabledList(field.Name);
}
}
}
private bool IsArrayOrList(Type listType)
{
if (listType.IsArray)
return true;
else if (listType.IsGenericType && listType.GetGenericTypeDefinition() == typeof(List<>))
return true;
return false;
}
private void CreateDisabledList(string fieldName)
{
var listProperty = serializedObject.FindProperty(fieldName);
var list = new ReorderableList(
serializedObject,
listProperty,
false, //this setting allows you to rearrange the items
true, //this setting displays a header label
false, //this setting removes the 'add elements' button
false //this setting removes the 'remove elements' button
);
list.drawElementCallback = (Rect rect, int index, bool isActive, bool isFocused) =>
{
var property = list.serializedProperty.GetArrayElementAtIndex(index);
rect.y += 2; //for some reason fields are vertically off center by default
EditorGUI.PropertyField(rect, property);
};
list.drawHeaderCallback = (Rect rect) => {
EditorGUI.LabelField(rect, listProperty.displayName);
};
_disabledLists.Add(fieldName, list);
}
public override void OnInspectorGUI()
{
serializedObject.Update();
SerializedProperty property = serializedObject.GetIterator();
while (property.NextVisible(true))
HandlePropertyDisplay(property);
serializedObject.ApplyModifiedProperties();
}
private void HandlePropertyDisplay(SerializedProperty property)
{
//skip redrawing child properties
if(property.propertyPath.Contains('.', StringComparison.Ordinal)) return;
//gray out the script, as it normally is
if(property.propertyPath.Equals("m_Script", StringComparison.Ordinal))
{
ShowDisabledProperty(property);
return;
}
//gray out the arrays we marked
if(_disabledLists.ContainsKey(property.name))
{
_disabledLists[property.name].DoLayoutList();
return;
}
//everything else is shown normally (non-array fields will be grayed out by the attribute drawer)
EditorGUILayout.PropertyField(property);
}
private void ShowDisabledProperty(SerializedProperty property)
{
EditorGUI.BeginDisabledGroup(true);
EditorGUILayout.PropertyField(property, true);
EditorGUI.EndDisabledGroup();
}
}
很遗憾,我无法使用ChoopTwisk在他们的答案中编写的属性。我没有找到一种方法来检查编辑器类内条件的结果。在我的示例中,任何标记为[NotEditable]
的字段都将永久变灰。如果您想使用它,下面是其代码。我在这里找到了它。
using UnityEditor;
using UnityEngine;
public class NotEditableAttribute : PropertyAttribute { }
[CustomPropertyDrawer(typeof(NotEditableAttribute))]
public sealed class NotEditableDrawer : PropertyDrawer
{
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
return EditorGUI.GetPropertyHeight(property, label, true);
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
EditorGUI.BeginDisabledGroup(true);
EditorGUI.PropertyField(position, property, label, true);
EditorGUI.EndDisabledGroup();
}
}
public class Test : MonoBehaviour
{
[SerializeField]
bool openFlag = false;
bool prevOpenFlag = false;
void Update()
{
if(prevOpenFlag != openFlag)
{
if (openFlag == true)
{
Open();
prevOpenFlag = openFlag;
}else
{
Close();
prevOpenFlag = openFlag;
}
}
}