Table of Contents

Architecture and Dependency Injection

Axolotl2D is a framework rather than an editor-driven engine. Your application owns startup, service registration, scenes, and components. The .NET Generic Host is the composition root, and Microsoft.Extensions.DependencyInjection supplies services throughout the framework.

Configure the host

Use UseSceneManagerGameHost<TGame>() for a scene-based game:

using Axolotl2D;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

var host = Host.CreateDefaultBuilder(args)
    .ConfigureServices(services =>
    {
        services.UseSceneManagerGameHost<MyGame>();
        services.AddScene<MainMenuScene>();
        services.AddScene<GameplayScene>();

        services.AddSingleton<GameSettings>();
        services.AddScoped<LevelSession>();
        services.AddTransient<EnemyBrain>();
    })
    .Build();

await host.RunAsync();

UseSceneManagerGameHost<TGame>() calls AddAxolotl2D() and registers the game and scene host. AddAxolotl2D() provides assets, audio, input actions, time, sprite and primitive rendering, UI and particle component dependencies, scoped shaders, physics, optional runtime inspection, and the GameObject factory. Pass enableDebugOverlay: true to the host extension during development.

The game host awaits Game.InitializeAsync before it starts the window and loads the default scene. Override that method for asset loading and pass its cancellation token to each loader.

UseSimpleGameHost<TGame>() remains available for applications that want to work directly with the Game events and do not need scenes.

Service lifetimes

Choose lifetimes according to ownership:

Lifetime Good fit Ends when
Singleton Configuration, save data, TimeService, InputActionSystem The application stops
Scoped Level state, InputActionMap, ShaderLibrary, PhysicsWorld The active scene changes
Transient Small stateless collaborators The consumer releases them

Axolotl2D registers scenes, action maps, shader libraries, physics worlds, and IGameObjectFactory as scoped services. Rendering, assets, audio, time, the input device system, the default camera, sprite batching, and text rendering are singleton services.

Constructor injection

Games, scenes, and components can request registered services through their constructors:

public sealed class GameplayScene(
    LevelSession session,
    AssetManager assets,
    Camera2D camera) : BaseScene
{
    public override void Load()
    {
        session.Begin();
        camera.Position = Vector2.Zero;

        var player = Instantiate("Player");
        player.AddComponent<PlayerController>();
    }
}

public sealed class PlayerController(
    GameObject gameObject,
    LevelSession session,
    ILogger<PlayerController> logger) : Component(gameObject)
{
    public override void Start() =>
        logger.LogInformation("Player entered level {Level}", session.LevelName);
}

GameObject.AddComponent<T>() uses ActivatorUtilities with the active scene provider. The current GameObject is supplied automatically, and the remaining constructor arguments come from DI. Components in the same scene therefore receive the same scoped LevelSession instance.

Ownership boundaries

The framework follows a clear ownership chain:

Generic Host
  Game and application-wide singleton services
    Scene DI scope
      Scene and scoped services
        GameObjects
          Components

Changing scenes disposes the old GameObjects and their components before disposing the old scene scope. This makes scene-scoped services safe to use from OnDestroy.

Avoid resolving services from the root provider inside gameplay code. Constructor injection keeps dependencies visible and ensures scoped services come from the correct scene.

Design patterns in the framework

Axolotl2D combines a small set of established patterns:

Pattern Where it appears Benefit
Composition GameObjects gain behavior from components Features stay reusable without deep inheritance trees
Dependency injection Host, scenes, custom services, and components Dependencies are explicit and replaceable
Lifetime scope One service scope per active scene Scene-local state has a clear lifetime and cleanup boundary
Factory IGameObjectFactory and Instantiate Runtime objects are created with the correct scene provider
Template method BaseScene and Component callbacks The framework controls ordering while game code supplies behavior
Command batching SpriteBatch queues draw commands Rendering can order work and reduce texture submissions
Strategy IAssetLoader<TAsset> New asset formats plug in without changing AssetManager
Adapter PhysicsWorld and PhysicsBody wrap Box2D.NET GameObjects use Box2D without losing access to raw handles

These patterns reinforce the same ownership model. A component declares what it needs, the current scene scope supplies it, the scene owns the resulting object, and the framework disposes the graph at a predictable boundary. That improves testability, makes scene transitions less prone to leaked state, and keeps rendering and asset code separate from gameplay behavior.

Replace or extend services

Register custom services before building the host. The built-in registrations use TryAdd, so an earlier registration can replace a default implementation where the service type permits it. Asset formats are extended by registering another closed IAssetLoader<TAsset>:

services.AddSingleton<IAssetLoader<TileMap>, TileMapAssetLoader>();

See Asset Management for a complete loader example and Scenes and Scene Scopes for scope behavior during transitions.