Summary
Object-oriented programming (OOP) is the paradigm that underpins all C# and Unity scripting. A class is a blueprint describing what data an object holds and what it can do. An object is a specific instance of that blueprint, created at runtime with new. OOP’s central discipline is encapsulation — keeping data private and controlled, exposing only the behaviours that the outside world should be able to trigger. MonoBehaviour, the base class for every Unity script, is itself a class; when you write public class PlayerController : MonoBehaviour, you are creating a new class that extends an existing one.
Key ideas
Class and object:
// Class — the blueprint (defined once)
public class Enemy
{
private int health = 100;
public void TakeDamage(int amount)
{
health -= amount;
if (health < 0) health = 0;
}
public int GetHealth()
{
return health;
}
}
// Object — a runtime instance (created with new)
Enemy goblin = new Enemy();
Enemy troll = new Enemy(); // completely separate from goblingoblin and troll are independent instances. Each has its own health value. Changing one does not affect the other.
Encapsulation — private data, public behaviour:
The rule of thumb from Rob Miles’ Yellow Book: “If it is a data member, make it private. If it is a method member, make it public.” (§4.5.2)
This means the only way the outside world can interact with an Enemy is through its public methods. The outside world cannot set health directly to zero and cheat — it must go through TakeDamage, which enforces valid state transitions.
Access modifiers:
| Modifier | Who can access |
|---|---|
public | Anyone — other classes, other scripts, Unity Inspector |
private | Only code inside this class |
protected | This class and any class that inherits from it (see csharp-inheritance) |
In Unity, fields marked public appear in the Inspector. Prefer [SerializeField] private when you only need Inspector access (see csharp-variables-and-types).
Constructors:
A constructor is a special method that runs once, automatically, when an object is created. It has the same name as the class and no return type.
public class Enemy
{
private int health;
private string name;
// Constructor — runs when 'new Enemy(...)' is called
public Enemy(string enemyName, int startingHealth)
{
name = enemyName;
health = startingHealth;
}
}
// Creating objects via the constructor
Enemy goblin = new Enemy("Goblin", 50);
Enemy boss = new Enemy("Dragon Boss", 500);If you define no constructor, C# provides a default no-argument constructor automatically. The moment you define your own, the default disappears — add it back explicitly if you still need it.
Constructor overloading:
A class can have multiple constructors with different parameter lists. C# picks the right one based on what you pass to new.
public Enemy() { health = 100; name = "Unknown"; }
public Enemy(string name) { this.name = name; health = 100; }
public Enemy(string name, int health) { this.name = name; this.health = health; }The this keyword:
Inside any method or constructor, this refers to the current instance. It is mainly used when a parameter name shadows a field name.
public Enemy(string name, int health)
{
this.name = name; // 'this.name' = field; 'name' = parameter
this.health = health;
}Static members:
A static member belongs to the class, not to any individual instance. There is one copy regardless of how many objects exist. Static is commonly used for shared counters or utility methods.
public class Enemy
{
private static int totalSpawned = 0;
public Enemy()
{
totalSpawned++;
}
public static int GetTotalSpawned()
{
return totalSpawned;
}
}
// Accessed via the class name, not an instance
int count = Enemy.GetTotalSpawned();The Unity unity-gamemanager-pattern relies heavily on a static singleton instance for this reason.
In practice (Unity)
In a Unity MonoBehaviour, encapsulation looks like this:
public class PlayerHealth : MonoBehaviour
{
[SerializeField] private int maxHealth = 100;
private int currentHealth;
private void Start()
{
currentHealth = maxHealth;
}
public void TakeDamage(int amount)
{
currentHealth -= amount;
if (currentHealth <= 0)
{
currentHealth = 0;
Die();
}
}
public int GetHealth() => currentHealth;
private void Die()
{
// death logic — private, only callable from within this class
gameObject.SetActive(false);
}
}currentHealthis private — nothing outside this script can set it arbitrarily.TakeDamageis public — other scripts call this to apply damage.Dieis private — it should only be triggered by this class’s own logic.
Class vs struct
C# has both classes and structs. The key difference: classes are reference types (the variable holds a reference to the object in memory); structs are value types (the variable holds the data directly, copied on assignment).
Unity’s Vector2, Vector3, Color, and Rect are all structs. This means:
Vector3 a = new Vector3(1, 2, 3);
Vector3 b = a; // b is a COPY of a
b.x = 99; // a.x is still 1 — they are independentIf Vector3 were a class, changing b.x would change a.x too (same reference). For small, frequently-used data bundles, structs avoid heap allocation overhead.
For your own game types: default to class. Only reach for struct for small, value-like data (e.g., a coordinate pair, a stat bundle) where copying is the desired behaviour.
Evidence
From Miles (2019 §4.5.2): “If objects are going to be useful we have to have a way of protecting the data within them… The posh word for this is encapsulation. I want all the important data hidden inside my object so that I have complete control over what is done with it.”
From Miles (2019 §4.6.1): “A static member is a member of the class, not a member of an instance of the class.”
Open questions
- When should a Unity game object be modelled as a class with data vs. a pure component that delegates state to other components?
- How does C# record syntax (introduced post-2019) change the class-vs-struct tradeoff for immutable data?
Related
- csharp-variables-and-types — the types that live inside classes
- csharp-methods — the methods that classes expose
- csharp-inheritance — extending one class from another
- csharp-interfaces — defining shared behaviour without shared implementation
- csharp-properties — C# property syntax (
get/set) - csharp-enums — enumerated types for state tracking
- unity-gamemanager-pattern — static singleton pattern in Unity
- unity-object-communication — how Unity scripts call methods on each other