Why You Should Avoid Public Variables in Unity

Using public variables in Unity can seem like a quick way to make values editable in the Inspector or accessible across scripts. After all, every YouTuber does it, what can go wrong? Actually, a lot can go very wrong. This approach often leads to poor coding practices and potential bugs as your project scales. You don’t need the headaches! The better solution? Combine private fields, [SerializeField], and use public properties as needed to keep your code clean, modular, and maintainable.

The Problems with Public Variables

  1. Breaks Encapsulation
    Public variables allow unrestricted access from other scripts. This means any script can modify your variable without your knowledge, which can lead to unexpected behaviors and bugs.
  2. Difficult to Debug
    When multiple scripts have access to a public variable, tracking down where it was changed can be a nightmare.
  3. No Validation
    Public variables don’t let you control how they’re modified. For instance, if you have a health variable, nothing stops another script from accidentally setting it to a negative value.
  4. Code Bloat
    When everything is public, your codebase becomes less modular and harder to maintain as your project grows.

The Better Approach: SerializeField with Public Properties

To address these issues, use [SerializeField] to expose private fields in the Unity Inspector and public properties to provide controlled access to the field when other scripts need to read or modify it.


Examples

1. Using [SerializeField] to Expose Private Fields

using UnityEngine;

public class Player : MonoBehaviour
{
    [SerializeField]
    private int health = 100; // Exposed in Inspector, but private to other scripts

    [SerializeField]
    private float movementSpeed = 5f;  // This cab also be edited in the Inspector

    void Update()
    {
        // Example of movement using movementSpeed
        transform.Translate(Vector3.right * movementSpeed * Time.deltaTime);
    }
}

This keeps your variables safe from being directly modified by other scripts while still allowing designers or developers to tweak them in the Unity Inspector. Try it out!


2. Using Public Properties for Read-Only Access

Sometimes, other parts of your code need to read a value but shouldn’t be allowed to modify it. Use a public property to achieve this.

using UnityEngine;

public class Player : MonoBehaviour
{
    [SerializeField]
    private int health = 100;

    // Public property for read-only access to health
    public int Health
    {
        get { return health; }
    }

    private void Update()
    {
        Debug.Log("Current Health: " + Health); // Other scripts can read but not modify
    }
}

With this setup:

  • The health field is editable in the Inspector.
  • Other scripts can read Health but cannot change its value directly.

3. Using Public Properties for Controlled Access

If you need to allow modification but with validation or custom behavior, use a public property with a getter and a setter.

using UnityEngine;

public class Player : MonoBehaviour
{
    [SerializeField]
    private int health = 100;

    // Public property with validation
    public int Health
    {
        get { return health; }
        set
        {
            // Ensure health never goes below 0
            health = Mathf.Max(0, value);

            // Optionally, trigger an event when health changes
            Debug.Log("Health updated to: " + health);
        }
    }

    private void Update()
    {
        // Simulate taking damage
        if (Input.GetKeyDown(KeyCode.Space))
        {
            Health -= 10; // Using the property ensures validation is applied
        }
    }
}

Benefits of this approach:

  • Prevents invalid values (e.g., negative health).
  • Adds flexibility to trigger additional logic (e.g., updating UI or triggering events) when the variable changes.

Why This Approach Works

AspectPublic Variable[SerializeField] + Property
EncapsulationNo protection, easily misusedMaintains modularity and encapsulation
DebuggingHard to track changesControlled access simplifies debugging
ValidationNoneAllows validation and logic in setters
Inspector CustomizationExposedExposed, but safe from external scripts

When to Use Public Variables

In rare cases, public variables are acceptable, such as:

  • Constants: Use public const for values that never change.
   public const int MaxHealth = 100;
  • Shared Data: Use public variables cautiously for global/shared settings, ideally with a singleton or static class.

But that is a lot of extra code!

Maybe you don’t want two variables to represent the same piece of data, Well there is way to get the best of both worlds. The later versions of Unity (2021 and above I believe) support public property fields. I am not sure if this is the right term but you’ll see below.

Consider the following :

public class Player : MonoBehaviour
    {
       
            [field:SerializeField]
            public int Health { get; private set; } = 100;

            void Update()
            {
                // Simulate taking damage
                if (Input.GetKeyDown(KeyCode.Space))
                {
                    // You can write validation before this happens or call a custom function
                    Health -= 10; 
                }
            }

    }

This works for simple types (like int, string, enum and so on). In this example any code in the project can read the value but only the Player script can change it. Inside the Player script you can treat Health just like any other field and if you are following a YouTube tutorial where the author is making everything public you can try this first then take off the field portion if needed. Last, you can drop off the initialization if you don’t need it (I like keeping it as it will set a default in the Inspector).

Final Thoughts

Using [SerializeField] for private fields ensures that your code remains encapsulated and maintainable while still being designer-friendly. Public properties provide a controlled way to allow access to your data without exposing it to unintended modifications. These practices not only keep your code cleaner but also help prevent bugs and improve long-term maintainability.

Adopt these patterns, and you’ll thank yourself later—especially as your project scales! 🎮