Quick Start¶
Get a Brine2D game running in 5 minutes. This guide takes you from zero to a window with a moving sprite.
Prerequisites¶
Before starting:
.NET 10 SDK installed
IDE ready (Visual Studio 2022+, VS Code, or Rider)
5 minutes
New to Brine2D? Perfect! This guide assumes no prior knowledge.
Already have a project? Skip to Add to Existing Project.
Step 1: Create Project¶
Open your terminal and create a new console application:
dotnet new console -n MyFirstGame
cd MyFirstGame
What this does:
- Creates a new .NET 10 console application
- Names it MyFirstGame
- Changes to the project directory
Step 2: Install Brine2D¶
Add the Brine2D package:
dotnet add package Brine2D
What this does: - Installs Brine2D (core engine, rendering, input, audio - everything you need)
Verify installation:
dotnet list package
You should see:
Top-level Package Requested Resolved
> Brine2D x.x.x x.x.x
Step 3: Create Your First Scene¶
Replace the contents of Program.cs with:
using Brine2D.Core;
using Brine2D.Engine;
using Brine2D.Hosting;
using Brine2D.Input;
using System.Numerics;
var builder = GameApplication.CreateBuilder(args);
builder.Configure(options =>
{
options.Window.Title = "My First Game";
options.Window.Width = 800;
options.Window.Height = 600;
});
builder.AddScene<GameScene>();
await using var game = builder.Build();
await game.RunAsync<GameScene>();
public class GameScene : Scene
{
private readonly IGameContext _gameContext;
private Vector2 _playerPosition = new(400, 300);
private readonly float _speed = 200f;
public GameScene(IGameContext gameContext)
{
_gameContext = gameContext;
}
protected override void OnUpdate(GameTime gameTime)
{
if (Input.IsKeyPressed(Key.Escape))
{
_gameContext.RequestExit();
}
var deltaTime = (float)gameTime.DeltaTime;
if (Input.IsKeyDown(Key.W)) _playerPosition.Y -= _speed * deltaTime;
if (Input.IsKeyDown(Key.S)) _playerPosition.Y += _speed * deltaTime;
if (Input.IsKeyDown(Key.A)) _playerPosition.X -= _speed * deltaTime;
if (Input.IsKeyDown(Key.D)) _playerPosition.X += _speed * deltaTime;
}
protected override void OnRender(GameTime gameTime)
{
Renderer.DrawRectangleFilled(
_playerPosition.X - 25,
_playerPosition.Y - 25,
50, 50,
Color.Blue);
Renderer.DrawText("WASD: Move | ESC: Quit", 10, 10, Color.White);
}
}
Notice the pattern:
- Constructor only has YOUR dependencies (IGameContext)
- Logger, World, Renderer, Input, Audio, Game are available automatically as framework properties
- No need to pass framework services through the constructor!
Step 4: Run Your Game¶
Start your game:
dotnet run
You should see: - A window titled "My First Game" - A blue square in the center - Instructions at the top - The square moves with WASD keys - Escape quits the game
Success! You've created your first Brine2D game.
Understanding the Code¶
Let's break down what each part does:
Application Setup¶
var builder = GameApplication.CreateBuilder(args);
builder.Configure(options =>
{
options.Window.Title = "My First Game";
options.Window.Width = 800;
options.Window.Height = 600;
});
builder.AddScene<GameScene>();
await using var game = builder.Build();
await game.RunAsync<GameScene>();
What this does: 1. Creates a game application builder (like ASP.NET Core) 2. Configures window and engine options 3. Registers your game scene 4. Builds and runs the game
Pattern: This is dependency injection - Brine2D uses ASP.NET Core patterns.
Scene Class¶
public class GameScene : Scene
{
private readonly IGameContext _gameContext;
public GameScene(IGameContext gameContext)
{
_gameContext = gameContext;
}
protected override void OnRender(GameTime gameTime)
{
Logger.LogDebug("Rendering frame");
var player = World.CreateEntity("Player");
Renderer.DrawText("Hello", 10, 10, Color.White);
}
}
What's important:
- Constructor: Only YOUR services (IGameContext, custom services, etc.)
- Framework properties: Logger, World, Renderer, Input, Audio, Game set automatically by the framework
- Clean: No need to inject ILogger<T>, IEntityWorld, IRenderer, IInputContext - they're all properties
Framework Properties¶
These are available in all lifecycle methods (after constructor):
| Property | Type | Purpose |
|---|---|---|
Logger |
ILogger |
Logging for this scene (typed automatically) |
World |
IEntityWorld |
Entity world (scoped per scene, auto-cleanup!) |
Renderer |
IRenderer |
Drawing + render state (clear color, camera, etc.) |
Input |
IInputContext |
Keyboard, mouse, and gamepad input |
Audio |
IAudioPlayer |
Play sounds and music |
Game |
IGameContext |
Game context (request exit, game time) |
You never inject these - they're set by SceneManager before any lifecycle methods run.
Update Loop¶
protected override void OnUpdate(GameTime gameTime)
{
if (Input.IsKeyPressed(Key.Escape))
{
_gameContext.RequestExit();
}
var deltaTime = (float)gameTime.DeltaTime;
if (Input.IsKeyDown(Key.W)) _playerPosition.Y -= _speed * deltaTime;
if (Input.IsKeyDown(Key.S)) _playerPosition.Y += _speed * deltaTime;
if (Input.IsKeyDown(Key.A)) _playerPosition.X -= _speed * deltaTime;
if (Input.IsKeyDown(Key.D)) _playerPosition.X += _speed * deltaTime;
}
What this does:
- Called every frame (~60 times per second)
- Handles input via the Input framework property
- Updates game state (position, physics, AI)
- Uses deltaTime for frame-rate independent movement
Render Loop¶
protected override void OnRender(GameTime gameTime)
{
Renderer.DrawRectangleFilled(
_playerPosition.X - 25,
_playerPosition.Y - 25,
50, 50,
Color.Blue);
Renderer.DrawText("WASD: Move | ESC: Quit", 10, 10, Color.White);
}
What this does:
- Called every frame after update
- Draws game objects (sprites, shapes, text) via Renderer property
- Frame management (clear, begin, end) is automatic
Add to Existing Project¶
Already have a .NET 10 project? Add Brine2D:
dotnet add package Brine2D
Then add the startup code to your Program.cs:
using Brine2D.Hosting;
var builder = GameApplication.CreateBuilder(args);
builder.Configure(options =>
{
options.Window.Title = "My Game";
options.Window.Width = 1280;
options.Window.Height = 720;
});
builder.AddScene<GameScene>();
await using var game = builder.Build();
await game.RunAsync<GameScene>();
Common Mistakes¶
-
Don't access framework properties in the constructor
// :x: Wrong - properties not set yet! public GameScene() { Logger.LogInformation("Created"); // Throws! World.CreateEntity("Player"); // Throws! } -
Don't inject framework services via constructor
// :x: Unnecessary - use properties instead public GameScene(IRenderer renderer, IEntityWorld world) { } // :white_check_mark: Correct - use framework properties protected override void OnRender(GameTime gameTime) { Renderer.DrawText("Hello", 10, 10, Color.White); } -
Don't manually clear World on unload
// :x: Not needed - automatic! protected override Task OnUnloadAsync(CancellationToken ct) { foreach (var entity in World.Entities) World.DestroyEntity(entity); // Not needed! return Task.CompletedTask; } -
Don't poll input in OnRender
// :x: Wrong protected override void OnRender(GameTime gameTime) { if (Input.IsKeyDown(Key.W)) { ... } // Input in update only! } -
Don't load assets in OnUpdate
// :x: Wrong - causes lag every frame! protected override void OnUpdate(GameTime gameTime) { var texture = await _assets.GetOrLoadTextureAsync(...); // NO! }
Summary¶
What you learned:
| Concept | Description |
|---|---|
| GameApplication | Entry point, similar to ASP.NET Core |
| Scene | Container for game logic (update + render) |
| Framework Properties | Logger, World, Renderer, Input, Audio, Game - set automatically |
| Scoped World | Each scene has isolated EntityWorld - automatic cleanup! |
| Dependency Injection | YOUR services injected via constructor |
| Game Loop | Update (logic) → Render (drawing) |
| deltaTime | Frame-rate independent movement |
Key patterns:
// 1. Setup
var builder = GameApplication.CreateBuilder(args);
builder.Configure(options =>
{
options.Window.Title = "My Game";
options.Window.Width = 1280;
options.Window.Height = 720;
});
builder.AddScene<GameScene>();
// 2. Scene
public class GameScene : Scene
{
public GameScene(IGameContext gameContext) { }
protected override void OnUpdate(GameTime gameTime)
{
Logger.LogDebug("Updating");
var player = World.GetEntityByName("Player");
}
protected override void OnRender(GameTime gameTime)
{
Renderer.DrawText("Hello", 10, 10, Color.White);
}
}
// 3. Run
await using var game = builder.Build();
await game.RunAsync<GameScene>();
Next Steps¶
Now that you have a working game, explore more features:
- Your First Game - Build a complete game with sprites, audio, and collision
- Project Structure - Organize your code
- Configuration - Configure game settings
- Input Guide - Master keyboard, mouse, and gamepad input
- Rendering Guide - Work with sprites and textures
Quick Reference¶
// Minimal Program.cs
using Brine2D.Hosting;
var builder = GameApplication.CreateBuilder(args);
builder.Configure(options =>
{
options.Window.Title = "My Game";
options.Window.Width = 800;
options.Window.Height = 600;
});
builder.AddScene<GameScene>();
await using var game = builder.Build();
await game.RunAsync<GameScene>();
// Minimal Scene
using Brine2D.Core;
using Brine2D.Engine;
using Brine2D.Input;
public class GameScene : Scene
{
private readonly IGameContext _gameContext;
public GameScene(IGameContext gameContext)
{
_gameContext = gameContext;
}
protected override void OnUpdate(GameTime gameTime)
{
if (Input.IsKeyPressed(Key.Escape))
{
_gameContext.RequestExit();
}
}
protected override void OnRender(GameTime gameTime)
{
Renderer.DrawText("Hello, Brine2D!", 10, 10, Color.White);
}
}
Ready to build your first complete game? Head to Your First Game!