NWH Common
Search Results for

    NUI Editor Framework

    NWH User Interface (NUI) is a custom editor framework that gives inspectors consistent styling across all NWH assets.

    Overview

    NUI provides:

    • Titled, indented subsections for grouping related fields
    • Tab-based organization, with the selected tab remembered per component type
    • Consistent styling across all NWH packages
    • Tooltips for all fields
    • Custom property drawers
    • Layout that adapts to inspector width

    All NWH component inspectors use NUI.

    NUI draws only the fields an editor explicitly asks for. A serialized field that no drawer.Field() call names does not appear in the inspector at all, which is the usual reason a newly added field seems to be missing.

    NUI Components

    NUIEditor

    Base class for custom editors. Override OnInspectorNUI(), call the base implementation first, draw the fields, then call drawer.EndEditor(this).

    OnInspectorNUI() returns bool and the base implementation always returns true. The if (!base.OnInspectorNUI()) return false; line is the house pattern and is kept for consistency, not because the header can collapse the body - Unity's own component foldout does that.

    Partial editor example (the inspected component is omitted):

    using NWH.NUI;
    using UnityEditor;
    
    [CustomEditor(typeof(MyComponent))]
    public class MyComponentEditor : NUIEditor
    {
        public override bool OnInspectorNUI()
        {
            if (!base.OnInspectorNUI())
                return false;
    
            drawer.BeginSubsection("Main Settings");
            drawer.Field("fieldName");
            drawer.EndSubsection();
    
            drawer.BeginSubsection("Advanced");
            drawer.Field("advancedField");
            drawer.EndSubsection();
    
            drawer.EndEditor(this);
            return true;
        }
    }
    

    NUIPropertyDrawer

    Base class for custom property drawers.

    Features:

    • Consistent styling with NUIEditor
    • Tooltip support
    • Flexible layout

    Partial property-drawer example (the MyStruct declaration is omitted):

    using NWH.NUI;
    using UnityEditor;
    using UnityEngine;
    
    [CustomPropertyDrawer(typeof(MyStruct))]
    public class MyStructDrawer : NUIPropertyDrawer
    {
        public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
        {
            drawer.BeginProperty(position, property, label);
    
            drawer.Field("field1");
            drawer.Field("field2");
    
            drawer.EndProperty();
        }
    }
    

    Creating Custom Editors

    Basic Editor

    Partial editor example (the inspected VehicleComponent and its fields are omitted):

    using UnityEditor;
    using NWH.NUI;
    
    [CustomEditor(typeof(VehicleComponent))]
    public class VehicleComponentEditor : NUIEditor
    {
        public override bool OnInspectorNUI()
        {
            if (!base.OnInspectorNUI())
                return false;
    
            // Start drawing
            drawer.BeginSubsection("Physics");
            drawer.Field("mass");
            drawer.Field("drag");
            drawer.EndSubsection();
    
            drawer.BeginSubsection("Visuals");
            drawer.Field("meshRenderer");
            drawer.Field("material");
            drawer.EndSubsection();
    
            drawer.EndEditor(this);
            return true;
        }
    }
    

    Editor with Tabs

    drawer.HorizontalToolbar(name, texts) draws the tab row and returns the selected index. The selection is stored in EditorPrefs under a key scoped to the inspected type, so two components that use the same tab name keep separate selections. NUIDrawer.SetTabIndex(name, value) is static and has no type to scope with, so it writes the unscoped key; that value is read only while the drawer has no scoped entry of its own, which means it applies until the user clicks a tab.

    Partial OnInspectorNUI() implementation (the containing editor class is omitted):

    public override bool OnInspectorNUI()
    {
        if (!base.OnInspectorNUI())
            return false;
    
        // Create tabs
        drawer.BeginSubsection("Settings");
    
        int tabIndex = drawer.HorizontalToolbar("settingsTab",
            new string[] { "General", "Physics", "Audio" });
    
        switch (tabIndex)
        {
            case 0:  // General
                drawer.Field("componentName");
                drawer.Field("enabled");
                break;
    
            case 1:  // Physics
                drawer.Field("mass");
                drawer.Field("drag");
                break;
    
            case 2:  // Audio
                drawer.Field("audioSource");
                drawer.Field("volume");
                break;
        }
    
        drawer.EndSubsection();
        drawer.EndEditor(this);
        return true;
    }
    

    Conditional Fields

    Partial inspector-body example (place this after a successful base.OnInspectorNUI() call):

    drawer.BeginSubsection("Engine");
    
    drawer.Field("engineEnabled");
    
    // Only show engine settings if enabled
    if (drawer.FindProperty("engineEnabled").boolValue)
    {
        drawer.Field("horsePower");
        drawer.Field("maxRPM");
    }
    
    drawer.EndSubsection();
    

    Drawer Methods

    Fields

    Partial inspector-body examples:

    // Draw property field
    drawer.Field("propertyName");
    
    // Field with custom label
    drawer.Field("propertyName", alternateLabel: "Custom Label");
    
    // Disabled field (the second parameter controls whether editing is enabled)
    drawer.Field("propertyName", enabled: false);
    
    // Nested property
    drawer.Field("parent.child.propertyName");
    

    Buttons

    Partial inspector-body examples (DoSomething() and PerformAction() are application methods):

    // Button
    if (drawer.Button("Click Me"))
    {
        // Button clicked
        DoSomething();
    }
    
    // Button with a custom GUIStyle
    GUIStyle actionStyle = new(EditorStyles.miniButton)
    {
        fontStyle = FontStyle.Bold
    };
    
    if (drawer.Button("Action", actionStyle))
    {
        PerformAction();
    }
    

    Labels and Info

    Partial inspector-body examples:

    // Info box
    drawer.Info("This is an information message");
    
    // Warning box
    drawer.Info("This is a warning", MessageType.Warning);
    
    // Error box
    drawer.Info("This is an error", MessageType.Error);
    
    // Label
    drawer.Label("Status: Active");
    
    // Bold label on a tinted strip
    drawer.Title("Configuration");
    

    drawer.Header() is the component header that NUIEditor draws for you. Do not call it from an editor body; use Title or BeginSubsection there.

    Layout

    Partial inspector-body examples:

    // Space
    drawer.Space(10f);
    
    // Horizontal separator line
    drawer.HorizontalRuler();
    

    Subsections

    Partial inspector-body example:

    // Titled subsection
    drawer.BeginSubsection("Section Name");
    drawer.Field("field1");
    drawer.Field("field2");
    drawer.EndSubsection();
    

    Property Attributes

    NUI supports custom attributes for additional control:

    Tooltip Attribute

    Partial component field declaration:

    [Tooltip("Maximum speed in m/s")]
    public float maxSpeed = 50f;
    

    Automatically displayed in NUI editors.

    Range Attribute

    Partial component field declaration:

    [Range(0, 100)]
    public float volume = 50f;
    

    Creates slider in inspector.

    Header Attribute

    Do not use Unity's [Header]. NUI positions each field in a rect sized from EditorGUI.GetPropertyHeight, which does not account for decorator attributes, so a header attribute has no space reserved for it. Group fields with drawer.BeginSubsection() / drawer.EndSubsection() instead. No NWH component uses [Header].

    Styling

    Layout constants and the header colors live in the static NUISettings class. The values that matter when writing a custom editor:

    Member Value Meaning
    fieldHeight 23 Default height of one inspector row, in pixels
    fieldSpacing 3 Vertical gap between rows, in pixels
    textMargin 2 Margin around text elements, in pixels
    editorHeaderColor RGB 20, 125, 211 Header bar of a MonoBehaviour editor
    scriptableObjectHeaderColor RGB 220, 122, 32 Header bar of a ScriptableObject editor
    propertyHeaderColor RGB 78, 152, 213 Header bar of a property drawer

    Info, warning and error boxes are Unity's EditorGUI.HelpBox and follow the editor theme.

    Best Practices

    1. Group related properties in subsections.
    2. Give every public field a tooltip.
    3. Consider tabs if the editor has >3 sections.
    4. Hide fields that are irrelevant to the current settings.
    5. Use descriptive field names and custom labels.
    6. Follow NWH naming conventions.
    7. Add a drawer.Field() call for each new serialized field. NUI shows only what the editor names.

    Example: Complete Component Editor

    This complete editor script targets NWH.WheelController3D.WheelController:

    using NWH.NUI;
    using NWH.WheelController3D;
    using UnityEditor;
    
    [CustomEditor(typeof(WheelController))]
    public class CustomWheelControllerEditor : NUIEditor
    {
        public override bool OnInspectorNUI()
        {
            if (!base.OnInspectorNUI())
                return false;
    
            // Tabs for major categories
            int tabIndex = drawer.HorizontalToolbar("mainTab",
                new string[] { "Wheel", "Suspension", "Friction", "Debug" });
    
            switch (tabIndex)
            {
                case 0:  // Wheel
                    DrawWheelTab();
                    break;
    
                case 1:  // Suspension
                    DrawSuspensionTab();
                    break;
    
                case 2:  // Friction
                    DrawFrictionTab();
                    break;
    
                case 3:  // Debug
                    DrawDebugTab();
                    break;
            }
    
            drawer.EndEditor(this);
            return true;
        }
    
        private void DrawWheelTab()
        {
            drawer.BeginSubsection("Wheel Properties");
            drawer.Field("wheel.radius");
            drawer.Field("wheel.width");
            drawer.Field("wheel.mass");
            drawer.EndSubsection();
    
            drawer.BeginSubsection("Visuals");
            drawer.Field("wheel.rotatingContainer");
            drawer.Field("wheel.nonRotatingContainer");
            drawer.EndSubsection();
        }
    
        private void DrawSuspensionTab()
        {
            drawer.BeginSubsection("Spring");
            drawer.Field("spring.maxForce");
            drawer.Field("spring.maxLength");
            drawer.Field("spring.progressiveness");
            drawer.EndSubsection();
    
            drawer.BeginSubsection("Damper");
            drawer.Field("damper.bumpRate");
            drawer.Field("damper.reboundRate");
            drawer.EndSubsection();
        }
    
        private void DrawFrictionTab()
        {
            drawer.BeginSubsection("Friction");
            drawer.Field("activeFrictionPreset");
    
            if (drawer.Button("Create New Preset"))
            {
                // Create friction preset logic
            }
    
            drawer.EndSubsection();
        }
    
        private void DrawDebugTab()
        {
            WheelController wheelController = (WheelController)target;
    
            drawer.BeginSubsection("Debug Info");
            drawer.Info($"Is Grounded: {wheelController.isGrounded}");
            drawer.Label($"Spring Force: {wheelController.spring.forceMagnitude:F1} N");
            drawer.Label(
                $"Slip: {wheelController.frictionSlip.x:F2}, {wheelController.frictionSlip.y:F2}");
            drawer.EndSubsection();
        }
    }
    

    Extending NUI

    Custom Drawer Helper

    Create reusable drawer methods:

    Partial extension example (the surrounding editor script imports UnityEditor):

    public static class NUIExtensions
    {
        public static SerializedProperty CurveField(this NUIDrawer drawer,
            string propertyPath, string label = null)
        {
            return drawer.Field(propertyPath, alternateLabel: label);
        }
    }
    
    // Usage
    drawer.CurveField("torqueCurve");
    

    Custom Property Drawer

    Partial property-drawer example (the Wheel type and PreviewWheel() helper are omitted):

    [CustomPropertyDrawer(typeof(Wheel))]
    public class WheelDrawer : NUIPropertyDrawer
    {
        public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
        {
            drawer.BeginProperty(position, property, label);
    
            drawer.Field("radius");
            drawer.Field("width");
    
            // Custom visualization
            if (drawer.Button("Preview Wheel"))
            {
                PreviewWheel(property);
            }
    
            drawer.EndProperty();
        }
    }
    

    Troubleshooting

    Editor not displaying:

    • Ensure script compiles without errors
    • Check [CustomEditor(typeof(YourClass))] attribute
    • Verify class inherits from NUIEditor

    Fields not showing:

    • Check property name matches exactly (case-sensitive)
    • Ensure property is serializable
    • Verify property is public or has [SerializeField]

    Sections not collapsing:

    • BeginSubsection() creates a titled, indented group; it does not create a foldout.
    • The component header created by NUIEditor is the inspector's collapsible section.

    A tab selection made from script does nothing:

    • SetTabIndex writes the unscoped preference key. Once the user has clicked a tab on that component type, the scoped key exists and wins.

    Tooltips not working:

    • Add [Tooltip("...")] attribute to field
    • Draw the serialized field with drawer.Field("field"); Unity uses the property's tooltip.

    API Reference

    Key classes in the NWH.NUI namespace (editor-only):

    • NUIEditor - Base custom editor class
    • NUIDrawer - Drawing utilities for inspector elements
    • NUIPropertyDrawer - Base property drawer class
    In this article
    Back to top Copyright © NWH - Vehicle Physics, Aerodynamics, Dynamic Water Physics