Editing Unity script variables in the Inspector – The case for Encapsulation & [SerializeField]
If you’ve read many Unity tutorials, you may already know that it’s very easy to edit your script fields in the Unity inspector.
Most Unity tutorials (including on the official page) tell you that if you have a MonoBehaviour attached to a GameObject, any public field can be edited.
While that does technically work, I want to explain why it’s not the best way to setup your scripts, and offer an alternative that I think will help you in the future.
In this article, you’ll learn how to use proper Encapsulation while still taking full advantage of the Unity Inspector.
Take a look at this “Car.cs” script.
using UnityEngine; public class Car : MonoBehaviour { public Tire FrontTires; public Tire RearTires; public Tire FrontRightTire; public Tire FrontLeftTire; public Tire RearRightTire; public Tire RearLeftTire; private void Start() { // Instantiate Tires FrontRightTire = Instantiate(FrontTires); FrontLeftTire = Instantiate(FrontTires); RearRightTire = Instantiate(RearTires); RearLeftTire = Instantiate(RearTires); } }
If you look at the Start method, you can tell that the fields “FrontTires” & “RearTires” are referring to prefabs that will be be used to instantiate the 4 tires of the car.
Once we’ve assigned some Tire prefabs, it looks like this in the Inspector.
In play mode, the Start method will instantiate the 4 actual tires on our car and it’ll look like this.
Problem #1 – Confusion
The first thing you might realize is that there could be some confusion about which fields to assign the prefab to.
You’ve just seen the code, or in your own projects, perhaps you’ve just written it, and it may seem like a non-issue.
But if your project ever grows, it’s likely others will need to figure out the difference, and to do so, they’ll need to look at the code too.
If your project lasts more than a few days/weeks, you also may forget and have to look back through the code.
Now you could solve this with special naming. I’ve seen plenty projects where the “Prefab” fields had a prefix or suffix like “Front Tires Prefab”.
That can also work, but then you still have 4 extra fields in there that you have to read every time. And remember, this is a simple example, your real classes could have dozens of these fields.
Fix #1 – Let’s Use Properties for anything public
To resolve this, let’s change the entries we don’t want to be editable into Properties.
Now let’s change the “Car.cs” script to match this.Microsoft recommends you make your fields all private and use properties for anything that is public. There are plenty of benefits not described in this article, so feel free to read in detail from Microsofts article Fields(C# Programming Guide)
using UnityEngine; public class Car : MonoBehaviour { public Tire FrontTires; public Tire RearTires; public Tire FrontRightTire { get; set; } public Tire FrontLeftTire { get; set; } public Tire RearRightTire { get; set; } public Tire RearLeftTire { get; set; } private void Start() { // Instantiate Tires FrontRightTire = Instantiate(FrontTires); FrontLeftTire = Instantiate(FrontTires); RearRightTire = Instantiate(RearTires); RearLeftTire = Instantiate(RearTires); } }
Here’s what it looks like in the Inspector
With that change, you may be thinking we’ve resolved the issue and everything is good now.
While it’s true that confusion in the editor is all cleared up, we still have one more problem to address.
That problem is lack of Encapsulation.
Problem #2 – No Encapsulation
“In general, encapsulation is one of the four fundamentals of OOP (object-oriented programming). Encapsulation refers to the bundling of data with the methods that operate on that data.”
There are countless articles and books available describing the benefits of encapsulation.
The key thing to know is that properly encapsulated classes only expose what’s needed to make them operate properly.
That means we don’t expose every property, field, or method as public.
Instead, we only expose the specific ones we want to be accessed by other classes, and we try to keep them to the bare minimum required.
Why?
We do this so that our classes/objects are easy to interact with. We want to minimize confusion and eliminate the ability to use the classes in an improper way.
You may be wondering why you should care if things are public. Afterall, public things are easy to get to, and you know what you want to get to and will ignore the rest.
But remember, current you will not be the only one working on your classes.
If your project lasts beyond a weekend, you need to think about:
- other people – make it hard for them to misuse your classes.
- and just as important, there’s future you.
Unless you have a perfect memory, good coding practices will help you in the future when you’re interacting with classes you wrote weeks or months ago.
Problem #2 – The Example
Let’s look at this “Wall” script now to get an idea of why proper encapsulation is so important.
using UnityEngine; public class Wall : MonoBehaviour { public void Update() { if (Input.GetButtonDown("Fire1")) DamageCar(FindObjectOfType<Car>()); } public void DamageCar(Car car) { car.FrontTires.Tread -= 1; car.RearTires.Tread -= 1; } }
The “DamageCar” method is supposed to damage all of the wheels on the car by reducing their Tread value by 1.
Do you see what’s wrong here?
If we look back to the “Car.cs” script, “FrontTires” & “RearTires” are actually the prefabs, not the instantiated tires the car should be using.
In this case, if we execute the method, we’re not only failing to properly damage our tires, we’re actually modifying the prefab values.
This is an easy mistake to make, because our prefab fields that we we shouldn’t be interacting with aren’t properly encapsulated.
Problem #2 – How do we fix it?
If we make the “FrontTires” & “RearTires” private, we won’t be able to edit them in the inspector… and we want to edit them in the inspector.
Luckily, Unity developers knew this would be a need and gave us the ability to flag our private fields as editable in the inspector.
[SerializeField]
Adding the [SerializeField] attribute before private fields makes them appear in the Inspector the same way a public field does, but allows us to keep the fields properly encapsulated.
Take a look at the updated car script
using UnityEngine; public class Car : MonoBehaviour { [SerializeField] private Tire _frontTires; [SerializeField] private Tire _rearTires; public Tire FrontRightTire { get; set; } public Tire FrontLeftTire { get; set; } public Tire RearRightTire { get; set; } public Tire RearLeftTire { get; set; } private void Start() { // Instantiate Tires FrontRightTire = Instantiate(_frontTires); FrontLeftTire = Instantiate(_frontTires); RearRightTire = Instantiate(_rearTires); RearLeftTire = Instantiate(_rearTires); } }
Here you see we no-longer expose the “FrontTires” and “RearTires” fields outside of our class (by marking them private).
In the inspector, we still see them available to be assigned to.
Now our problems are solved and our class is properly encapsulated!
You may also notice that the casing on them has been changed. While this is not required to properly encapsulate your objects, it is very common practice in the C# community to denote private fields with camel case prefixed by an underscore. If you don’t like the underscore, consider at least using camel casing for your private fields and reserve pascal casing for public properties.
Video Version
Project Download
Want the source for this project to try it out yourself? Here it is: https://unity3dcollege.blob.core.windows.net/site/Downloads/Encapsulation%20SerializeField.zip