I needed that to draw a ScriptableObject how I wanted:
using UnityEditor;
using UnityEngine;
using static Helpers.Graphics;
[CustomEditor(typeof(NPCData))]
public sealed class NPCDataInspector : Editor {
private SerializedProperty npcName;
private SerializedProperty country;
private SerializedProperty mesh;
private SerializedProperty voices;
private SerializedProperty infosDicoSonic;
private SerializedProperty infosDicoShadow;
private SerializedProperty infosDicoMetalSonic;
private SerializedProperty infosDicoLast;
private void OnEnable() {
npcName = serializedObject.FindProperty("npcName");
country = serializedObject.FindProperty("country");
mesh = serializedObject.FindProperty("mesh");
voices = serializedObject.FindProperty("voices");
infosDicoSonic = serializedObject.FindProperty("infosDicoSonic");
infosDicoShadow = serializedObject.FindProperty("infosDicoShadow");
infosDicoMetalSonic = serializedObject.FindProperty("infosDicoMetalSonic");
infosDicoLast = serializedObject.FindProperty("infosDicoLast");
}
public override void OnInspectorGUI() {
EditorGUILayout.PropertyField(npcName);
EditorGUILayout.PropertyField(country);
EditorGUILayout.PropertyField(mesh);
EditorGUILayout.PropertyField(voices);
EditorGUILayout.Space();
EditorGUILayout.PropertyField(infosDicoSonic);
EditorGUILayout.PropertyField(infosDicoShadow);
EditorGUILayout.PropertyField(infosDicoMetalSonic);
EditorGUILayout.PropertyField(infosDicoLast);
serializedObject.ApplyModifiedProperties();
}
}
[CustomPropertyDrawer(typeof(NPCLine<,>), true)]
public class NPCLineDrawer : PropertyDrawer {
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
EditorGUI.BeginProperty(position, label, property);
float lineHeight = EditorGUIUtility.singleLineHeight;
SerializedProperty text = property.FindPropertyRelative("text");
SerializedProperty eventFlag = property.FindPropertyRelative("eventFlag");
SerializedProperty emotion = property.FindPropertyRelative("emotion");
SerializedProperty useHub = property.FindPropertyRelative("useHub");
SerializedProperty hub = property.FindPropertyRelative("hub");
SerializedProperty useMission = property.FindPropertyRelative("useMission");
SerializedProperty townMission = property.FindPropertyRelative("townMission");
SerializedProperty hasChoices = property.FindPropertyRelative("hasChoices");
SerializedProperty choices = property.FindPropertyRelative("choices");
string foldoutLabel = string.IsNullOrEmpty(text.stringValue) ? $"Element {property.propertyPath.Substring(property.propertyPath.LastIndexOf('[') + 1, property.propertyPath.LastIndexOf(']') - property.propertyPath.LastIndexOf('[') - 1)}" : text.stringValue;
// Calculate rect for foldout label
Rect foldoutLabelRect = new(position.x, position.y, position.width, lineHeight);
// Change foldout label color based on hasChoices
GUIStyle foldoutLabelStyle = new(EditorStyles.foldout);
if (useMission.boolValue) {
foldoutLabelStyle.normal.textColor = HexToColor("#00AAFF");
}
else if (eventFlag.objectReferenceValue) {
foldoutLabelStyle.normal.textColor = Color.green;
}
else if (hasChoices.boolValue) {
foldoutLabelStyle.normal.textColor = Color.yellow;
}
// Draw foldout
property.isExpanded = EditorGUI.Foldout(
foldoutLabelRect,
property.isExpanded,
foldoutLabel,
true,
foldoutLabelStyle
);
// Draw other fields if expanded
if (property.isExpanded) {
// Indent the child properties
EditorGUI.indentLevel++;
float emotionHeightBonus = emotion.isExpanded ? 6.5f : 0.5f;
float useHubHeightBonus = useHub.boolValue ? 1f : 0f;
float hubHeightBonus = hub.isExpanded ? 3.5f : 0.5f;
float missionHeightBonus = useMission.boolValue ? 1f : 0f;
float emotionHubHeightBonus = emotionHeightBonus + useHubHeightBonus + hubHeightBonus;
float foldablesHeightBonus = emotionHubHeightBonus + missionHeightBonus;
if (!useHub.boolValue) hub.isExpanded = false;
if (!hasChoices.boolValue) choices.isExpanded = false;
// Calculate rects
Rect textRect = new(position.x, position.y + lineHeight + 2f, position.width, lineHeight);
Rect eventFlagRect = new(position.x, position.y + 2f * (lineHeight + 2f), position.width, lineHeight);
Rect emotionRect = new(position.x, position.y + 3f * (lineHeight + 2f), position.width, lineHeight);
Rect useHubRect = new(position.x, position.y + (emotionHeightBonus + 4f) * (lineHeight + 2f), position.width, lineHeight);
Rect hubRect = new(position.x, position.y + (emotionHeightBonus + 5f) * (lineHeight + 2f), position.width, lineHeight);
Rect useMissionRect = new(position.x, position.y + (emotionHubHeightBonus + 5f) * (lineHeight + 2f), position.width, lineHeight);
Rect townMissionRect = new(position.x, position.y + (emotionHubHeightBonus + 6f) * (lineHeight + 2f), position.width, lineHeight);
Rect hasChoicesRect = new(position.x, position.y + (foldablesHeightBonus + 6.5f) * (lineHeight + 2f), position.width, lineHeight);
// Draw fields
EditorGUI.PropertyField(textRect, text);
EditorGUI.PropertyField(eventFlagRect, eventFlag);
// Draw emotion properties
if (emotion != null) {
// Draw emotion foldout
EditorGUI.PropertyField(emotionRect, emotion);
// If emotion is expanded, draw its properties
if (emotion.isExpanded) {
SerializedProperty faceAnimation = emotion.FindPropertyRelative("faceAnimation");
SerializedProperty bodyAnimation = emotion.FindPropertyRelative("bodyAnimation");
SerializedProperty emotionFX = emotion.FindPropertyRelative("emotionFX");
SerializedProperty textBubble = emotion.FindPropertyRelative("textBubble");
SerializedProperty textSize = emotion.FindPropertyRelative("textSize");
SerializedProperty voice = emotion.FindPropertyRelative("voice");
// Indent for emotion properties
EditorGUI.indentLevel++;
// Calculate rects for emotion fields
Rect faceAnimationRect = new(position.x, position.y + 4f * (lineHeight + 2f), position.width, lineHeight);
Rect bodyAnimationRect = new(position.x, position.y + 5f * (lineHeight + 2f), position.width, lineHeight);
Rect emotionFXRect = new(position.x, position.y + 6f * (lineHeight + 2f), position.width, lineHeight);
Rect textBubbleRect = new(position.x, position.y + 7f * (lineHeight + 2f), position.width, lineHeight);
Rect textSizeRect = new(position.x, position.y + 8f * (lineHeight + 2f), position.width, lineHeight);
Rect voiceRect = new(position.x, position.y + 9f * (lineHeight + 2f), position.width, lineHeight);
// Draw emotion fields
EditorGUI.PropertyField(faceAnimationRect, faceAnimation);
EditorGUI.PropertyField(bodyAnimationRect, bodyAnimation);
EditorGUI.PropertyField(emotionFXRect, emotionFX);
EditorGUI.PropertyField(textBubbleRect, textBubble);
EditorGUI.PropertyField(textSizeRect, textSize);
EditorGUI.PropertyField(voiceRect, voice);
// Un-indent emotion properties
EditorGUI.indentLevel--;
}
}
EditorGUI.PropertyField(useHubRect, useHub);
// Draw hub properties
if (useHub.boolValue && hub != null) {
// Draw hub foldout
if (useHub.boolValue) EditorGUI.PropertyField(hubRect, hub);
// If hub is expanded, draw its properties
if (hub.isExpanded) {
SerializedProperty hubName = hub.FindPropertyRelative("name");
SerializedProperty startPos = hub.FindPropertyRelative("startPos");
SerializedProperty startRot = hub.FindPropertyRelative("startRot");
// Indent for emotion properties
EditorGUI.indentLevel++;
// Calculate rects for emotion fields
Rect hubNameRect = new(position.x, position.y + (emotionHeightBonus + 6f) * (lineHeight + 2f), position.width, lineHeight);
Rect startPosRect = new(position.x, position.y + (emotionHeightBonus + 7f) * (lineHeight + 2f), position.width, lineHeight);
Rect startRotRect = new(position.x, position.y + (emotionHeightBonus + 8f) * (lineHeight + 2f), position.width, lineHeight);
// Draw emotion fields
EditorGUI.PropertyField(hubNameRect, hubName);
EditorGUI.PropertyField(startPosRect, startPos);
EditorGUI.PropertyField(startRotRect, startRot);
// Un-indent emotion properties
EditorGUI.indentLevel--;
}
}
EditorGUI.PropertyField(useMissionRect, useMission);
if (useMission.boolValue) EditorGUI.PropertyField(townMissionRect, townMission);
EditorGUI.PropertyField(hasChoicesRect, hasChoices);
// Draw choices if hasChoices is true
if (hasChoices.boolValue) {
Rect choicesRect = new(position.x, position.y + (foldablesHeightBonus + 7.5f) * (lineHeight + 2f), position.width, lineHeight);
EditorGUI.PropertyField(choicesRect, choices, true);
}
// Un-indent the child properties
EditorGUI.indentLevel--;
}
EditorGUI.EndProperty();
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
SerializedProperty emotion = property.FindPropertyRelative("emotion");
SerializedProperty useHub = property.FindPropertyRelative("useHub");
SerializedProperty hub = property.FindPropertyRelative("hub");
SerializedProperty useMission = property.FindPropertyRelative("useMission");
SerializedProperty townMission = property.FindPropertyRelative("townMission");
SerializedProperty hasChoices = property.FindPropertyRelative("hasChoices");
SerializedProperty choices = property.FindPropertyRelative("choices");
float emotionHeightBonus = emotion.isExpanded ? 6.5f : 0.5f;
float lineHeight = EditorGUIUtility.singleLineHeight;
float height = lineHeight;
if (property.isExpanded) {
height += (emotionHeightBonus + 6f) * (lineHeight + 2f); // Text, eventFlag, emotion
height += EditorGUI.GetPropertyHeight(hasChoices, true) + lineHeight * 0.25f; // hasChoices
if (useHub.boolValue) {
height += EditorGUI.GetPropertyHeight(hub, true) + lineHeight * 0.25f; // Choices
}
if (useMission.boolValue) {
height += EditorGUI.GetPropertyHeight(townMission, true) + lineHeight * 0.25f; // Choices
}
if (hasChoices.boolValue) {
height += EditorGUI.GetPropertyHeight(choices, true) + lineHeight * 0.25f; // Choices
}
}
return height;
}
}