Layer System¶
The Layer system in Evoker Engine provides a modular way to organize game logic, rendering, and UI. Layers allow you to separate concerns and control the order of updates and rendering.
Overview¶
Layers are stackable components that:
- Organize game logic into manageable modules
- Control update and render order
- Handle events in a prioritized manner
- Can be pushed, popped, and reordered dynamically
- Support overlays (always-on-top layers)
Layer Base Class¶
public abstract class Layer
{
public string Name { get; protected set; }
protected Layer(string name = "Layer");
public virtual void OnAttach() { }
public virtual void OnDetach() { }
public virtual void OnUpdate(float deltaTime) { }
public virtual void OnRender() { }
public virtual void OnEvent(Event e) { }
}
Creating a Custom Layer¶
using EvokerEngine.Core;
using Silk.NET.Input;
public class GameLayer : Layer
{
public GameLayer() : base("Game Layer") { }
public override void OnAttach()
{
// Called once when layer is added
Logger.Info($"{Name} attached");
}
public override void OnDetach()
{
// Called once when layer is removed
Logger.Info($"{Name} detached");
}
public override void OnUpdate(float deltaTime)
{
// Called every frame
// deltaTime is in seconds
}
public override void OnRender()
{
// Called every frame for rendering
}
public override void OnEvent(Event e)
{
// Handle input and window events
if (e is KeyPressedEvent keyEvent)
{
Logger.Info($"Key pressed: {keyEvent.KeyCode}");
}
}
}
Layer Lifecycle¶
1. OnAttach()¶
Called when the layer is first added to the layer stack.
Use cases: - Initialize layer-specific resources - Create entities and components - Load assets - Set up state
public override void OnAttach()
{
var scene = Application.Instance.ActiveScene;
_player = scene.CreateEntity("Player");
_camera = scene.CreateEntity("Camera");
}
2. OnUpdate(float deltaTime)¶
Called every frame to update game logic.
Parameters:
- deltaTime: Time since last frame in seconds
Use cases: - Update game logic - Handle input polling - Update animations - Physics calculations
public override void OnUpdate(float deltaTime)
{
// Move player
if (Input.IsKeyPressed(Key.W))
_playerPos.Y += 100f * deltaTime;
// Update physics
_physicsWorld.Step(deltaTime);
}
3. OnRender()¶
Called every frame for rendering operations.
Use cases: - Render game objects - Draw debug information - Submit rendering commands
public override void OnRender()
{
// Render player
_renderer.DrawSprite(_playerTexture, _playerPos);
}
4. OnEvent(Event e)¶
Called when an event occurs (input, window events).
Parameters:
- e: Event object (keyboard, mouse, window, etc.)
Use cases: - Handle keyboard/mouse input - Respond to window resize - Process gamepad input
public override void OnEvent(Event e)
{
if (e is KeyPressedEvent keyEvent)
{
if (keyEvent.KeyCode == Key.Space)
{
_player.Jump();
e.Handled = true; // Stop event propagation
}
}
}
5. OnDetach()¶
Called when the layer is removed from the layer stack.
Use cases: - Clean up resources - Save state - Unload assets
Layer Stack¶
The LayerStack manages all layers in the application.
Adding Layers¶
// Regular layers (processed first)
app.PushLayer(new GameLayer());
app.PushLayer(new PhysicsLayer());
app.PushLayer(new AudioLayer());
// Overlays (processed last, rendered on top)
app.PushOverlay(new ImGuiLayer());
app.PushOverlay(new DebugOverlay());
Layer Order¶
Layers are processed in the order they're added:
┌─────────────────┐
│ Debug Overlay │ ← Overlay (top)
├─────────────────┤
│ ImGui Layer │ ← Overlay
├─────────────────┤
│ Audio Layer │ ← Regular layer
├─────────────────┤
│ Physics Layer │ ← Regular layer
├─────────────────┤
│ Game Layer │ ← Regular layer (bottom)
└─────────────────┘
Update order: Bottom to top (Game → Physics → Audio → ImGui → Debug)
Render order: Bottom to top (Game → Physics → Audio → ImGui → Debug)
Event order: Top to bottom (Debug → ImGui → Audio → Physics → Game)
Event Handling and Propagation¶
Events are dispatched to layers in reverse order (top to bottom), allowing overlays to handle events first.
Stopping Event Propagation¶
Set e.Handled = true to prevent the event from reaching lower layers:
public override void OnEvent(Event e)
{
if (e is MouseButtonPressedEvent mouseEvent)
{
if (IsMouseOverUI(mouseEvent))
{
// UI handled the click, don't pass to game
e.Handled = true;
}
}
}
Example: UI Layer Blocking Game Input¶
class UILayer : Layer
{
public override void OnEvent(Event e)
{
if (e is MouseButtonPressedEvent)
{
if (_menuOpen)
{
// Menu is open, block game input
e.Handled = true;
}
}
}
}
class GameLayer : Layer
{
public override void OnEvent(Event e)
{
if (e is MouseButtonPressedEvent mouseEvent)
{
// Only runs if UILayer didn't handle it
if (!e.Handled)
{
FireWeapon();
}
}
}
}
Common Layer Patterns¶
Game Layer¶
Main gameplay logic and entities.
class GameLayer : Layer
{
private Scene _scene;
private List<Entity> _enemies;
public override void OnAttach()
{
_scene = Application.Instance.ActiveScene;
SpawnPlayer();
SpawnEnemies();
}
public override void OnUpdate(float deltaTime)
{
UpdatePlayer(deltaTime);
UpdateEnemies(deltaTime);
CheckCollisions();
}
public override void OnRender()
{
RenderEntities();
}
}
Physics Layer¶
Physics simulation and collision detection.
class PhysicsLayer : Layer
{
private PhysicsWorld _world;
public override void OnAttach()
{
_world = new PhysicsWorld();
}
public override void OnUpdate(float deltaTime)
{
_world.Step(deltaTime);
ApplyPhysicsToEntities();
}
}
UI Layer¶
User interface rendering and interaction.
class UILayer : Layer
{
private bool _menuOpen;
public override void OnRender()
{
if (_menuOpen)
{
RenderMenu();
}
RenderHUD();
}
public override void OnEvent(Event e)
{
if (e is KeyPressedEvent keyEvent)
{
if (keyEvent.KeyCode == Key.Escape)
{
_menuOpen = !_menuOpen;
e.Handled = true;
}
}
}
}
Debug Overlay¶
Debug information and development tools.
class DebugOverlay : Layer
{
private bool _showDebug = true;
public override void OnRender()
{
if (_showDebug)
{
RenderFPS();
RenderPerformanceStats();
RenderEntityCount();
}
}
public override void OnEvent(Event e)
{
if (e is KeyPressedEvent keyEvent)
{
if (keyEvent.KeyCode == Key.F3)
{
_showDebug = !_showDebug;
}
}
}
}
Advanced Usage¶
Dynamic Layer Management¶
You can add and remove layers at runtime:
class GameManager : Layer
{
private Layer? _pauseMenuLayer;
public void OpenPauseMenu()
{
_pauseMenuLayer = new PauseMenuLayer();
Application.Instance.PushOverlay(_pauseMenuLayer);
}
public void ClosePauseMenu()
{
if (_pauseMenuLayer != null)
{
// Note: PopOverlay not exposed in base Application
// Typically handle via enable/disable flags
_pauseMenuLayer = null;
}
}
}
Layer Communication¶
Layers can communicate through events or shared state:
class GameLayer : Layer
{
public int Score { get; set; }
public override void OnUpdate(float deltaTime)
{
if (EnemyKilled())
{
Score += 100;
}
}
}
class UILayer : Layer
{
private GameLayer? _gameLayer;
public override void OnAttach()
{
// Find game layer
// In practice, use dependency injection or events
}
public override void OnRender()
{
if (_gameLayer != null)
{
RenderScore(_gameLayer.Score);
}
}
}
Best Practices¶
✅ Do's¶
- Keep layers focused on a single responsibility
- Use overlays for UI and debug information
- Set
e.Handled = trueto stop event propagation - Clean up resources in
OnDetach() - Use descriptive layer names for debugging
❌ Don'ts¶
- Don't make layers depend on each other directly
- Don't store large amounts of state in layers
- Don't perform heavy computations in
OnRender() - Don't forget to handle layer removal/cleanup
Performance Tips¶
- Minimize layer count - Each layer has overhead
- Early exit in OnUpdate - Skip inactive layers
- Batch rendering - Combine draw calls when possible
- Profile layer performance - Identify bottlenecks
public override void OnUpdate(float deltaTime)
{
if (!_isActive) return; // Early exit
// Update logic
}
Complete Example¶
using EvokerEngine.Core;
using Silk.NET.Input;
using System.Numerics;
class MyGame
{
static void Main()
{
var app = new Application("Layer Example", 1280, 720);
app.PushLayer(new GameLayer());
app.PushLayer(new PhysicsLayer());
app.PushOverlay(new UILayer());
app.PushOverlay(new DebugOverlay());
app.Run();
}
}
class GameLayer : Layer
{
private Entity _player;
public GameLayer() : base("Game") { }
public override void OnAttach()
{
var scene = Application.Instance.ActiveScene;
_player = scene.CreateEntity("Player");
}
public override void OnUpdate(float deltaTime)
{
// Game logic
}
public override void OnEvent(Event e)
{
if (e is KeyPressedEvent keyEvent)
{
if (keyEvent.KeyCode == Key.Escape)
{
Application.Instance.Close();
}
}
}
}
See Also¶
- Application - Application lifecycle
- Events - Event system
- ECS - Entity Component System