Featured Articles:

Monday, September 14, 2026

The 4 OOP Principles Every Unity Developer Should Know And Actually Use

You've probably heard of Object Oriented Programming, but what does it mean exactly? One may think it's pretty obvious. Well, it's not. OOP is one of those terms that gets thrown around in job listings and Reddit threads until it stops meaning anything.

Here's the thing though: Unity is built on OOP. Every MonoBehaviour you've ever written is a class. Every GameObject you drag components onto is basically a walking demonstration of composition. So whether you've been "using OOP" on purpose or by accident, you've already got your hands dirty.

The 4 principles; encapsulation, inheritance, polymorphism, and abstraction, all sound like you need a degree in computer programming - but after reading this article, you'll be surprised how simple it really is. They're the difference between a codebase you can expand on for years and one that collapses the moment you add a third enemy type. Let's go through them one at a time, Unity-style.

 

1. Encapsulation: Stop Letting Everything Touch Everything

Encapsulation is the idea that an object should manage its own data, and other objects shouldn't be able to poke at it directly. Think of it like a vending machine: you put in money and press a button, you don't reach in and rearrange the snacks yourself.

In Unity terms, this is the difference between:

public class PlayerHealth : MonoBehaviour
{
    public int currentHealth = 100;
}

and:

public class PlayerHealth : MonoBehaviour
{
    [SerializeField] private int currentHealth = 100;

    public int CurrentHealth => currentHealth;

    public void TakeDamage(int amount)
    {
        currentHealth = Mathf.Max(0, currentHealth - amount);
    }
}

The first version lets literally any script in your project set currentHealth = -9999 for fun. The second version forces everything to go through TakeDamage(), which means you control exactly how health can change. Want to add damage resistance later? One method to edit, not forty scripts to hunt down.

The shape: Outside script → TakeDamage() → controlled change to currentHealth

Not: Outside script → currentHealth (directly, chaos, regret)

Encapsulation is basically Unity's way of saying "private fields aren't paranoia, they're professionalism."

 

2. Inheritance: Don't Repeat Yourself, Extend Yourself

Inheritance lets a class borrow behavior from a parent class instead of rewriting it from scratch. If you've ever made a base Enemy class and then a Zombie or Skeleton class that inherits from it, congratulations, you've done inheritance.

public class Enemy : MonoBehaviour
{
    public int health = 50;

    public virtual void Attack()
    {
        Debug.Log("Enemy attacks!");
    }
}

public class Zombie : Enemy
{
    public override void Attack()
    {
        Debug.Log("Zombie lunges and bites!");
    }
}

Zombie gets health for free and only overrides what makes it unique; its attack. That's the whole point. You're not copy-pasting an Attack() method into every enemy script and hoping you remember to update all of them when you tweak the combat system (you won't remember, nobody does).

The shape: Enemy (base) → Zombie, Skeleton, Goblin (all inherit shared stuff, override unique stuff)

The catch? Unity devs learn this one the hard way usually is that deep inheritance chains (Enemy → GroundEnemy → ArmoredGroundEnemy → ArmoredGroundBossEnemy) become a nightmare to untangle. Inheritance is a tool, not a lifestyle. Use it for genuine "is-a" relationships, not just to avoid retyping a variable.

 

3. Polymorphism: One Call, Many Behaviors

Polymorphism is what makes inheritance actually useful instead of just a fancy filing system. It means you can treat different objects the same way and let each one respond in its own way.

Say you've got a list of enemies, all different types, and you want them to attack:

List<Enemy> enemies = new List<Enemy>();

foreach (Enemy enemy in enemies)
{
    enemy.Attack();
}

You're not checking "is this a zombie, is this a skeleton, is this a goblin" with a pile of if-statements. You just call Attack(), and each enemy handles it their own way because of that virtual/override setup from before. The zombie bites, the skeleton throws bones, the goblin steals your wallet and runs. Same method call, different results.

The shape: enemy.Attack() → Zombie? bites. Skeleton? throws bones. Goblin? steals wallet.

This is where OOP starts paying off in a real way, you can add a whole new enemy type later without touching the code that loops through and calls Attack(). That loop doesn't even know new enemy types exist, and it doesn't need to.

 

4. Abstraction: Hide The Mess, Show The Handle

Abstraction means exposing only what's necessary and hiding the implementation details underneath. You already do this every time you call transform.position — you have no idea (and don't need to know) what Unity's internal engine is doing under the hood to make that work.

You can build this into your own code with abstract classes:

public abstract class Ability : ScriptableObject
{
    public abstract void Activate(GameObject user);
}

public class FireballAbility : Ability
{
    public override void Activate(GameObject user)
    {
        // spawn fireball, apply damage, screen shake, whatever
    }
}

Anything using Ability only needs to know one thing: call Activate(). It doesn't need to know whether that means launching a fireball, healing an ally, or summoning a rubber duck army. The complexity is tucked away inside each specific ability.

The shape: Ability.Activate() → hides fireball logic, heal logic, summon logic behind one clean call

Abstraction and encapsulation get mixed up a lot, and honestly, fair — they're cousins. Encapsulation is about protecting data. Abstraction is about simplifying what other code has to think about. Encapsulation hides the "how it's stored," abstraction hides the "how it works."


Putting It All Together

Here's what it looks like when all four principles are working together in a small combat system:

  • Encapsulation protects each enemy's health from being edited carelessly
  • Inheritance lets Zombie, Skeleton, and Goblin share a common Enemy base
  • Polymorphism lets you loop through a List<Enemy> and call Attack() without caring which subtype you're dealing with
  • Abstraction lets abilities, attacks, or AI behaviors expose a single clean method while hiding the messy details behind it

None of these principles exist in a vacuum, which is honestly the most annoying and most useful thing about OOP. You'll rarely use just one. A well-structured Unity project usually has all four quietly holding hands in the background.


Should You Refactor Your Whole Project Right Now?

No. Please don't. If your prototype works and nobody else has to read the code, ship it. OOP principles matter most when a project is growing. Multiple enemy types, multiple abilities, a team of more than one person touching the same scripts. If you're a solo dev making a game jam project in 48 hours, public int health is not a crime.

But if you're building something that needs to scale, more content, more systems, more collaborators, then these four principles are what keep that growth from turning into spaghetti. Learn them, use them where they make sense, and don't force them where they don't (looking at you, five-layer inheritance chain for a game with three enemy types).


~happy Coding!


No comments:

Post a Comment