Welcome to another blog post about a couple of little keywords that decide who gets to touch your variables: public, private, and [SerializeField]. If you've ever made a variable public just so you could tweak it in the Inspector, and then felt a vague sense of guilt about it, you're asking the classic public vs private C# question, and this one's for you.
Let's dive in!
What Are Access Modifiers in C#?
Access modifiers are keywords that control which other pieces of code are allowed to see or change a field or method. Think of them as a bouncer for your variables. Some variables get a wide-open door, some only let their own class in, and some sit behind a velvet rope.
The reason access modifiers in C# matter in a game project is simple: the more scripts that can change a value, the harder it is to figure out which one broke it. If five different scripts can set the player's health, good luck finding the one that set it to -9999 at 2 AM.
your variable → access modifier → decides who is allowed to touch it
Public: Everyone Gets In
A public field can be read and changed by any script in your project. Unity also shows public fields in the Inspector, which is why beginners reach for it so often.
public class Player : MonoBehaviour
{
public int health = 100;
}
// Any other script can do this:
player.health = 0;
That's convenient, and also exactly the problem. Nothing stops an enemy script, a UI script, and a pickup script from all changing health in their own special ways. When a bug shows up, any of them could be the culprit.
Private: Only This Class Gets In
A private field can only be used inside the class that declares it. Everyone else gets a compiler error. That's not an annoyance, that's the feature.
public class Player : MonoBehaviour
{
private int health = 100;
}
public class Enemy : MonoBehaviour
{
void Attack(Player player)
{
player.health = 0; // compiler error, health is private
}
}
Small detail worth knowing: if you don't write any access modifier on a field, C# treats it as private by default. So int health = 100; and private int health = 100; mean the same thing. Writing it out anyway makes your intent obvious, which future you will appreciate.
SerializeField Unity: Private in Code, Visible in the Inspector
Here's the part most beginners miss. You don't have to choose between "safe" and "visible in the Inspector." The [SerializeField] attribute tells Unity to show and save a field in the Inspector even though it's private.
public class Player : MonoBehaviour
{
[SerializeField] private int health = 100;
[SerializeField] private float moveSpeed = 5f;
}
Now you can tweak health and moveSpeed in the Inspector all day long, while other scripts still can't touch them. This is the pattern most experienced SerializeField Unity developers default to, and it's a good habit to pick up early.
Under the hood, "serialized" just means Unity can convert the field's value into a saveable form. That's how your Inspector tweaks survive pressing Play, saving the scene, or reopening the project.
Letting Other Scripts Read a Value Safely
Sometimes another script genuinely needs to know the player's health, a UI bar for example, but it shouldn't be able to change it. A read-only property solves that neatly.
public class Player : MonoBehaviour
{
[SerializeField] private int health = 100;
public int Health => health; // others can read it, not set it
public void TakeDamage(int amount)
{
health -= amount;
}
}
Other scripts can read player.Health and call player.TakeDamage(10), but they can't just write player.Health = 0. All changes go through one controlled doorway, which means all your health bugs live in one place.
Public but Hidden: HideInInspector
Occasionally you need a field to be public so other scripts can use it, but you don't want it cluttering the Inspector. That's what [HideInInspector] is for.
public class Player : MonoBehaviour
{
[HideInInspector] public int currentCombo;
}
It's still public, still accessible from anywhere, just not shown. Note that Unity still serializes it behind the scenes. If you want it neither shown nor saved, add [System.NonSerialized] instead.
The Less Common Access Modifiers in C#
Two more are worth knowing exist, even if you won't use them every day:
protected- accessible inside the class and any class that inherits from it, but nobody else. Great for base classes likeEnemywith child classes likeBoss.internal- accessible anywhere within the same assembly (roughly, the same compiled chunk of your project). You'll mostly meet this when working with assembly definitions or packages.
public class Enemy : MonoBehaviour
{
protected int baseDamage = 10;
}
public class Boss : Enemy
{
void Rage()
{
baseDamage *= 2; // works, Boss inherits from Enemy
}
}
Public vs Private C#: Which Should You Actually Use?
Here's the no-fluff version, the gut check you can run through in your head:
- Not sure →
private, you can always loosen it later - Need to tweak it in the Inspector →
[SerializeField] private - Other scripts need to read it → a public read-only property
- Other scripts need to trigger behavior → a
publicmethod - Child classes need access →
protected - Has to be public but clutters the Inspector →
[HideInInspector]
The public vs private C# decision isn't about being strict for the sake of it, it's about asking "who actually needs to touch this?" Start with the smallest answer, and widen access only when something genuinely needs it.
Wrapping Up
Making everything public works fine right up until your project gets big enough that you can't tell who changed what. Defaulting to private, adding [SerializeField] when you want Inspector access (the everyday SerializeField Unity workflow), and exposing only what other scripts truly need keeps your code easier to debug and a lot harder to break by accident. It's one of the cheapest habits you can build, and it pays off on every project after this one.
~happy Coding!


No comments:
Post a Comment