Core Theory

The Architecture Model

A layered system with one absolute rule: dependencies always point inward.

The Core Idea

Clean Game Architecture is a layered system with one absolute rule: dependencies always point inward. The inner layers know nothing about the outer layers. The outer layers depend on the inner layers - never the reverse.

This is not a new concept. Robert C. Martin’s Clean Architecture has articulated this principle for decades. What’s different here is the adaptation to the specific reality of game development - where engines, real-time loops, UI frameworks, and network layers create unique pressures to couple everything together.

The Four Layers

Visualize four concentric rings. From inside out:

The four concentric layers of Clean Game Architecture

Core - The Heart of Your Game

The Core layer contains the domain: the entities, the rules, the state that makes your game your game. A player’s inventory. A combat formula. The rules of friendship. The state machine for a quest.

What belongs here:

What does NOT belong here:

The Core is the most stable, most testable, most valuable part of your codebase. If you deleted every other layer, the Core would still compile. It would still be testable. It would still describe your game. For a deeper exploration of how to identify what belongs in the Core - including a useful thought experiment - see Finding Your Core.

Use Cases - The Feature’s API and Orchestration

The Use Cases layer serves two roles: it defines the public interface of a feature, and it contains the implementations that orchestrate business logic.

Use case interfaces are the feature’s public API - the only thing other features see. They describe what a feature can do: IClaimReward, ICheckQuestState, IAddFriend. These are resolved by other features via dependency injection.

Use case implementations contain the orchestration logic: “when the player claims a reward, validate eligibility, update inventory, notify analytics, trigger UI.” They coordinate the Core with the outer world, executing multi-step flows that may span multiple domain operations.

What also belongs here:

Key insight: The Use Cases layer defines what it needs from Controllers (via interfaces), but the Controllers implement those interfaces. The dependency points inward: Controllers depend on Use Case-defined contracts, not the other way around.

Think of it this way: if another feature needs to interact with your system, it depends on a use case interface. If a controller needs to drive a flow, it calls a use case implementation. Both live in this layer.

Controllers - The Bridge

The Controllers layer connects Use Cases to the outside world. Controllers react to user input, subscribe to domain events, and drive views - translating between the language of the domain and the language of the UI.

What belongs here:

Controllers know about Use Cases and Core (they depend inward), and they know about their Views (they depend outward to drive the UI). But they contain no engine-specific code themselves - they work through view interfaces, keeping presentation logic testable without the engine.

Services & Views - The Outside World

The outermost layer contains everything that touches external systems: the game engine, the UI framework, the network, the database, the file system.

What belongs here:

This layer depends on everything inside it but nothing inside depends on it. The Core doesn’t know it exists. The Use Cases layer talks to it only through interfaces defined in Use Cases.

The Dependency Rule

The single most important principle:

Source code dependencies must point inward only.

An outer layer may reference, import, or depend on types from an inner layer. An inner layer must never reference types from an outer layer.

Allowed and forbidden dependency directions between the four layers

This means:

How Inner Layers Talk to Outer Layers

If the Core can’t reference Services, how does game state get saved? How does the UI get updated?

Two mechanisms:

1. Dependency Inversion (Ports)

The Core defines interfaces (ports) for what it needs - e.g., “I need something that can save player data.” The actual implementation lives in Services. At runtime, dependency injection wires the concrete implementation to the interface. The Core only knows the interface.

Core defines:     IPersistence { void Save(PlayerData data); }
Services provides: LocalFilePersistence : IPersistence { ... }
                   CloudPersistence : IPersistence { ... }

The Core calls persistence.Save(data) without any knowledge of whether it’s writing to a local file, a cloud server, or an in-memory test double.

2. Events

The Core emits events when state changes. Outer layers subscribe to those events and react. The Core doesn’t know who’s listening or what they do with the information.

Core emits:              OnInventoryChanged
Use Cases subscribe:     Updates quest progress tracking
Controllers subscribe:   Refreshes the inventory UI
Services subscribe:      Triggers an analytics event

The Core simply announces that something happened. Everything else is someone else’s problem.

Why This Matters for Games

The Engine Becomes a Plugin

In a traditional game project, the engine is at the center. Everything depends on it. Your game logic is entangled with MonoBehaviours, Actors, or Nodes. Changing anything means fighting the engine.

With Clean Game Architecture, the engine lives in the outermost layer. Your game logic has zero engine dependencies. The engine is just one adapter among many - a way to render things on screen and handle input.

This doesn’t mean you’ll switch engines. But the discipline of keeping the engine at the periphery forces clean design decisions throughout the entire codebase. For a deeper exploration of how the engine relates to game logic - including a useful analogy to web browsers and the DOM - see The Game Object Model.

Features Become Independent

Each feature is a self-contained slice through the layers. The inventory system has its own Core, its own Use Cases, its own Controllers, its own Views. Features interact through Use Case interfaces, never by reaching into each other’s internals.

Feature A can depend on Feature B’s Use Cases - its public API. It never touches Feature B’s Controllers, Services or Views directly. For a complete walkthrough of how this looks in practice - folder structure, code, and runtime flow - see Vertical Slice Example.

Testing Becomes Trivial

Because the Core has zero external dependencies, you can test game rules in a plain test harness without launching the engine. Because Use Cases talk to Controllers through interfaces, you can substitute test doubles. Because features interact through Use Case interfaces, you can test features in isolation.

This is not a minor benefit. In most game projects, testing means “launch the game and click around.” With Clean Game Architecture, you can verify complex game logic in milliseconds.

Applying This in Practice

The theory is straightforward. The challenge is discipline - maintaining the dependency rule under the constant pressure of deadlines, prototyping, and “I’ll fix it later.”

The following topics explore the practical toolkit for making this work:

To understand what happens without this discipline, see Difficulties of Game Architecture.

If you’re wondering whether this applies to your specific type of game - especially if your gameplay relies heavily on physics, real-time input, or engine-driven mechanics - see Finding Your Core for a genre-by-genre breakdown.

If you’re working with an existing codebase and wondering how to introduce this architecture incrementally, see Transition from Legacy for the migration strategy - both technical and organizational.

If you’re concerned about the performance cost of these layers - allocations, indirection, GC pressure - see The Performance Dilemma for an honest accounting of what this architecture costs at runtime and how Data-Oriented Design fits cleanly behind architectural boundaries.