Moving Sprites¶
Difficulty: Beginner | Time: 15 minutes
In this tutorial, you'll learn the fundamentals of rendering and moving sprites in Brine2D.
What You'll Build¶
A simple game scene with:
- A sprite loaded from an image file
- Smooth keyboard-controlled movement
- Frame-rate independent motion using delta time
- Proper boundary checking
Prerequisites¶
- Completed Quick Start
- A sprite image file at
assets/images/player.png
Step 1: Create the Scene¶
Create MovingSpriteScene.cs:
using System.Numerics;
using Brine2D.Assets;
using Brine2D.Core;
using Brine2D.Engine;
using Brine2D.Input;
using Brine2D.Rendering;
namespace MyGame;
public class MovingSpriteScene : Scene
{
private readonly IAssetLoader _assets;
private ITexture? _playerTexture;
private Vector2 _playerPosition = new(400, 300);
private const float Speed = 200f; // pixels per second
public MovingSpriteScene(IAssetLoader assets)
{
_assets = assets;
}
}
What's happening:
- We inject
IAssetLoader- the unified service for loading textures, sounds, fonts, and music - Framework properties (
Input,Renderer,Logger,World,Audio,Game) are set automatically - no constructor injection needed for those
Step 2: Load the Sprite¶
Override OnLoadAsync to load your texture:
protected override async Task OnLoadAsync(CancellationToken ct, IProgress<float>? progress = null)
{
Logger.LogInformation(""Loading player sprite..."");
_playerTexture = await _assets.GetOrLoadTextureAsync(
""assets/images/player.png"",
TextureScaleMode.Nearest, // Sharp scaling for pixel art
ct);
Logger.LogInformation(""Sprite loaded: {W}x{H}"",
_playerTexture.Width, _playerTexture.Height);
}
Key points:
GetOrLoadTextureAsyncloads and caches the texture - subsequent calls return the cached versionTextureScaleMode.Nearestkeeps pixel art sharp when scaled- Always pass the
CancellationToken
Step 3: Draw the Sprite¶
Override OnRender to draw the texture:
protected override void OnRender(GameTime gameTime)
{
if (_playerTexture != null)
{
Renderer.DrawTexture(_playerTexture, _playerPosition.X, _playerPosition.Y);
}
}
Note: Frame management (clear, begin, end) is automatic - just draw.
Step 4: Add Movement¶
Override OnUpdate to handle keyboard input:
protected override void OnUpdate(GameTime gameTime)
{
var deltaTime = (float)gameTime.DeltaTime;
// Calculate movement direction
var movement = Vector2.Zero;
if (Input.IsKeyDown(Key.Left) || Input.IsKeyDown(Key.A)) movement.X -= 1;
if (Input.IsKeyDown(Key.Right) || Input.IsKeyDown(Key.D)) movement.X += 1;
if (Input.IsKeyDown(Key.Up) || Input.IsKeyDown(Key.W)) movement.Y -= 1;
if (Input.IsKeyDown(Key.Down) || Input.IsKeyDown(Key.S)) movement.Y += 1;
// Normalize to prevent faster diagonal movement
if (movement != Vector2.Zero)
{
movement = Vector2.Normalize(movement);
_playerPosition += movement * Speed * deltaTime;
}
// Keep player on screen
var spriteWidth = _playerTexture?.Width ?? 32;
var spriteHeight = _playerTexture?.Height ?? 32;
_playerPosition.X = Math.Clamp(_playerPosition.X, 0, Renderer.Width - spriteWidth);
_playerPosition.Y = Math.Clamp(_playerPosition.Y, 0, Renderer.Height - spriteHeight);
}
Key points:
IsKeyDownreturnstrueevery frame the key is held - perfect for movement- Multiplying by
deltaTimemakes movement frame-rate independent (same speed at 30fps and 144fps) Vector2.Normalizeprevents diagonal movement from being 41% faster
Step 5: Register and Run¶
In Program.cs:
using Brine2D.Hosting;
using MyGame;
var builder = GameApplication.CreateBuilder(args);
builder.Configure(options =>
{
options.Window.Title = ""Moving Sprites Tutorial"";
options.Window.Width = 1280;
options.Window.Height = 720;
});
builder.AddScene<MovingSpriteScene>();
await using var game = builder.Build();
await game.RunAsync<MovingSpriteScene>();
Complete Code¶
using System.Numerics;
using Brine2D.Assets;
using Brine2D.Core;
using Brine2D.Engine;
using Brine2D.Input;
using Brine2D.Rendering;
namespace MyGame;
public class MovingSpriteScene : Scene
{
private readonly IAssetLoader _assets;
private ITexture? _playerTexture;
private Vector2 _playerPosition = new(400, 300);
private const float Speed = 200f;
public MovingSpriteScene(IAssetLoader assets) => _assets = assets;
protected override async Task OnLoadAsync(CancellationToken ct, IProgress<float>? progress = null)
{
_playerTexture = await _assets.GetOrLoadTextureAsync(
""assets/images/player.png"",
TextureScaleMode.Nearest,
ct);
}
protected override void OnUpdate(GameTime gameTime)
{
var deltaTime = (float)gameTime.DeltaTime;
var movement = Vector2.Zero;
if (Input.IsKeyDown(Key.Left) || Input.IsKeyDown(Key.A)) movement.X -= 1;
if (Input.IsKeyDown(Key.Right) || Input.IsKeyDown(Key.D)) movement.X += 1;
if (Input.IsKeyDown(Key.Up) || Input.IsKeyDown(Key.W)) movement.Y -= 1;
if (Input.IsKeyDown(Key.Down) || Input.IsKeyDown(Key.S)) movement.Y += 1;
if (movement != Vector2.Zero)
{
movement = Vector2.Normalize(movement);
_playerPosition += movement * Speed * deltaTime;
}
_playerPosition.X = Math.Clamp(_playerPosition.X, 0, Renderer.Width - (_playerTexture?.Width ?? 32));
_playerPosition.Y = Math.Clamp(_playerPosition.Y, 0, Renderer.Height - (_playerTexture?.Height ?? 32));
}
protected override void OnRender(GameTime gameTime)
{
if (_playerTexture != null)
{
Renderer.DrawTexture(_playerTexture, _playerPosition.X, _playerPosition.Y);
}
}
}
Challenges¶
Easy¶
- Change the speed - make the sprite faster or slower
- Different keys - add gamepad support with
Input.GetGamepadLeftStick() - Background color - set
Renderer.ClearColorinOnEnter
Medium¶
- Multiple sprites - load and move 2-3 sprites independently
- Wrap-around - when the sprite leaves one edge, appear on the opposite
- Speed boost - hold Shift to move 2x faster
Hard¶
- Smooth acceleration - gradually speed up and slow down
- Mouse follow - move toward the mouse with
Input.MousePosition - Rotation - rotate the sprite to face the direction of movement
What You Learned¶
Loading textures with
IAssetLoader.GetOrLoadTextureAsync()
Drawing sprites with
Renderer.DrawTexture()
Keyboard input with
Input.IsKeyDown()
Delta time for frame-rate independent movement
Vector math with
Vector2.Normalize()
Boundary checking with
Math.Clamp()
Scene lifecycle - Load, Update, Render
Next Steps¶
- Animation System - Animate your sprite with sprite sheets
- Keyboard Guide - Master all keyboard input
- First Game - Build a complete game