Most games start with mechanics and layer content on top.
This project did the opposite.

From the beginning, the goal was not to build features, but to build systems that survive content growth, engine constraints, and tooling changes.

That decision led to a strict rule:

Unity delivers UI and rendering.
Game logic and data live outside the engine.

UI-Data


Unity Is Not the Core

Unity is used for:

  • UI rendering (UI Toolkit)
  • input handling
  • presentation
  • lifecycle glue

Unity is not:

  • the source of truth for game logic
  • where narrative rules live
  • where content behavior is defined

All domain logic — conditions, actions, dialogue flow, state evaluation — is implemented as plain C#, independent of Unity APIs.

This separation is intentional.


Data + Pure C# as the Authority

At the core of the project:

  • data defines what exists
  • pure C# systems define how it behaves
  • Unity asks questions and renders answers

The game does not ask:

“Which MonoBehaviour should run?”

It asks:

“What does the data allow right now?”


What This Enables

1. Engine-Agnostic Logic

Because logic does not depend on Unity:

  • switching engines is possible, not theoretical
  • UI can be rewritten without rewriting systems
  • simulation code survives engine churn

Unity is treated as a frontend, not a foundation.


2. External Toolchains

Because data + logic are decoupled:

  • validators can run outside Unity
  • content editors can be built without the engine
  • CI can fail on broken content before runtime

Examples:

  • YAML schema validation in VS Code
  • headless content loaders
  • test runners without Unity Test Framework

3. Deterministic, Testable Systems

Pure C# systems mean:

  • no scene setup for tests
  • no hidden state in GameObjects
  • no editor-only assumptions

Example: condition evaluation can be tested like any other library.


4. Long-Term Content Scaling

As content grows:

  • systems stay fixed
  • schemas evolve carefully
  • data volume increases without logic explosion

This avoids the classic:

“We need to refactor everything because content doubled.”


5. Inspectability and Debuggability

If something breaks:

  • inspect the data
  • inspect the state
  • re-run the evaluation

No guessing which script ran in which order.


A Small Concrete Example

Condition Evaluation (Pure C#)

public static bool Evaluate(ConditionBlock when, GameState state)
{
    if (when == null)
        return true;

    if (when.All != null && when.All.Any(c => !c.Evaluate(state)))
        return false;

    if (when.Any != null && !when.Any.Any(c => c.Evaluate(state)))
        return false;

    return true;
}

No Unity types. No engine dependencies. Fully testable.

Data-Driven Action Definition (YAML)

- !Action
  id: pay-debt # pseudo-unigue identifier for the action
  label: Pay the bar tab # Label to show for the UI
  when: # Condition block
    all: # Evaluation clause
      - hasTag: owes_bar
      - hasCredits: 50
  effects: # Effect of the action to the state
    - removeTag: owes_bar
    - changeCredits: -50

The UI does not know why this action appears. It only knows whether it does.

foreach (var action in visibleActions)
{
    var button = new Button(() => Execute(action));
    button.text = action.Label;
    actionContainer.Add(button);
}

Unity renders - Systems decide.


This Also Enables Engine Switching

Engine changes are expensive — unless logic is isolated.

In this architecture, the engine is not the game.
It is a delivery mechanism.

All authoritative behavior lives outside the engine:

  • game state
  • condition evaluation
  • action resolution
  • dialogue flow
  • narrative rules

Because of this separation:

  • data remains unchanged
  • core systems remain unchanged
  • only the presentation layer needs to be rewritten

This does not make engine switching cheap.
It makes it possible without rewriting the game itself.

That distinction matters.

An engine swap becomes a controlled migration instead of a total rewrite:

  • Unity → another engine
  • desktop → mobile
  • real-time → turn-based presentation

The game logic does not care.

The cost moves from “rewrite everything” to “replace the shell”.

For a long-lived project, that alone justifies the architecture.