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
- 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. - Difficult to Debug
When multiple scripts have access to a public variable, tracking down where it was changed can be a nightmare. - 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. - 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
Aspect | Public Variable | [SerializeField] + Property |
---|---|---|
Encapsulation | No protection, easily misused | Maintains modularity and encapsulation |
Debugging | Hard to track changes | Controlled access simplifies debugging |
Validation | None | Allows validation and logic in setters |
Inspector Customization | Exposed | Exposed, 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! 🎮