com.wallstop-studios.unity-helpers
Version:
Treasure chest of Unity developer tools
558 lines (389 loc) • 11.9 kB
Markdown
# Odin Inspector to Unity Helpers Migration Guide
A practical guide for migrating from Odin Inspector to Unity Helpers. Examples are verified against the actual source code.
---
## Quick Reference Table
| 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>` |
---
## 1. Serializable Collections
### Dictionary
**Odin:**
```csharp
using Sirenix.OdinInspector;
using Sirenix.Serialization;
public class Example : SerializedMonoBehaviour
{
public Dictionary<string, int> scores;
}
```
**Unity Helpers:**
```csharp
using UnityEngine;
using WallstopStudios.UnityHelpers.Core.DataStructure.Adapters;
public class Example : MonoBehaviour
{
[SerializeField]
private SerializableDictionary<string, int> scores = new SerializableDictionary<string, int>();
}
```
**Key differences:**
- No special base class required (use standard `MonoBehaviour`)
- Must use `[SerializeField]` or `public`
- Initialize with `new` to avoid null references (good practice, Unity will initialize this like it does List<T> and arrays)
### HashSet
**Odin:**
```csharp
using Sirenix.OdinInspector;
using Sirenix.Serialization;
public class Example : SerializedMonoBehaviour
{
public HashSet<string> unlockedItems;
}
```
**Unity Helpers:**
```csharp
using UnityEngine;
using WallstopStudios.UnityHelpers.Core.DataStructure.Adapters;
public class Example : MonoBehaviour
{
[SerializeField]
private SerializableHashSet<string> unlockedItems = new SerializableHashSet<string>();
}
```
---
## 2. Inspector Buttons
**Odin:**
```csharp
[Button("Regenerate")]
private void RegenerateLevel() { }
[Button, ButtonGroup("Actions")]
private void Save() { }
```
**Unity Helpers:**
```csharp
[WButton("Regenerate")]
private void RegenerateLevel() { }
[WButton(groupName: "Actions")]
private void Save() { }
```
**Additional options:**
```csharp
// 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() { }
```
**Odin Inspector Support:**
WButton works with Odin's `SerializedMonoBehaviour` and `SerializedScriptableObject` without additional setup. Use `[WButton]` on your methods.
**Manual integration may be needed if:**
- You create a custom `OdinEditor` for a specific type
- See [Inspector Buttons - Custom Editors](../features/inspector/inspector-button.md#integration-with-custom-odin-editors) for details
---
## 3. Conditional Display
### Basic Boolean Condition
**Odin:**
```csharp
public bool showAdvanced;
[ShowIf("showAdvanced")]
public float advancedSetting;
```
**Unity Helpers:**
```csharp
public bool showAdvanced;
[WShowIf(nameof(showAdvanced))]
public float advancedSetting;
```
### Hide If (Inverse)
**Odin:**
```csharp
[HideIf("isDisabled")]
public float value;
```
**Unity Helpers:**
```csharp
[WShowIf(nameof(isDisabled), inverse: true)]
public float value;
```
### Enum Value Comparison
**Odin:**
```csharp
public AttackType attackType;
[ShowIf("attackType", AttackType.Ranged)]
public float range;
```
**Unity Helpers:**
```csharp
public AttackType attackType;
[WShowIf(nameof(attackType), AttackType.Ranged)]
public float range;
```
### Numeric Comparisons
**Odin:**
```csharp
[ShowIf("@level >= 5")]
public Ability ultimateAbility;
```
**Unity Helpers:**
```csharp
[WShowIf(nameof(level), WShowIfComparison.GreaterThanOrEqual, 5)]
public Ability ultimateAbility;
```
**Available comparisons:** `Equal`, `NotEqual`, `GreaterThan`, `GreaterThanOrEqual`, `LessThan`, `LessThanOrEqual`, `IsNull`, `IsNotNull`, `IsNullOrEmpty`, `IsNotNullOrEmpty`
---
## 4. Enum Toggle Buttons
**Odin:**
```csharp
[EnumToggleButtons]
public Direction direction;
[EnumToggleButtons]
public MovementFlags flags; // [Flags] enum
```
**Unity Helpers:**
```csharp
[WEnumToggleButtons]
public Direction direction;
[WEnumToggleButtons(showSelectAll: true, showSelectNone: true)]
public MovementFlags flags; // [Flags] enum
```
**Control buttons per row:**
```csharp
[WEnumToggleButtons(buttonsPerRow: 4)]
public DamageType damageTypes;
```
---
## 5. Value Dropdowns
### Integer Dropdown
**Odin:**
```csharp
[ValueDropdown("GetFrameRates")]
public int targetFrameRate;
private int[] GetFrameRates() => new[] { 30, 60, 120 };
```
**Unity Helpers:**
```csharp
// 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 };
```
### String Dropdown
**Odin:**
```csharp
[ValueDropdown("GetDifficulties")]
public string difficulty;
```
**Unity Helpers:**
```csharp
// Inline values
[StringInList("Easy", "Normal", "Hard")]
public string difficulty;
// Or with provider
[StringInList(nameof(GetDifficulties))]
public string difficulty;
```
### Generic Value Dropdown
**Unity Helpers:**
```csharp
// 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;
```
---
## 6. Grouping Fields
**Odin:**
```csharp
[BoxGroup("Movement")]
public float speed;
[BoxGroup("Movement")]
public float jumpHeight;
[FoldoutGroup("Advanced")]
public float acceleration;
```
**Unity Helpers:**
```csharp
// 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;
```
### Nested Groups
**Unity Helpers:**
```csharp
[WGroup("Character", displayName: "Character Settings")]
public string characterName;
[WGroup("Stats", parentGroup: "Character")]
public int health;
public int mana;
```
---
## 7. Inline Editors
**Odin:**
```csharp
[InlineEditor]
public EnemyConfig config;
[InlineEditor(InlineEditorModes.GUIOnly)]
public ItemData item;
```
**Unity Helpers:**
```csharp
[WInLineEditor]
public EnemyConfig config;
[WInLineEditor(WInLineEditorMode.FoldoutExpanded, inspectorHeight: 200f)]
public ItemData item;
```
**Available modes:** `AlwaysExpanded`, `FoldoutExpanded`, `FoldoutCollapsed`
---
## 8. Required/NotNull Validation
**Odin:**
```csharp
[Required]
public GameObject prefab;
[Required("Player reference is required!")]
public Transform player;
```
**Unity Helpers:**
```csharp
[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
}
```
### Collection Validation
For validating that collections aren't empty:
```csharp
[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();
}
```
---
## 9. Read-Only Fields
**Odin:**
```csharp
[ReadOnly]
public string generatedId;
```
**Unity Helpers:**
```csharp
[WReadOnly]
public string generatedId;
```
---
## 10. Complete Migration Example
**Before (Odin):**
```csharp
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):**
```csharp
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() { }
}
```
---
## Namespace Reference
```csharp
// Attributes
using WallstopStudios.UnityHelpers.Core.Attributes;
// Serializable collections
using WallstopStudios.UnityHelpers.Core.DataStructure.Adapters;
```
---
## Key Differences Summary
1. **No special base class** - Use standard `MonoBehaviour` / `ScriptableObject`
2. **Use `nameof()`** - Unity Helpers uses `nameof()` for condition fields (type-safe)
3. **Initialize collections** - Initialize `new SerializableDictionary<K,V>()` etc. to avoid null references
4. **[HideIf] becomes inverse** - Use `[WShowIf(..., inverse: true)]` instead of `[HideIf]`
5. **Numeric conditions** - Use `WShowIfComparison` enum instead of expression strings
6. **Groups auto-include** - `[WGroup]` can auto-include subsequent fields with `autoIncludeCount`
---
## See Also
- **[Inspector Overview](../features/inspector/inspector-overview.md)** - Complete inspector features guide
- **[Serialization Types](../features/serialization/serialization-types.md)** - All serializable types
- **[Inspector Buttons](../features/inspector/inspector-button.md)** - WButton detailed guide
- **[Inspector Conditional Display](../features/inspector/inspector-conditional-display.md)** - WShowIf detailed guide
- **[Inspector Selection Attributes](../features/inspector/inspector-selection-attributes.md)** - Dropdowns and toggles