NUI Editor Framework
NWH User Interface (NUI) is a custom editor framework providing consistent, professional inspectors across all NWH assets.
Overview
NUI provides:
- Collapsible sections with headers
- Tab-based organization
- Consistent styling across all NWH packages
- Tooltips for all fields
- Custom property drawers
- Responsive layout
All NWH component inspectors use NUI for a unified look and feel.
Benefits
- User-Friendly - Clear organization with collapsible sections
- Professional - Consistent appearance across all assets
- Informative - Tooltips explain every field
- Efficient - Collapse unused sections to reduce clutter
- Extensible - Easy to create custom editors
NUI Components
NUIEditor
Base class for custom editors.
Features:
- Automatic section grouping
- Collapsible headers
- Tab support
- Tooltip integration
- Responsive design
Example Usage:
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
Example:
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
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
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
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
// Draw property field
drawer.Field("propertyName");
// Field with custom label
drawer.Field("propertyName", "Custom Label");
// Field with tooltip
drawer.Field("propertyName", true); // Uses property tooltip
// Nested property
drawer.Field("parent.child.propertyName");
Buttons
// Button
if (drawer.Button("Click Me"))
{
// Button clicked
DoSomething();
}
// Button with custom size
if (drawer.Button("Action", 100, 30))
{
PerformAction();
}
Labels and Info
// Info box
drawer.Info("This is an information message");
// Warning box
drawer.Warning("This is a warning");
// Error box
drawer.Error("This is an error");
// Label
drawer.Label("Status: Active");
// Header
drawer.Header("Configuration");
Layout
// Begin horizontal group
drawer.BeginHorizontal();
drawer.Field("fieldA");
drawer.Field("fieldB");
drawer.EndHorizontal();
// Space
drawer.Space(10);
// Separator line
drawer.Separator();
Subsections
// Collapsible subsection
drawer.BeginSubsection("Section Name");
drawer.Field("field1");
drawer.Field("field2");
drawer.EndSubsection();
// Subsection with custom header
drawer.BeginSubsection("Settings", "settingsExpanded");
// Content
drawer.EndSubsection();
Property Attributes
NUI supports custom attributes for additional control:
Tooltip Attribute
[Tooltip("Maximum speed in m/s")]
public float maxSpeed = 50f;
Automatically displayed in NUI editors.
Range Attribute
[Range(0, 100)]
public float volume = 50f;
Creates slider in inspector.
Header Attribute
[Header("Physics Settings")]
public float mass;
public float drag;
Creates section header (NUI styling applied automatically).
Styling
NUI uses consistent colors and styling:
Colors
- Header Background: Dark gray
- Section Background: Medium gray
- Field Background: Light gray
- Info Box: Blue tint
- Warning Box: Yellow tint
- Error Box: Red tint
Fonts
- Headers: Bold, larger size
- Labels: Regular, standard size
- Tooltips: Italic, smaller size
Spacing
- Section Padding: 4px
- Field Spacing: 2px
- Subsection Margins: 8px
Best Practices
- Group Related Fields: Use subsections to organize related properties
- Provide Tooltips: Every public field should have a tooltip
- Use Tabs for Complexity: If editor has >3 sections, consider tabs
- Conditional Visibility: Hide irrelevant fields based on settings
- Clear Labels: Use descriptive field names and custom labels
- Consistent Naming: Follow NWH naming conventions
- Test Collapsing: Ensure editors work when sections are collapsed
Example: Complete Component Editor
using UnityEditor;
using NWH.NUI;
[CustomEditor(typeof(WheelController))]
public class WheelControllerEditor : 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("radius");
drawer.Field("width");
drawer.Field("mass");
drawer.EndSubsection();
drawer.BeginSubsection("Visuals");
drawer.Field("rotatingContainer");
drawer.Field("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()
{
drawer.BeginSubsection("Debug Info");
drawer.Info($"Is Grounded: {(target as WheelController).isGrounded}");
drawer.Label($"Spring Force: {(target as WheelController).springForce:F1} N");
drawer.Label($"Slip: {(target as WheelController).slip:F2}");
drawer.EndSubsection();
}
}
Extending NUI
Custom Drawer Helper
Create reusable drawer methods:
public static class NUIExtensions
{
public static void CurveField(this NUIDrawer drawer,
string propertyPath, string label = null)
{
SerializedProperty property = drawer.FindProperty(propertyPath);
EditorGUILayout.CurveField(
label ?? property.displayName,
property.animationCurveValue
);
}
}
// Usage
drawer.CurveField("torqueCurve");
Custom Property Drawer
[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:
- Each subsection needs unique key (second parameter)
- Check EditorPrefs for saved collapse states
Tooltips not working:
- Add
[Tooltip("...")]attribute to field - Ensure tooltip parameter is true:
drawer.Field("field", true)
API Reference
For detailed API documentation, see the API reference in the navigation menu for: