Skip to content

Project Structure

Brine2D follows a clean, modular architecture inspired by ASP.NET Core's design principles. Each package has a single responsibility and depends only on what it needs.

Architecture Overview

graph TB
    Game["<b>Your Game Project</b><br/><i>BasicGame.csproj</i>"]

    subgraph "Direct References"
        Hosting["<b>Brine2D.Hosting</b>"]
        RenderingSDL["<b>Brine2D.Rendering.SDL</b>"]
        InputSDL["<b>Brine2D.Input.SDL</b>"]
        AudioSDL["<b>Brine2D.Audio.SDL</b>"]
        UI["<b>Brine2D.UI</b>"]
    end

    subgraph "Transitive Dependencies"
        Engine["<b>Brine2D.Engine</b>"]
        Rendering["<b>Brine2D.Rendering</b>"]
        Input["<b>Brine2D.Input</b>"]
        Audio["<b>Brine2D.Audio</b>"]
        Core["<b>Brine2D.Core</b>"]
    end

    Game --> Hosting
    Game --> RenderingSDL
    Game --> InputSDL
    Game --> AudioSDL
    Game --> UI

    Hosting --> Engine
    Engine --> Core

    RenderingSDL --> Rendering
    InputSDL --> Input
    AudioSDL --> Audio
    UI --> Core
    UI --> Rendering
    UI --> Input

    Rendering --> Core
    Input --> Core
    Audio -.-> Core

This design follows the Dependency Inversion Principle: high-level modules (your game) depend on abstractions (interfaces), not concrete implementations.

Core Packages

Brine2D.Core

Purpose: Core abstractions and data structures used throughout the engine.

Key Types: - IScene - Interface for game scenes - Scene - Base class with lifecycle methods - GameTime - Holds frame timing information - IGameContext - Access to game state and exit control - Color, Rectangle, RectangleF - Common data types

Namespaces: - Brine2D.Core - Core interfaces and base classes - Brine2D.Core.Animation - Sprite animation system - Brine2D.Core.Collision - Collision detection - Brine2D.Core.Tilemap - Tilemap loading and rendering

No Dependencies - This package is the foundation and doesn't depend on other Brine2D packages.

// Example: Implementing a custom scene
public class MyScene : Scene
{
    public MyScene(ILogger<MyScene> logger) : base(logger) { }

    protected override void OnUpdate(GameTime gameTime) { }
    protected override void OnRender(GameTime gameTime) { }
}

Brine2D.Engine

Purpose: Game loop, scene management, and engine coordination.

Key Types: - IGameEngine - Core engine interface - GameEngine - Coordinates subsystems - IGameLoop - Game loop abstraction - GameLoop - Frame timing and update/render cycle - ISceneManager - Scene loading and transitions - SceneManager - Manages active scene

Dependencies: - Brine2D.Core - Microsoft.Extensions.* (DI, Logging, Configuration)

// The engine coordinates everything
var engine = serviceProvider.GetRequiredService<IGameEngine>();
await engine.InitializeAsync();

// Scene manager handles scene transitions
var sceneManager = serviceProvider.GetRequiredService<ISceneManager>();
await sceneManager.LoadSceneAsync<MenuScene>();

Brine2D.Hosting

Purpose: ASP.NET-style application hosting and builder pattern.

Key Types: - GameApplication - The main application host - GameApplicationBuilder - Fluent API for configuration - Service registration extensions

Dependencies: - Brine2D.Core - Brine2D.Engine - Microsoft.Extensions.Hosting

This is your entry point:

// Familiar ASP.NET-style builder
var builder = GameApplication.CreateBuilder(args);

// Configure services
builder.Services.AddSDL3Rendering();
builder.Services.AddSDL3Input();
builder.Services.AddScene<GameScene>();

// Build and run
var game = builder.Build();
await game.RunAsync<GameScene>();

Rendering System

Brine2D.Rendering

Purpose: Rendering abstractions (interfaces and options).

