Unity自定义PropertyDrawer中枚举控件与滑块重叠问题求助
大家好,我最近在做Unity编辑器扩展的时候遇到个头疼的问题:我自定义了GapRangeAttribute和对应的GapRangeDrawer,用来在Inspector里给float变量显示可切换整数/浮点数模式的滑块。但现在的问题是,这个Drawer里的枚举选择框和滑块会和脚本里的其他变量重叠(就是红圈标注的部分)。我尝试过写一个自定义的CubeSpawnerEditor来手动布局,但效果没改善多少,最后又回到了原来的代码。
下面是我目前的代码和尝试过的方案:
1. 属性定义脚本(GapRangeAttribute)
using UnityEngine; using UnityEditor; public enum GapDisplayMode { Integers, Floats } public class GapRangeAttribute : PropertyAttribute { public float minRange; public float maxRange; public GapDisplayMode displayMode; public GapRangeAttribute(float minRange, float maxRange, GapDisplayMode displayMode) { this.minRange = minRange; this.maxRange = maxRange; this.displayMode = displayMode; } }
2. PropertyDrawer脚本(GapRangeDrawer)
using UnityEditor; using UnityEngine; [CustomPropertyDrawer(typeof(GapRangeAttribute))] public class GapRangeDrawer : PropertyDrawer { public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { GapRangeAttribute gapRange = attribute as GapRangeAttribute; if (property.propertyType == SerializedPropertyType.Float) { EditorGUI.BeginProperty(position, label, property); Rect popupPosition = new Rect(position.x, position.y, position.width, EditorGUIUtility.singleLineHeight); Rect sliderPosition = new Rect(position.x, position.y + EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing, position.width, EditorGUIUtility.singleLineHeight); gapRange.displayMode = (GapDisplayMode)EditorGUI.EnumPopup(popupPosition, "Display Mode", gapRange.displayMode); if (gapRange.displayMode == GapDisplayMode.Integers) { int intValue = EditorGUI.IntSlider(sliderPosition, label, Mathf.RoundToInt(property.floatValue), (int)gapRange.minRange, (int)gapRange.maxRange); property.floatValue = intValue; } else { float floatValue = EditorGUI.Slider(sliderPosition, label, property.floatValue, gapRange.minRange, gapRange.maxRange); property.floatValue = floatValue; } EditorGUI.EndProperty(); } else { EditorGUI.LabelField(position, label.text, "Use [GapRangeDrawer] with float values only!"); } } }
3. 测试用MonoBehaviour脚本(CubeSpawner)
using UnityEngine; using UnityEditor; public class CubeSpawner : MonoBehaviour { public GameObject cubePrefab; public LayerMask terrainLayer; [GapRange(0, 100, GapDisplayMode.Floats)] public float gap = 0f; // 还有其他变量比如cameraMoveSpeed、zoomSpeed等 }
我尝试过的替代方案(自定义Editor)
我还写了一个CubeSpawnerEditor手动控制布局,但问题依然存在,最后又回到了Drawer方案:
using UnityEditor; using UnityEngine; [CustomEditor(typeof(CubeSpawner))] public class CubeSpawnerEditor : Editor { private SerializedProperty gapProperty; private void OnEnable() { gapProperty = serializedObject.FindProperty("gap"); } public override void OnInspectorGUI() { serializedObject.Update(); CubeSpawner cubeSpawner = (CubeSpawner)target; EditorGUILayout.PropertyField(serializedObject.FindProperty("cubePrefab")); EditorGUILayout.PropertyField(serializedObject.FindProperty("terrainLayer")); GapDisplayMode displayMode = cubeSpawner.displayMode; displayMode = (GapDisplayMode)EditorGUILayout.EnumPopup("Display Mode", displayMode); EditorGUILayout.Space(); if (displayMode == GapDisplayMode.Floats) { EditorGUILayout.Slider(gapProperty, cubeSpawner.minFloatRange, cubeSpawner.maxFloatRange, "Gap"); } else if (displayMode == GapDisplayMode.Integers) { EditorGUILayout.IntSlider(gapProperty, (int)cubeSpawner.minIntRange, (int)cubeSpawner.maxIntRange, "Gap"); } EditorGUILayout.PropertyField(serializedObject.FindProperty("cameraMoveSpeed")); EditorGUILayout.PropertyField(serializedObject.FindProperty("zoomSpeed")); EditorGUILayout.PropertyField(serializedObject.FindProperty("rotationSpeed")); serializedObject.ApplyModifiedProperties(); } }
问题核心
现在的关键问题是:自定义的PropertyDrawer里我绘制了两行UI(枚举选择框+滑块),但Unity编辑器默认只给这个属性分配了一行的高度空间,导致下面的其他控件往上挤,和滑块重叠了。
解决方法:重写GetPropertyHeight方法
要解决重叠问题,我们需要告诉Unity编辑器这个属性需要占用多少高度。只需要在GapRangeDrawer里重写GetPropertyHeight方法,返回两行控件的高度加上间距即可:
修改后的完整GapRangeDrawer脚本:
using UnityEditor; using UnityEngine; [CustomPropertyDrawer(typeof(GapRangeAttribute))] public class GapRangeDrawer : PropertyDrawer { // 重写方法,告诉编辑器该属性需要的垂直高度 public override float GetPropertyHeight(SerializedProperty property, GUIContent label) { // 两行控件高度 + 标准垂直间距 return EditorGUIUtility.singleLineHeight * 2 + EditorGUIUtility.standardVerticalSpacing; } public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { GapRangeAttribute gapRange = attribute as GapRangeAttribute; if (property.propertyType == SerializedPropertyType.Float) { EditorGUI.BeginProperty(position, label, property); Rect popupPosition = new Rect(position.x, position.y, position.width, EditorGUIUtility.singleLineHeight); Rect sliderPosition = new Rect(position.x, position.y + EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing, position.width, EditorGUIUtility.singleLineHeight); gapRange.displayMode = (GapDisplayMode)EditorGUI.EnumPopup(popupPosition, "Display Mode", gapRange.displayMode); if (gapRange.displayMode == GapDisplayMode.Integers) { int intValue = EditorGUI.IntSlider(sliderPosition, label, Mathf.RoundToInt(property.floatValue), (int)gapRange.minRange, (int)gapRange.maxRange); property.floatValue = intValue; } else { float floatValue = EditorGUI.Slider(sliderPosition, label, property.floatValue, gapRange.minRange, gapRange.maxRange); property.floatValue = floatValue; } EditorGUI.EndProperty(); } else { EditorGUI.LabelField(position, label.text, "Use [GapRangeDrawer] with float values only!"); // 错误情况返回默认单行高度 base.GetPropertyHeight(property, label); } } }
原理说明
Unity的PropertyDrawer默认会给每个属性分配一行的高度(EditorGUIUtility.singleLineHeight)。当我们在OnGUI里绘制了多行控件时,必须通过GetPropertyHeight主动告诉编辑器这个属性需要更多的垂直空间,这样编辑器才会给这个属性留出足够的位置,不会让下面的控件往上重叠。
如果用自定义Editor的方式,EditorGUILayout会自动处理高度,但Drawer方案更通用,适合多个脚本复用这个属性。
备注:内容来源于stack exchange,提问作者Daniel Lip

