Scene Transitions¶
Scene transitions provide visual feedback during scene changes, making your game feel polished and professional. Brine2D supports fade transitions and custom loading screens.
Overview¶
| Feature | Purpose | Use For |
|---|---|---|
| ISceneTransition | Visual effect during transition | Fade in/out, wipes, crossfades |
| LoadingScene | Progress indication | Long loads, asset loading |
| Combined | Transition + loading screen | Best user experience |
Basic Scene Transitions¶
Without Transition (Instant)¶
// Instant scene change (no transition)
_sceneManager.LoadScene<GameScene>();
With Fade Transition¶
using Brine2D.Engine.Transitions;
// Fade to black and back (1 second)
_sceneManager.LoadScene<GameScene>(
new FadeTransition(duration: 1f));
// Fade to white (2 seconds)
_sceneManager.LoadScene<GameScene>(
new FadeTransition(duration: 2f, color: Color.White));
The ISceneTransition Interface¶
public interface ISceneTransition
{
float Duration { get; }
bool IsComplete { get; }
float Progress { get; }
void Begin();
void Update(GameTime gameTime);
void Render(IRenderer renderer);
}
Loading Screens¶
The LoadingScene Class¶
LoadingScene extends SceneBase (not Scene) — it does NOT have a World. Loading screens are visual-only and render between scene scopes.
public abstract class LoadingScene : SceneBase
{
protected float LoadingProgress { get; }
protected string LoadingMessage { get; }
public void UpdateProgress(float progress, string? message = null);
protected virtual void OnRenderLoading(GameTime gameTime);
}
Key features:
- Progress tracking (0.0 to 1.0)
-
Status messages
-
Thread-safe progress updates
-
No EntityWorld — visual-only
Using Loading Screens¶
// Load with custom loading screen
_sceneManager.LoadScene<GameScene, MyLoadingScreen>();
// With transition AND loading screen
_sceneManager.LoadScene<GameScene, MyLoadingScreen>(
new FadeTransition(1f));
Basic Loading Screen¶
public class SimpleLoadingScreen : LoadingScene
{
// Default implementation shows:
// - "Loading..." text
// - Progress bar
// - Percentage
// No need to override anything!
}
Custom Loading Screen¶
public class CustomLoadingScreen : LoadingScene
{
protected override void OnRenderLoading(GameTime gameTime)
{
var centerX = Renderer.Width / 2f;
var centerY = Renderer.Height / 2f;
Renderer.DrawText("Loading Game...", centerX - 80, centerY - 100, Color.Cyan);
// Progress bar
var barWidth = 400f;
var barHeight = 30f;
var barX = centerX - barWidth / 2f;
var barY = centerY + 20;
Renderer.DrawRectangleFilled(barX, barY, barWidth, barHeight, Color.FromArgb(40, 40, 40));
Renderer.DrawRectangleFilled(barX, barY, barWidth * LoadingProgress, barHeight, Color.FromArgb(50, 150, 255));
Renderer.DrawRectangleOutline(barX, barY, barWidth, barHeight, Color.White, 2f);
Renderer.DrawText(LoadingMessage, centerX - 60, centerY + 70, Color.FromArgb(200, 200, 200));
Renderer.DrawText($"{(int)(LoadingProgress * 100)}%", centerX - 20, centerY - 10, Color.White);
}
}
Scene with Loading Screen¶
public class GameScene : Scene
{
private readonly IAssetLoader _assets;
public GameScene(IAssetLoader assets)
{
_assets = assets;
}
protected override async Task OnLoadAsync(CancellationToken ct, IProgress<float>? progress = null)
{
progress?.Report(0.2f);
_playerTexture = await _assets.GetOrLoadTextureAsync("assets/images/player.png", cancellationToken: ct);
progress?.Report(0.5f);
_bgMusic = await _assets.GetOrLoadMusicAsync("assets/audio/theme.ogg", ct);
progress?.Report(0.8f);
// Or use AssetManifest for parallel preloading:
// await _assets.PreloadAsync(_manifest, progress: ..., cancellationToken: ct);
progress?.Report(1.0f);
}
}
Transition from Any Scene¶
public class MenuScene : Scene
{
private readonly ISceneManager _sceneManager;
public MenuScene(ISceneManager sceneManager)
{
_sceneManager = sceneManager;
}
protected override void OnUpdate(GameTime gameTime)
{
if (Input.IsKeyPressed(Key.Enter))
{
// Simple transition
_sceneManager.LoadScene<GameScene>(
new FadeTransition(duration: 0.5f));
}
if (Input.IsKeyPressed(Key.Space))
{
// With loading screen
_sceneManager.LoadScene<GameScene, CustomLoadingScreen>(
new FadeTransition(duration: 0.5f));
}
}
}
ISceneManager API¶
public interface ISceneManager
{
Scene? CurrentScene { get; }
event EventHandler<SceneLoadFailedEventArgs>? SceneLoadFailed;
// Fire-and-forget transitions (void return)
void LoadScene<TScene>(ISceneTransition? transition = null, CancellationToken ct = default)
where TScene : Scene;
void LoadScene<TScene, TLoadingScene>(ISceneTransition? transition = null, CancellationToken ct = default)
where TScene : Scene
where TLoadingScene : LoadingScene;
void LoadScene(Type sceneType, ISceneTransition? transition = null, LoadingScene? loadingScreen = null, CancellationToken ct = default);
void LoadScene<TScene>(Func<IServiceProvider, TScene> sceneFactory, ISceneTransition? transition = null, LoadingScene? loadingScreen = null, CancellationToken ct = default)
where TScene : Scene;
}
Important: LoadScene is fire-and-forget. It queues a transition for the end of the current frame. React to the transition in the target scene's OnEnter() or handle failures via the SceneLoadFailed event.
Best Practices¶
- Use transitions for all player-visible scene changes
- Use loading screens for scenes with heavy asset loading
- Report progress via the
IProgress<float>?parameter inOnLoadAsync - Use
AssetManifestfor parallel asset preloading in complex scenes
Next Steps¶
- Scene Lifecycle — Scene lifecycle in depth
- Scenes Overview — Scene concepts