Key Types: - IRenderer - Core rendering interface - ITexture - Texture abstraction - ITextureLoader - Async texture loading - IFont, IFontLoader - Font rendering - ICamera - Camera abstraction - Camera2D - 2D camera implementation - Color - Color representation - RenderingOptions - Configuration

Dependencies: - Brine2D.Core

// All rendering goes through IRenderer
public class GameScene : Scene
{
    private readonly IRenderer _renderer;

    protected override void OnRender(GameTime gameTime)
    {
        _renderer.Clear(Color.Black);
        _renderer.BeginFrame();
        _renderer.DrawTexture(texture, x, y);
        _renderer.EndFrame();
    }
}

Brine2D.Rendering.SDL

Purpose: SDL3-based rendering implementation.

Key Types: - SDL3Renderer - Implements IRenderer using SDL3 - SDL3Texture - SDL3 texture wrapper - SDL3TextureLoader - Loads textures via SDL3_image - SDL3Font, SDL3FontLoader - Font rendering via SDL3_ttf - Service registration extensions

Dependencies: - Brine2D.Rendering - SDL3-CS (NuGet package - C# bindings for SDL3)

Registration:

builder.Services.AddSDL3Rendering(options =>
{
    options.WindowTitle = "My Game";
    options.WindowWidth = 1280;
    options.WindowHeight = 720;
    options.VSync = true;
    options.Backend = GraphicsBackend.GPU; // or Legacy
});

Input System

Brine2D.Input

Purpose: Input abstractions for keyboard, mouse, and gamepad.

Key Types: - IInputService - Unified input interface - Keys - Keyboard key enumeration - MouseButton - Mouse button enumeration - GamepadButton, GamepadAxis - Gamepad enums - IInputLayer - For layered input processing (like middleware) - InputLayerManager - Manages input layers

Dependencies: - Brine2D.Core

// Unified input API
if (_input.IsKeyPressed(Keys.Space)) { Jump(); }
if (_input.IsMouseButtonDown(MouseButton.Left)) { Shoot(); }
var stick = _input.GetGamepadLeftStick();

Brine2D.Input.SDL

Purpose: SDL3-based input implementation.

Key Types: - SDL3InputService - Implements IInputService - Service registration extensions

Dependencies: - Brine2D.Input - SDL3-CS

Registration:

builder.Services.AddSDL3Input();

Audio System

Brine2D.Audio

Purpose: Audio abstractions for sound effects and music.

Key Types: - IAudioService - Audio playback interface - ISoundEffect - Short sound effect - IMusic - Background music

Dependencies: - None (pure abstractions)

// Simple audio API
var jumpSound = await _audio.LoadSoundAsync("jump.wav");
_audio.PlaySound(jumpSound);

var bgMusic = await _audio.LoadMusicAsync("theme.mp3");
_audio.PlayMusic(bgMusic, loops: -1); // Loop forever

Brine2D.Audio.SDL

Purpose: SDL3_mixer-based audio implementation.

Key Types: - SDL3AudioService - Implements IAudioService - SDL3SoundEffect, SDL3Music - SDL3 wrappers - Service registration extensions

Dependencies: - Brine2D.Audio - SDL3-CS (includes SDL3_mixer bindings)

Registration:

builder.Services.AddSDL3Audio();

UI System

Brine2D.UI

Purpose: Immediate-mode UI framework.

Key Types: - UICanvas - Container for UI components - UIButton, UILabel, UISlider - Basic components - UITextInput, UICheckbox, UIDropdown - Form controls - UIDialog, UITooltip - Advanced components - UIPanel, UIScrollView - Layout containers - IUIComponent - Interface for custom UI

Dependencies: - Brine2D.Core - Brine2D.Rendering - Brine2D.Input

// Add UI canvas to your scene
private readonly UICanvas _uiCanvas;

var button = new UIButton("Click Me", new Vector2(10, 10), new Vector2(100, 30));
button.OnClick += () => Logger.LogInformation("Clicked!");
_uiCanvas.Add(button);

// Update and render
_uiCanvas.Update(deltaTime);
_uiCanvas.Render(_renderer);

Optional Packages

Brine2D.Core.Animation

Included in: Brine2D.Core

Purpose: Sprite animation system.

Key Types: - SpriteAnimator - Plays animation clips - AnimationClip - Sequence of frames - SpriteFrame - Single frame with duration

var animator = new SpriteAnimator();
var walkAnim = AnimationClip.FromSpriteSheet("walk", 32, 32, 8, columns: 10);
animator.AddAnimation(walkAnim);
animator.Play("walk");

Brine2D.Core.Collision

Included in: Brine2D.Core

Purpose: Collision detection system.

Key Types: - CollisionSystem - Manages collision checks - CollisionShape - Base for all colliders - BoxCollider, CircleCollider - Shape types - RectangleF - Floating-point rectangle - CollisionResponse - Helper methods for collision resolution

var collisionSystem = new CollisionSystem();
var playerCollider = new BoxCollider(32, 32);
collisionSystem.AddShape(playerCollider);

var collisions = collisionSystem.GetCollisions(playerCollider);

Brine2D.Core.Tilemap

Included in: Brine2D.Core

Purpose: Tilemap loading and rendering (Tiled format).

Key Types: - Tilemap - Loaded tilemap data - ITilemapLoader - Loads .tmj files - TilemapRenderer - Renders tilemaps

Supports: - Tiled Editor .tmj format - Multiple layers - Automatic collision generation

var tilemap = await _tilemapLoader.LoadAsync("level1.tmj");
await _tilemapRenderer.LoadTilesetAsync(tilemap, _textureLoader);
_tilemapRenderer.Render(tilemap, _renderer, _camera);

// Generate collision from tilemap
var colliders = tilemap.GenerateColliders("collision");

Dependency Flow

Understanding how packages depend on each other:

Your Game
    ↓ references
Brine2D.Hosting
    ↓ uses
Brine2D.Engine + Brine2D.Core
    ↓ abstractions
Brine2D.Rendering ← Brine2D.Rendering.SDL (implementation)
Brine2D.Input ← Brine2D.Input.SDL (implementation)
Brine2D.Audio ← Brine2D.Audio.SDL (implementation)

Key insight: Your game references abstractions (interfaces), and the hosting layer wires up concrete implementations via dependency injection.


Directory Structure

Brine2D/
├── src/
│   ├── Brine2D.Core/
│   │   ├── Scene.cs, IScene.cs
│   │   ├── GameTime.cs, IGameContext.cs
│   │   ├── Animation/
│   │   │   ├── SpriteAnimator.cs
│   │   │   └── AnimationClip.cs
│   │   ├── Collision/
│   │   │   ├── CollisionSystem.cs
│   │   │   └── CollisionShape.cs
│   │   └── Tilemap/
│   │       ├── Tilemap.cs
│   │       └── TilemapLoader.cs
│   │
│   ├── Brine2D.Engine/
│   │   ├── GameEngine.cs
│   │   ├── GameLoop.cs
│   │   └── SceneManager.cs
│   │
│   ├── Brine2D.Hosting/
│   │   ├── GameApplication.cs
│   │   └── GameApplicationBuilder.cs
│   │
│   ├── Brine2D.Rendering/
│   │   ├── IRenderer.cs
│   │   ├── ITexture.cs, ITextureLoader.cs
│   │   ├── IFont.cs, IFontLoader.cs
│   │   ├── ICamera.cs, Camera2D.cs
│   │   ├── Color.cs
│   │   └── RenderingOptions.cs
│   │
│   ├── Brine2D.Rendering.SDL/
│   │   ├── SDL3Renderer.cs
│   │   ├── SDL3Texture.cs, SDL3TextureLoader.cs
│   │   └── SDL3Font.cs, SDL3FontLoader.cs
│   │
│   ├── Brine2D.Input/
│   │   ├── IInputService.cs
│   │   ├── Keys.cs, MouseButton.cs
│   │   ├── GamepadButton.cs, GamepadAxis.cs
│   │   └── IInputLayer.cs, InputLayerManager.cs
│   │
│   ├── Brine2D.Input.SDL/
│   │   └── SDL3InputService.cs
│   │
│   ├── Brine2D.Audio/
│   │   ├── IAudioService.cs
│   │   ├── ISoundEffect.cs
│   │   └── IMusic.cs
│   │
│   ├── Brine2D.Audio.SDL/
│   │   ├── SDL3AudioService.cs
│   │   ├── SDL3SoundEffect.cs
│   │   └── SDL3Music.cs
│   │
│   └── Brine2D.UI/
│       ├── UICanvas.cs
│       ├── IUIComponent.cs
│       ├── UIButton.cs, UILabel.cs
│       ├── UISlider.cs, UITextInput.cs
│       └── UIDialog.cs, UITooltip.cs
│
├── samples/
│   ├── BasicGame/
│   ├── PlatformerGame/
│   └── AdvancedGame/
│
└── tests/
    ├── Brine2D.Core.Tests/
    ├── Brine2D.Engine.Tests/
    └── ...

Design Principles

Brine2D's architecture follows these key principles:

1. Dependency Inversion

High-level modules (your game) depend on abstractions (IRenderer), not implementations (SDL3Renderer).

2. Single Responsibility

Each package has one job: - Core = abstractions - Engine = game loop - Rendering = draw things - Input = handle input - etc.

3. Open/Closed

Open for extension (implement IRenderer with DirectX, Metal, etc.), closed for modification (core interfaces rarely change).

4. Dependency Injection

Everything is resolved via DI container—testable, mockable, swappable.

5. Configuration Over Code

Prefer gamesettings.json over hardcoded values.


Extending Brine2D

Add a Custom Renderer

// 1. Implement IRenderer
public class MyCustomRenderer : IRenderer
{
    public void DrawTexture(ITexture texture, float x, float y) { ... }
    // ... implement all methods
}

// 2. Register it
builder.Services.AddSingleton<IRenderer, MyCustomRenderer>();

Add a Custom Input Provider

// 1. Implement IInputService
public class MyInputService : IInputService
{
    public bool IsKeyDown(Keys key) { ... }
    // ... implement all methods
}

// 2. Register it
builder.Services.AddSingleton<IInputService, MyInputService>();

Create Custom UI Components

public class MyCustomWidget : IUIComponent
{
    public Vector2 Position { get; set; }
    public Vector2 Size { get; set; }
    public bool Visible { get; set; } = true;
    public bool Enabled { get; set; } = true;
    public UITooltip? Tooltip { get; set; }

    public void Update(float deltaTime) { }
    public void Render(IRenderer renderer) { }
    public bool Contains(Vector2 screenPosition) { return false; }
}

Best Practices

DO: Depend on Interfaces

// Good
private readonly IRenderer _renderer;

// Bad
private readonly SDL3Renderer _renderer;

DO: Use Constructor Injection

public MyScene(IRenderer renderer, IInputService input, ILogger<MyScene> logger)
    : base(logger)
{
    _renderer = renderer;
    _input = input;
}

DO: Keep Scenes Focused

Each scene should represent one game state (Menu, Gameplay, GameOver, etc.).

DO: Use Async for Loading

protected override async Task OnLoadAsync(CancellationToken cancellationToken)
{
    _texture = await _textureLoader.LoadTextureAsync("sprite.png", cancellationToken);
}

DON'T: Directly Reference SDL3

Let the SDL implementations handle SDL—your game should only use Brine2D abstractions.

DON'T: Use Static State

Use DI instead of singletons or static classes.


Next Steps


Understanding Brine2D's structure makes it easy to navigate, extend, and maintain. The modular design means you can swap out any piece—just like ASP.NET! 🎯