UNPKG

com.wallstop-studios.unity-helpers

Version:

Treasure chest of Unity developer tools

696 lines (522 loc) 21.1 kB
# Inspector Validation Attributes **Protect your data with declarative validation and read-only presentation.** Unity Helpers provides validation attributes that help maintain data integrity and prevent accidental modifications. These attributes work seamlessly with the Unity inspector and can validate fields at runtime. --- ## Table of Contents - [WReadOnly](#wreadonly) - [WNotNull](#wnotnull) - [ValidateAssignment](#validateassignment) - [Best Practices](#best-practices) --- ## WReadOnly Displays a field in the inspector as read-only, preventing accidental modifications while keeping the value visible. ### Basic Usage ```csharp using UnityEngine; using WallstopStudios.UnityHelpers.Core.Attributes; public class GameManagerReadOnly : MonoBehaviour { [WReadOnly] public string sessionId = "abc-123-xyz"; [WReadOnly] public float elapsedTime = 0f; [WReadOnly] [SerializeField] private int internalScore = 100; } ``` **Behavior:** - Field appears grayed out in the inspector - Value is visible but cannot be edited through the inspector - Useful for displaying computed values, debug info, or auto-generated IDs - Works with any serializable field type > **Visual Reference** > > ![WReadOnly attribute showing grayed-out fields in the inspector](../../images/inspector/validation/wreadonly-basic.png) > > _Fields marked with [WReadOnly] appear grayed out and cannot be edited_ ### Common Use Cases ```csharp using UnityEngine; using WallstopStudios.UnityHelpers.Core.Attributes; public class EntityReadOnly : MonoBehaviour { // Auto-generated unique identifier [WReadOnly] public string entityId = System.Guid.NewGuid().ToString(); // Computed property exposed for debugging [WReadOnly] [SerializeField] private float _currentHealth; // Reference that should only be set via code [WReadOnly] public Transform cachedTarget; // Frame counter for debugging [WReadOnly] public int framesSinceLastUpdate; } ``` **Why Use WReadOnly:** - **Prevent accidents**: Stop designers from accidentally modifying auto-generated values - **Debug visibility**: Show internal state without allowing modification - **Documentation**: Make it clear which fields are managed by code vs. configured in the editor - **Data integrity**: Protect computed or cached values from manual overrides > **Visual Reference** > > ![WReadOnly showing various field types as read-only](../../images/inspector/validation/wreadonly-use-cases.png) > > _Multiple field types (string, float, Transform, int) displayed as read-only_ --- ## WNotNull Validates that a field is not null, providing both **visual inspector feedback** and **runtime validation**. When a field marked with `[WNotNull]` is null, the inspector displays a warning or error HelpBox. Additionally, calling `CheckForNulls()` on an object will throw an `ArgumentNullException` for any null `[WNotNull]` fields. ### Basic Usage ```csharp using UnityEngine; using WallstopStudios.UnityHelpers.Core.Attributes; public class PlayerControllerNotNull : MonoBehaviour { [WNotNull] public Rigidbody2D rb; [WNotNull] public SpriteRenderer spriteRenderer; [WNotNull] [SerializeField] private AudioSource audioSource; private void Awake() { // Validates all [WNotNull] fields are assigned // Throws ArgumentNullException if any are null in Editor ONLY this.CheckForNulls(); } } ``` **Behavior:** - **Inspector feedback**: Displays a HelpBox warning (yellow) or error (red) when the field is null - **Runtime validation**: Call `this.CheckForNulls()` to validate all `[WNotNull]` fields - Throws `ArgumentNullException` with the field name if any marked field is null - Works with both Unity `Object` types and plain C# objects - All validation runs **only in the Unity Editor** (stripped in builds for performance) > **Visual Reference** > > ![WNotNull fields in inspector with null references highlighted](../../images/inspector/validation/wnotnull-inspector.png) > > _Fields marked with [WNotNull] display a HelpBox in the inspector when null_ ### WNotNullMessageType Enum The `WNotNullMessageType` enum controls how null fields are displayed in the inspector: | Value | Description | | --------- | ------------------------------------------- | | `Warning` | Displays a yellow warning HelpBox (default) | | `Error` | Displays a red error HelpBox | ### Constructor Overloads The `[WNotNull]` attribute supports multiple constructor overloads for flexibility: #### Default Warning ```csharp // Default: warning message type with auto-generated message [WNotNull] public GameObject target; // Inspector shows: "target must be assigned" ``` #### Specify Message Type ```csharp // Error message type with auto-generated message [WNotNull(WNotNullMessageType.Error)] public AudioSource criticalAudioSource; // Inspector shows red error: "criticalAudioSource must be assigned" ``` #### Custom Message ```csharp // Warning with custom message [WNotNull("Player needs a target to attack")] public Transform attackTarget; // Inspector shows yellow warning: "Player needs a target to attack" ``` #### Full Customization ```csharp // Error with custom message [WNotNull(WNotNullMessageType.Error, "Audio source is required for sound effects")] public AudioSource audioSource; // Inspector shows red error: "Audio source is required for sound effects" ``` ### Inspector Display Examples ```csharp using UnityEngine; using WallstopStudios.UnityHelpers.Core.Attributes; public class EnemyAI : MonoBehaviour { // Yellow warning - nice to have [WNotNull] public ParticleSystem hitEffect; // Red error - critical reference [WNotNull(WNotNullMessageType.Error)] public Transform patrolPath; // Warning with helpful context [WNotNull("Assign the player tag or the enemy won't detect the player")] public string playerTag; // Error with specific instructions [WNotNull(WNotNullMessageType.Error, "Drag the NavMeshAgent component here - required for movement")] public UnityEngine.AI.NavMeshAgent agent; } ``` > **Visual Reference** > > ![WNotNull with different message types](../../images/inspector/validation/wnotnull-message-types.png) > > _Warning (yellow) and Error (red) HelpBoxes for null fields in the inspector_ ### Runtime Validation with CheckForNulls() The `CheckForNulls()` extension method validates all `[WNotNull]` fields at runtime: ```csharp using UnityEngine; using WallstopStudios.UnityHelpers.Core.Attributes; public class EnemySpawner : MonoBehaviour { [WNotNull] public GameObject enemyPrefab; [WNotNull] public Transform spawnPoint; [WNotNull(WNotNullMessageType.Error)] public EnemyManager enemyManager; private void Start() { // If any [WNotNull] field is null, this throws with the field name // Example: ArgumentNullException("enemyPrefab") this.CheckForNulls(); // Safe to use - we know these are assigned SpawnEnemy(); } private void SpawnEnemy() { GameObject enemy = Instantiate(enemyPrefab, spawnPoint.position, Quaternion.identity); enemyManager.RegisterEnemy(enemy); } } ``` ### Editor-Only Validation Both the inspector HelpBox display and the `CheckForNulls()` extension method are only active in the Unity Editor: ```csharp // The validation code only runs in UNITY_EDITOR // In builds, CheckForNulls() does nothing (stripped for performance) this.CheckForNulls(); ``` This means: - **Development**: Visual feedback in inspector + runtime null checking with detailed exception messages - **Production**: Zero runtime cost (all validation code is stripped) ### Combining with Other Attributes ```csharp public class UIManager : MonoBehaviour { // Required reference with error severity - validated at runtime [WNotNull(WNotNullMessageType.Error)] [SerializeField] private Canvas mainCanvas; // Optional reference - not validated [SerializeField] private AudioSource clickSound; // Read-only and required [WReadOnly] [WNotNull] public RectTransform cachedRect; // Group with validation and custom message [WGroup("UI Elements")] [WNotNull("Start button required for main menu")] public Button startButton; // In group [WNotNull(WNotNullMessageType.Error, "Quit button required for main menu")] [WGroupEnd("UI Elements")] // quitButton IS included, then closes public Button quitButton; // In group (last field) } ``` **Why Use WNotNull:** - **Visual feedback**: See missing references immediately in the inspector without running the game - **Severity control**: Use warnings for nice-to-have references, errors for critical ones - **Custom messages**: Provide helpful context about why a reference is needed - **Early failure**: Catch missing references at game start with `CheckForNulls()`, not when first used - **Clear errors**: Get the exact field name in the exception message - **Documentation**: Make required references explicit in code - **Zero runtime cost**: All validation stripped from builds --- ## ValidateAssignment Validates that a field is properly assigned, providing **visual inspector feedback** when validation fails. Unlike `[WNotNull]` which only checks for null references, `[ValidateAssignment]` validates that fields are "properly assigned" based on their type—including checking for empty strings, empty collections, and null references. ### What ValidateAssignment Validates | Field Type | Validation Rule | | ------------------------- | ------------------------ | | Unity `Object` references | Not null | | Strings | Not null or whitespace | | `IList` (arrays, List<T>) | Has at least one element | | `ICollection` | Has at least one element | | `IEnumerable` | Has at least one element | | Other types | Not null | ### Basic Usage ```csharp using System.Collections.Generic; using UnityEngine; using WallstopStudios.UnityHelpers.Core.Attributes; public class EnemySpawner : MonoBehaviour { [ValidateAssignment] public GameObject enemyPrefab; [ValidateAssignment] public string enemyName; [ValidateAssignment] public List<Transform> spawnPoints; private void Start() { // Logs warnings for any invalid [ValidateAssignment] fields this.ValidateAssignments(); } } ``` **Behavior:** - **Inspector feedback**: Displays a HelpBox warning (yellow) or error (red) when the field is invalid - **Runtime validation**: Call `this.ValidateAssignments()` to log warnings for invalid fields - **Programmatic checking**: Call `this.AreAnyAssignmentsInvalid()` to check if any fields are invalid - Works with Unity `Object` types, strings, collections, and any reference type - Inspector validation runs **only in the Unity Editor** (stripped in builds for performance) > **Visual Reference** > > ![ValidateAssignment fields in inspector with invalid values highlighted](../../images/inspector/validation/validateassignment-inspector.png) > > _Fields marked with [ValidateAssignment] display a HelpBox in the inspector when invalid_ ### ValidateAssignmentMessageType Enum The `ValidateAssignmentMessageType` enum controls how invalid fields are displayed in the inspector: | Value | Description | | --------- | ------------------------------------------- | | `Warning` | Displays a yellow warning HelpBox (default) | | `Error` | Displays a red error HelpBox | ### Constructor Overloads The `[ValidateAssignment]` attribute supports multiple constructor overloads for flexibility: #### Default Warning ```csharp // Default: warning message type with auto-generated message [ValidateAssignment] public GameObject target; // Inspector shows: "target must be assigned" ``` #### Specify Message Type ```csharp // Error message type with auto-generated message [ValidateAssignment(ValidateAssignmentMessageType.Error)] public AudioSource criticalAudioSource; // Inspector shows red error: "criticalAudioSource must be assigned" ``` #### Custom Message ```csharp // Warning with custom message [ValidateAssignment("Enemy name is required for UI display")] public string enemyName; // Inspector shows yellow warning: "Enemy name is required for UI display" ``` #### Full Customization ```csharp // Error with custom message [ValidateAssignment(ValidateAssignmentMessageType.Error, "Spawn points list cannot be empty")] public List<Transform> spawnPoints; // Inspector shows red error: "Spawn points list cannot be empty" ``` ### Inspector Display Examples ```csharp using UnityEngine; using WallstopStudios.UnityHelpers.Core.Attributes; using System.Collections.Generic; public class GameConfig : MonoBehaviour { // Yellow warning - validates not null [ValidateAssignment] public GameObject playerPrefab; // Red error - validates string not empty/whitespace [ValidateAssignment(ValidateAssignmentMessageType.Error)] public string gameTitle; // Warning with helpful context - validates list not empty [ValidateAssignment("Add at least one difficulty level")] public List<string> difficultyLevels; // Error with specific instructions - validates array not empty [ValidateAssignment(ValidateAssignmentMessageType.Error, "Spawn points are required - add Transform references")] public Transform[] spawnPoints; } ``` > **Visual Reference** > > ![ValidateAssignment with different field types and message types](../../images/inspector/validation/validateassignment-message-types.png) > > _Warning (yellow) and Error (red) HelpBoxes for various invalid field types_ ### Runtime Validation Methods Two extension methods are available for runtime validation of `[ValidateAssignment]` fields: #### ValidateAssignments() Logs warnings to the console for all invalid fields: ```csharp using UnityEngine; using WallstopStudios.UnityHelpers.Core.Attributes; public class LevelManager : MonoBehaviour { [ValidateAssignment] public GameObject[] enemyPrefabs; [ValidateAssignment] public string levelName; private void Start() { // Logs a warning for each invalid [ValidateAssignment] field this.ValidateAssignments(); } } ``` #### AreAnyAssignmentsInvalid() Returns `true` if any `[ValidateAssignment]` field is invalid: ```csharp using UnityEngine; using WallstopStudios.UnityHelpers.Core.Attributes; public class SpawnManager : MonoBehaviour { [ValidateAssignment] public GameObject spawnPrefab; [ValidateAssignment] public List<Transform> spawnLocations; private void Start() { if (this.AreAnyAssignmentsInvalid()) { Debug.LogError("SpawnManager has invalid assignments - spawning disabled"); enabled = false; return; } // Safe to proceed - all assignments are valid SpawnEnemies(); } } ``` ### ValidateAssignment vs WNotNull Both attributes validate fields and provide inspector feedback, but they serve different purposes: | Feature | ValidateAssignment | WNotNull | | -------------------------- | ------------------------------ | ------------------------------ | | **Null object check** | ✓ | ✓ | | **Empty string check** | ✓ | ✗ | | **Empty collection check** | ✓ | ✗ | | **Runtime behavior** | Logs warnings | Throws `ArgumentNullException` | | **Best for** | Comprehensive field validation | Strict null reference checking | **When to use which:** - Use `[ValidateAssignment]` when you need to validate that strings are non-empty or collections have elements - Use `[WNotNull]` when you want strict null checking with an exception thrown at runtime - Use `[ValidateAssignment]` when you want softer runtime validation (warnings instead of exceptions) - Use `[WNotNull]` for critical references where the game should fail fast if not assigned ```csharp public class PlayerSetup : MonoBehaviour { // Use WNotNull for critical references - throws if null [WNotNull(WNotNullMessageType.Error)] public Rigidbody2D rb; // Use ValidateAssignment for string validation [ValidateAssignment] public string playerName; // Use ValidateAssignment for collection validation [ValidateAssignment(ValidateAssignmentMessageType.Error, "Add at least one weapon")] public List<GameObject> startingWeapons; private void Awake() { // Throws ArgumentNullException if rb is null this.CheckForNulls(); // Logs warnings if playerName is empty or startingWeapons is empty this.ValidateAssignments(); } } ``` ### Combining with Other Attributes ```csharp using UnityEngine; using WallstopStudios.UnityHelpers.Core.Attributes; using System.Collections.Generic; public class UIManager : MonoBehaviour { // Group with validation [WGroup("Required UI Elements")] [ValidateAssignment(ValidateAssignmentMessageType.Error)] public Canvas mainCanvas; // In group [ValidateAssignment("Button text cannot be empty")] public string startButtonText; // In group (auto-included) [ValidateAssignment(ValidateAssignmentMessageType.Error, "Add menu items to display")] [WGroupEnd("Required UI Elements")] // menuItems IS included, then closes public List<GameObject> menuItems; // In group (last field) // Conditional validation [WShowIf(nameof(useCustomTheme))] [ValidateAssignment("Custom theme name required when using custom theme")] public string customThemeName; public bool useCustomTheme; } ``` **Why Use ValidateAssignment:** - **Comprehensive validation**: Validates strings, collections, and references—not just null checks - **Visual feedback**: See invalid fields immediately in the inspector - **Severity control**: Use warnings for nice-to-have fields, errors for critical ones - **Custom messages**: Provide helpful context about validation requirements - **Non-throwing validation**: Use `ValidateAssignments()` for warnings instead of exceptions - **Programmatic checking**: Use `AreAnyAssignmentsInvalid()` for conditional logic - **Zero runtime cost**: All validation stripped from builds --- ## Best Practices ### 1. Validate Early Call `CheckForNulls()` and `ValidateAssignments()` in `Awake()` or `Start()` to catch missing references immediately: ```csharp private void Awake() { // Throws for critical null references this.CheckForNulls(); // Logs warnings for empty strings, collections, etc. this.ValidateAssignments(); } ``` ### 2. Choose the Right Validation Attribute ```csharp // Use WNotNull for critical object references (throws on null) [WNotNull(WNotNullMessageType.Error)] public Rigidbody2D rb; // Use ValidateAssignment for strings (validates not empty/whitespace) [ValidateAssignment] public string playerName; // Use ValidateAssignment for collections (validates not empty) [ValidateAssignment(ValidateAssignmentMessageType.Error)] public List<Transform> waypoints; ``` ### 3. Use WReadOnly for Computed Values ```csharp [WReadOnly] public float Speed => rb.velocity.magnitude; ``` ### 4. Combine with Relational Components ```csharp // Auto-wired but protected from manual changes [WReadOnly] [SiblingComponent] public Collider2D col; ``` ### 5. Document Intent ```csharp // Required: Must be assigned in inspector [WNotNull] public AudioClip attackSound; // Required: Must not be empty [ValidateAssignment] public string characterName; // Optional: May be null public AudioClip hitSound; ``` ### 6. Use with ScriptableObjects ```csharp [CreateAssetMenu] public class GameConfig : ScriptableObject { [WNotNull] public GameObject playerPrefab; [WNotNull] public Material defaultMaterial; [ValidateAssignment("Game title cannot be empty")] public string gameTitle; [ValidateAssignment(ValidateAssignmentMessageType.Error)] public List<string> supportedLanguages; private void OnEnable() { this.CheckForNulls(); this.ValidateAssignments(); } } ``` --- ## See Also - **[Inspector Grouping Attributes](./inspector-grouping-attributes.md)** - Organize related fields - **[Inspector Conditional Display](./inspector-conditional-display.md)** - Show/hide fields conditionally - **[Relational Components](../relational-components/relational-components.md)** - Auto-wire component references