Summary
Inheritance allows a class (the child or derived class) to pick up all the data and behaviour of another class (the parent or base class), then extend or modify specific parts. Every Unity script already uses inheritance: public class PlayerController : MonoBehaviour means PlayerController is a child class of MonoBehaviour, gaining Start, Update, transform, gameObject, and all other MonoBehaviour features automatically. Understanding inheritance is essential for building reusable, extensible game code.
Key ideas
Extending a class:
Use : followed by the parent class name to declare inheritance.
public class Animal
{
protected string name;
public Animal(string name)
{
this.name = name;
}
public void Breathe()
{
Debug.Log($"{name} breathes.");
}
}
public class Dog : Animal
{
public Dog(string name) : base(name) { } // calls Animal's constructor
public void Bark()
{
Debug.Log($"{name} barks!");
}
}Dog inherits Breathe() from Animal without rewriting it. It adds Bark() on top.
virtual and override:
A method marked virtual in the parent can be replaced in a child using override. This is the mechanism for polymorphism — different types responding differently to the same method call.
public class Enemy : MonoBehaviour
{
protected int health = 100;
public virtual void TakeDamage(int amount)
{
health -= amount;
if (health <= 0) Die();
}
protected virtual void Die()
{
Destroy(gameObject);
}
}
public class BossEnemy : Enemy
{
public override void TakeDamage(int amount)
{
// Boss takes half damage
base.TakeDamage(amount / 2);
}
protected override void Die()
{
// Play special death sequence before destroying
StartCoroutine(BossDeathSequence());
}
}virtualsays “this method can be replaced in a child class.”overridesays “use this version instead of the parent’s.”base.TakeDamage(...)calls the parent implementation — avoids duplicating code and ensures shared logic runs.
The base keyword:
base refers to the parent class. Two uses:
- Call parent method:
base.TakeDamage(amount)— invoke the parent’s version from inside an override. - Call parent constructor:
: base(name)— chain to the parent constructor during object creation.
The protected access modifier:
protected members are visible within the class and within any child class, but not from the outside. Use it for data that child classes need direct access to but the outside world should not touch.
protected int health; // accessible in Enemy and BossEnemy, not from other scriptsabstract methods and classes:
An abstract method has no body in the parent class — the child must provide one. A class containing an abstract method must itself be declared abstract, and cannot be instantiated directly.
public abstract class Weapon : MonoBehaviour
{
public abstract void Fire(); // every weapon must implement this
public void Reload() // shared implementation, not abstract
{
Debug.Log("Reloading...");
}
}
public class Pistol : Weapon
{
public override void Fire()
{
Debug.Log("Bang!");
}
}
public class Shotgun : Weapon
{
public override void Fire()
{
Debug.Log("BOOM!");
}
}You can’t do new Weapon() — you can only create Pistol or Shotgun. But a variable of type Weapon can hold either, and calling .Fire() dispatches to the correct subclass.
sealed:
Marks a class or overriding method as non-extendable. sealed class Pistol prevents any other class from inheriting from Pistol. Rarely needed in game dev but useful for security-critical or finality-enforced types.
Constructor chaining:
When creating a child object, the parent’s constructor always runs first. Use : base(...) to specify which parent constructor to call and pass arguments.
public class BossEnemy : Enemy
{
public BossEnemy(string name) : base(name, 500) // Enemy(string, int)
{
// BossEnemy-specific setup here
}
}In practice (Unity)
Shared base class for all damageable things:
public abstract class Damageable : MonoBehaviour
{
[SerializeField] protected int maxHealth = 100;
protected int currentHealth;
protected virtual void Start()
{
currentHealth = maxHealth;
}
public abstract void TakeDamage(int amount);
protected abstract void Die();
public float HealthPercent => (float)currentHealth / maxHealth;
}
public class Player : Damageable
{
public override void TakeDamage(int amount)
{
currentHealth -= amount;
if (currentHealth <= 0) Die();
}
protected override void Die()
{
SceneManager.LoadScene("GameOver");
}
}
public class StandardEnemy : Damageable
{
public override void TakeDamage(int amount)
{
currentHealth -= amount;
if (currentHealth <= 0) Die();
}
protected override void Die()
{
Destroy(gameObject);
}
}A single method call — target.TakeDamage(10) — works on any Damageable, regardless of whether it is a Player or StandardEnemy. This is polymorphism in action.
Evidence
From Miles (2019, §4.9): “Inheritance lets a class pick up behaviours from the class which is its parent… ‘I can do these things because my parent can’.”
From Miles (2019, §4.9.3 Programmer’s Point): “Block Copy is Evil… A great programmer writes every piece of code once, and only once.” — the motivation for inheritance over copy-pasting.
From Miles (2019, §4.9.7): “An abstract class can be thought of as a kind of template. If you want to make an instance of a class based on an abstract parent you must provide implementations of all the abstract methods given in the parent.”
Gotchas
- Forgetting
virtualon the parent method means overriding won’t work correctly — the compiler will warn and suggest usingnew(method replacement) instead, which breaks polymorphism. - Deep inheritance hierarchies are brittle: changes at the top cascade down. Prefer composition via components over deep inheritance chains (Unity’s component model encourages this).
protecteddata in a base class means child classes can corrupt that data — if safety matters, keep dataprivateand expose it throughprotectedaccessor methods.- If a parent class constructor requires parameters, every child class constructor must explicitly call
base(...). Missing this causes a compile error.
Related
- csharp-oop-fundamentals — classes, encapsulation, constructors
- csharp-interfaces — alternative to inheritance for shared behaviour contracts
- csharp-properties — properties in class hierarchies
- monobehaviour-lifecycle — MonoBehaviour itself uses virtual methods (Start, Update, OnTriggerEnter2D, etc.)
- unity-object-communication — how scripts call methods on other objects