Code of the Day
IntermediateGame Architecture

Entity design

Understand why flat data plus logic works for small games, where deep inheritance breaks down, and how composition scales to complex behaviour.

Game DevIntermediate6 min read
By the end of this lesson you will be able to:
  • Distinguish data (position, health) from logic (update, draw)
  • Explain why flat attributes are fine for small games but composition scales better
  • Preview the entity-component pattern and when to reach for it

In the beginner labs every game object was a pygame.Rect and a handful of variables. That is the right approach when the game is small. As games grow, you need to represent players, enemies, projectiles, power-ups, and environmental objects — each with their own update logic, collision responses, and rendering rules. How you organise this code determines whether adding a new enemy type takes five minutes or five hours.

The flat class: good for small games

The simplest approach is one class per object type, with all data as instance attributes and all logic as methods:

class Player:
    def __init__(self, x, y):
        self.rect   = pygame.Rect(x, y, 40, 40)
        self.health = 3
        self.speed  = 4
        self.score  = 0

    def update(self, events, keys):
        if keys[pygame.K_LEFT]:  self.rect.x -= self.speed
        if keys[pygame.K_RIGHT]: self.rect.x += self.speed
        # ...

    def draw(self, screen):
        pygame.draw.rect(screen, (100, 210, 120), self.rect)

This is direct and readable. For a game with one player, three enemy types, and a few power-ups, it is the right choice. Premature architecture is a real cost: time spent designing abstractions is time not spent making the game better.

Where deep inheritance breaks

Suppose your game needs a FlyingEnemy — an enemy that moves, shoots, and can be above obstacles. You also need a ShootingEnemy — one that stays on the ground but fires projectiles. With inheritance you might write:

class Enemy(Entity): ...
class FlyingEnemy(Enemy): ...    # adds flight
class ShootingEnemy(Enemy): ...  # adds shooting
class FlyingShootingEnemy(???):  # inherits from both?

Python supports multiple inheritance, but it makes method resolution order surprising and tends to produce fragile code. Java and C# do not support it at all. The diamond problem — where two parent classes share a common ancestor — causes ambiguity that can only be resolved with explicit workarounds.

More practically: once you have ten entity types with overlapping capabilities, the inheritance tree becomes a map you have to consult before touching anything.

Composition: give entities components

The alternative is to give entities a collection of components — small objects that encapsulate one specific capability — instead of encoding every capability in the class hierarchy.

class Entity:
    def __init__(self, components):
        self.components = components

    def update(self, events):
        for c in self.components:
            c.update(self, events)

    def draw(self, screen):
        for c in self.components:
            c.draw(self, screen)

# Separate components:
class MovementComponent: ...
class HealthComponent:   ...
class ShootComponent:    ...
class RenderComponent:   ...

A FlyingShootingEnemy is just an Entity with [MovementComponent(aerial=True), ShootComponent(), RenderComponent()]. Adding "shooting" to any existing entity is adding a component, not changing a class.

This is the entity-component pattern. Its fully elaborated form — an Entity-Component-System (ECS) — also moves the update logic out of components and into systems that iterate over all entities with a matching component set. ECS is an advanced topic; the full-ECS lesson covers it later.

Do not rush to composition. If your game has six objects with simple behaviour, a flat class per type is faster to write and easier to read. Reach for composition when you notice yourself copying and pasting the same chunk of logic between two unrelated classes.

Where to go next

Next: event systems — decoupling the objects inside a scene from each other using a publish/subscribe event bus.

Finished reading? Mark it complete to track your progress.

On this page