Treasure chest of Unity developer tools. Professional inspector tooling, high-performance utilities, spatial queries, and 20+ editor tools.
A practical guide for migrating from Odin Inspector to Unity Helpers. All examples are verified against the actual source code.
| Odin Feature | Unity Helpers Equivalent |
|---|---|
[Button] |
[WButton] |
[ReadOnly] |
[WReadOnly] |
[ShowIf] / [HideIf] |
[WShowIf] |
[EnumToggleButtons] |
[WEnumToggleButtons] |
[ValueDropdown] |
[WValueDropDown], [IntDropDown], [StringInList] |
[BoxGroup] |
[WGroup] |
[FoldoutGroup] |
[WGroup(collapsible: true)] |
[InlineEditor] |
[WInLineEditor] |
[Required] |
[WNotNull], [ValidateAssignment] |
SerializedMonoBehaviour |
Standard MonoBehaviour |
SerializedDictionary |
SerializableDictionary<K,V> |
| N/A (paid feature) | SerializableHashSet<T> |
Odin:
using Sirenix.OdinInspector;
using Sirenix.Serialization;
public class Example : SerializedMonoBehaviour
{
public Dictionary<string, int> scores;
}
Unity Helpers:
using UnityEngine;
using WallstopStudios.UnityHelpers.Core.DataStructure.Adapters;
public class Example : MonoBehaviour
{
[SerializeField]
private SerializableDictionary<string, int> scores = new SerializableDictionary<string, int>();
}
Key differences:
MonoBehaviour)[SerializeField] or publicnew to avoid null references (good practice, Unity will initialize this like it does ListOdin:
using Sirenix.OdinInspector;
using Sirenix.Serialization;
public class Example : SerializedMonoBehaviour
{
public HashSet<string> unlockedItems;
}
Unity Helpers:
using UnityEngine;
using WallstopStudios.UnityHelpers.Core.DataStructure.Adapters;
public class Example : MonoBehaviour
{
[SerializeField]
private SerializableHashSet<string> unlockedItems = new SerializableHashSet<string>();
}
Odin:
[Button("Regenerate")]
private void RegenerateLevel() { }
[Button, ButtonGroup("Actions")]
private void Save() { }
Unity Helpers:
[WButton("Regenerate")]
private void RegenerateLevel() { }
[WButton(groupName: "Actions")]
private void Save() { }
Additional options:
// Control button order within a group
[WButton(drawOrder: 1, groupName: "Debug")]
private void PrintDebugInfo() { }
// Control group placement (top or bottom of inspector)
[WButton(groupName: "Authoring", groupPlacement: WButtonGroupPlacement.Top)]
private void GenerateIds() { }
✅ Automatic Odin Inspector Support:
WButton works automatically with Odin’s SerializedMonoBehaviour and SerializedScriptableObject - no setup required! Just use [WButton] on your methods.
Only need manual integration if:
OdinEditor for a specific typeOdin:
public bool showAdvanced;
[ShowIf("showAdvanced")]
public float advancedSetting;
Unity Helpers:
public bool showAdvanced;
[WShowIf(nameof(showAdvanced))]
public float advancedSetting;
Odin:
[HideIf("isDisabled")]
public float value;
Unity Helpers:
[WShowIf(nameof(isDisabled), inverse: true)]
public float value;
Odin:
public AttackType attackType;
[ShowIf("attackType", AttackType.Ranged)]
public float range;
Unity Helpers:
public AttackType attackType;
[WShowIf(nameof(attackType), AttackType.Ranged)]
public float range;
Odin:
[ShowIf("@level >= 5")]
public Ability ultimateAbility;
Unity Helpers:
[WShowIf(nameof(level), WShowIfComparison.GreaterThanOrEqual, 5)]
public Ability ultimateAbility;
Available comparisons: Equal, NotEqual, GreaterThan, GreaterThanOrEqual, LessThan, LessThanOrEqual, IsNull, IsNotNull, IsNullOrEmpty, IsNotNullOrEmpty
Odin:
[EnumToggleButtons]
public Direction direction;
[EnumToggleButtons]
public MovementFlags flags; // [Flags] enum
Unity Helpers:
[WEnumToggleButtons]
public Direction direction;
[WEnumToggleButtons(showSelectAll: true, showSelectNone: true)]
public MovementFlags flags; // [Flags] enum
Control buttons per row:
[WEnumToggleButtons(buttonsPerRow: 4)]
public DamageType damageTypes;
Odin:
[ValueDropdown("GetFrameRates")]
public int targetFrameRate;
private int[] GetFrameRates() => new[] { 30, 60, 120 };
Unity Helpers:
// Inline values (simplest)
[IntDropDown(30, 60, 120)]
public int targetFrameRate;
// Or with provider method
[IntDropDown(nameof(GetFrameRates))]
public int targetFrameRate;
private IEnumerable<int> GetFrameRates() => new[] { 30, 60, 120 };
Odin:
[ValueDropdown("GetDifficulties")]
public string difficulty;
Unity Helpers:
// Inline values
[StringInList("Easy", "Normal", "Hard")]
public string difficulty;
// Or with provider
[StringInList(nameof(GetDifficulties))]
public string difficulty;
Unity Helpers:
// Static provider from another class
[WValueDropDown(typeof(AudioManager), nameof(AudioManager.GetSoundNames))]
public string soundEffect;
// Instance provider (method on same class)
[WValueDropDown(nameof(GetAvailableWeapons), typeof(WeaponData))]
public WeaponData selectedWeapon;
Odin:
[BoxGroup("Movement")]
public float speed;
[BoxGroup("Movement")]
public float jumpHeight;
[FoldoutGroup("Advanced")]
public float acceleration;
Unity Helpers:
// Auto-include next N fields
[WGroup("Movement", autoIncludeCount: 2)]
public float speed; // Field 1: in group
public float jumpHeight; // Field 2: in group (auto-included, last by count)
// Or explicit end marker
[WGroup("Movement")]
public float speed; // In group
public float jumpHeight; // In group (auto-included)
[WGroupEnd] // friction IS included, then group closes
public float friction; // In group (last field)
// Collapsible (foldout)
[WGroup("Advanced", collapsible: true, startCollapsed: true)]
public float acceleration;
Unity Helpers:
[WGroup("Character", displayName: "Character Settings")]
public string characterName;
[WGroup("Stats", parentGroup: "Character")]
public int health;
public int mana;
Odin:
[InlineEditor]
public EnemyConfig config;
[InlineEditor(InlineEditorModes.GUIOnly)]
public ItemData item;
Unity Helpers:
[WInLineEditor]
public EnemyConfig config;
[WInLineEditor(WInLineEditorMode.FoldoutExpanded, inspectorHeight: 200f)]
public ItemData item;
Available modes: AlwaysExpanded, FoldoutExpanded, FoldoutCollapsed
Odin:
[Required]
public GameObject prefab;
[Required("Player reference is required!")]
public Transform player;
Unity Helpers:
[WNotNull]
public GameObject prefab;
[WNotNull(WNotNullMessageType.Error, "Player reference is required!")]
public Transform player;
// Runtime validation in Awake/Start
private void Awake()
{
this.CheckForNulls(); // Extension method
}
For validating that collections aren’t empty:
[ValidateAssignment]
public List<Transform> spawnPoints; // Warns if null or empty
[ValidateAssignment(ValidateAssignmentMessageType.Error, "Need at least one enemy type")]
public List<EnemyData> enemyTypes;
// Runtime check
private void Start()
{
this.ValidateAssignments();
}
Odin:
[ReadOnly]
public string generatedId;
Unity Helpers:
[WReadOnly]
public string generatedId;
Before (Odin):
using Sirenix.OdinInspector;
using Sirenix.Serialization;
using UnityEngine;
using System.Collections.Generic;
public class EnemySpawner : SerializedMonoBehaviour
{
[BoxGroup("Settings")]
[Required]
public GameObject enemyPrefab;
[BoxGroup("Settings")]
[ShowIf("useWaves")]
public int wavesCount = 3;
public bool useWaves;
[EnumToggleButtons]
public SpawnPattern pattern;
[ValueDropdown("GetSpawnRates")]
public float spawnRate;
public Dictionary<string, int> enemyWeights;
[Button("Spawn Wave")]
private void SpawnWave() { }
private float[] GetSpawnRates() => new[] { 0.5f, 1f, 2f };
}
After (Unity Helpers):
using UnityEngine;
using System.Collections.Generic;
using WallstopStudios.UnityHelpers.Core.Attributes;
using WallstopStudios.UnityHelpers.Core.DataStructure.Adapters;
public class EnemySpawner : MonoBehaviour
{
[WGroup("Settings", autoIncludeCount: 2)]
[WNotNull]
[SerializeField]
private GameObject enemyPrefab;
[WShowIf(nameof(useWaves))]
[SerializeField]
private int wavesCount = 3;
[SerializeField]
private bool useWaves;
[WEnumToggleButtons]
[SerializeField]
private SpawnPattern pattern;
[WValueDropDown(0.5f, 1f, 2f)]
[SerializeField]
private float spawnRate;
[SerializeField]
private SerializableDictionary<string, int> enemyWeights =
new SerializableDictionary<string, int>();
[WButton("Spawn Wave")]
private void SpawnWave() { }
}
// Attributes
using WallstopStudios.UnityHelpers.Core.Attributes;
// Serializable collections
using WallstopStudios.UnityHelpers.Core.DataStructure.Adapters;
MonoBehaviour / ScriptableObjectnameof() - Unity Helpers uses nameof() for condition fields (type-safe)new SerializableDictionary<K,V>() etc.[WShowIf(..., inverse: true)] instead of [HideIf]WShowIfComparison enum instead of expression strings[WGroup] can auto-include subsequent fields with autoIncludeCount