Event System¶
The Event system in Evoker Engine provides a flexible way to handle input, window events, and custom game events. Events are dispatched to layers, allowing for decoupled communication between systems.
Overview¶
The event system:
- Handles keyboard, mouse, and gamepad input
- Manages window events (resize, close, focus)
- Supports event propagation and handling
- Allows layers to stop event propagation
- Type-safe event handling with inheritance
Event Base Class¶
public abstract class Event
{
public bool Handled { get; set; }
public abstract EventType GetEventType();
public abstract string GetName();
}
Event Properties¶
- Handled: Set to
trueto stop event propagation to lower layers - GetEventType(): Returns the event type enum
- GetName(): Returns a string description of the event
Event Types¶
public enum EventType
{
None = 0,
// Window events
WindowClose, WindowResize, WindowFocus, WindowLostFocus, WindowMoved,
// Application events
AppTick, AppUpdate, AppRender,
// Keyboard events
KeyPressed, KeyReleased, KeyTyped,
// Mouse events
MouseButtonPressed, MouseButtonReleased, MouseMoved, MouseScrolled,
// Gamepad events
GamepadConnected, GamepadDisconnected,
GamepadButtonPressed, GamepadButtonReleased
}
Window Events¶
WindowCloseEvent¶
Fired when the window close button is clicked.
Example:
public override void OnEvent(Event e)
{
if (e is WindowCloseEvent)
{
SaveGameState();
Logger.Info("Window closing");
}
}
WindowResizeEvent¶
Fired when the window is resized.
Example:
public override void OnEvent(Event e)
{
if (e is WindowResizeEvent resizeEvent)
{
Logger.Info($"Window resized to {resizeEvent.Width}x{resizeEvent.Height}");
UpdateCamera(resizeEvent.Width, resizeEvent.Height);
}
}
Keyboard Events¶
KeyPressedEvent¶
Fired when a key is pressed or held down.
Example:
public override void OnEvent(Event e)
{
if (e is KeyPressedEvent keyEvent)
{
if (keyEvent.KeyCode == Key.Space && !keyEvent.IsRepeat)
{
Player.Jump();
e.Handled = true;
}
if (keyEvent.KeyCode == Key.Escape)
{
OpenPauseMenu();
}
}
}
KeyReleasedEvent¶
Fired when a key is released.
Example:
public override void OnEvent(Event e)
{
if (e is KeyReleasedEvent keyEvent)
{
if (keyEvent.KeyCode == Key.W)
{
Player.StopMovingForward();
}
}
}
Mouse Events¶
MouseButtonPressedEvent¶
Fired when a mouse button is pressed.
Example:
public override void OnEvent(Event e)
{
if (e is MouseButtonPressedEvent mouseEvent)
{
if (mouseEvent.Button == MouseButton.Left)
{
var mousePos = Input.GetMousePosition();
ShootProjectile(mousePos);
}
if (mouseEvent.Button == MouseButton.Right)
{
OpenContextMenu();
}
}
}
MouseButtonReleasedEvent¶
Fired when a mouse button is released.
MouseMovedEvent¶
Fired when the mouse cursor moves.
Example:
public override void OnEvent(Event e)
{
if (e is MouseMovedEvent moveEvent)
{
// Update crosshair position
_crosshairPos = new Vector2(moveEvent.X, moveEvent.Y);
// First-person camera look
_camera.Rotate(moveEvent.X * _sensitivity, moveEvent.Y * _sensitivity);
}
}
MouseScrolledEvent¶
Fired when the mouse wheel is scrolled.
public class MouseScrolledEvent : Event
{
public float OffsetX { get; }
public float OffsetY { get; }
}
Example:
public override void OnEvent(Event e)
{
if (e is MouseScrolledEvent scrollEvent)
{
// Zoom camera
_cameraZoom += scrollEvent.OffsetY * 0.1f;
_cameraZoom = Math.Clamp(_cameraZoom, 0.5f, 5.0f);
}
}
Gamepad Events¶
GamepadConnectedEvent¶
Fired when a gamepad is connected.
public class GamepadConnectedEvent : Event
{
public int GamepadIndex { get; }
public string GamepadName { get; }
}
Example:
public override void OnEvent(Event e)
{
if (e is GamepadConnectedEvent gamepadEvent)
{
Logger.Info($"Gamepad connected: {gamepadEvent.GamepadName} at index {gamepadEvent.GamepadIndex}");
ShowControllerUI();
}
}
GamepadDisconnectedEvent¶
Fired when a gamepad is disconnected.
public class GamepadDisconnectedEvent : Event
{
public int GamepadIndex { get; }
public string GamepadName { get; }
}
GamepadButtonPressedEvent¶
Fired when a gamepad button is pressed.
public class GamepadButtonPressedEvent : Event
{
public ButtonName Button { get; }
public int GamepadIndex { get; }
}
Example:
public override void OnEvent(Event e)
{
if (e is GamepadButtonPressedEvent gamepadEvent)
{
switch (gamepadEvent.Button)
{
case ButtonName.A:
Player.Jump();
break;
case ButtonName.B:
Player.Dodge();
break;
case ButtonName.Start:
OpenPauseMenu();
break;
}
}
}
GamepadButtonReleasedEvent¶
Fired when a gamepad button is released.
public class GamepadButtonReleasedEvent : Event
{
public ButtonName Button { get; }
public int GamepadIndex { get; }
}
Event Handling Patterns¶
Pattern 1: Type Checking with is¶
public override void OnEvent(Event e)
{
if (e is KeyPressedEvent keyEvent)
{
HandleKeyPress(keyEvent);
}
else if (e is MouseButtonPressedEvent mouseEvent)
{
HandleMouseClick(mouseEvent);
}
}
Pattern 2: Switch on Event Type¶
public override void OnEvent(Event e)
{
switch (e.GetEventType())
{
case EventType.KeyPressed:
HandleKeyPress((KeyPressedEvent)e);
break;
case EventType.MouseButtonPressed:
HandleMouseClick((MouseButtonPressedEvent)e);
break;
}
}
Pattern 3: Dedicated Handler Methods¶
public override void OnEvent(Event e)
{
if (e is KeyPressedEvent keyEvent)
OnKeyPressed(keyEvent);
else if (e is MouseMovedEvent moveEvent)
OnMouseMoved(moveEvent);
}
private void OnKeyPressed(KeyPressedEvent e)
{
// Handle key press
}
private void OnMouseMoved(MouseMovedEvent e)
{
// Handle mouse movement
}
Event Propagation¶
Events are dispatched to layers in reverse order (top to bottom):
┌──────────────┐
│ UI Layer │ ← Event received first
├──────────────┤
│ Game Layer │ ← Event received second (if not handled)
└──────────────┘
Stopping Propagation¶
Set e.Handled = true to prevent the event from reaching lower layers:
class UILayer : Layer
{
public override void OnEvent(Event e)
{
if (e is MouseButtonPressedEvent mouseEvent)
{
if (IsMouseOverButton(mouseEvent))
{
OnButtonClicked();
e.Handled = true; // Don't pass to game layer
}
}
}
}
class GameLayer : Layer
{
public override void OnEvent(Event e)
{
if (e is MouseButtonPressedEvent mouseEvent)
{
// Only executed if UI didn't handle it
if (!e.Handled)
{
SelectUnit(mouseEvent);
}
}
}
}
Complete Examples¶
Example 1: Player Controller¶
class PlayerControllerLayer : Layer
{
private Entity _player;
private float _moveSpeed = 5f;
public override void OnEvent(Event e)
{
if (e is KeyPressedEvent keyEvent)
{
HandleKeyPress(keyEvent);
}
else if (e is GamepadButtonPressedEvent gamepadEvent)
{
HandleGamepadButton(gamepadEvent);
}
}
private void HandleKeyPress(KeyPressedEvent e)
{
switch (e.KeyCode)
{
case Key.Space:
if (!e.IsRepeat)
Jump();
break;
case Key.ShiftLeft:
_moveSpeed = 10f; // Sprint
break;
}
}
private void HandleGamepadButton(GamepadButtonPressedEvent e)
{
if (e.Button == ButtonName.A)
{
Jump();
}
}
private void Jump()
{
var scene = Application.Instance.ActiveScene;
var rb = scene.Registry.GetComponent<RigidbodyComponent>(_player);
if (rb != null)
{
rb.AddImpulse(new Vector3(0, 10, 0));
}
}
}
Example 2: Pause Menu¶
class PauseMenuLayer : Layer
{
private bool _isPaused = false;
public override void OnEvent(Event e)
{
if (e is KeyPressedEvent keyEvent)
{
if (keyEvent.KeyCode == Key.Escape)
{
TogglePause();
e.Handled = true;
}
}
if (_isPaused)
{
// Block all input when paused
e.Handled = true;
}
}
private void TogglePause()
{
_isPaused = !_isPaused;
Time.TimeScale = _isPaused ? 0f : 1f;
Logger.Info(_isPaused ? "Game paused" : "Game resumed");
}
public override void OnRender()
{
if (_isPaused)
{
RenderPauseMenu();
}
}
}
Example 3: Camera Controller¶
class CameraControllerLayer : Layer
{
private Entity _camera;
private float _rotationX = 0f;
private float _rotationY = 0f;
private float _sensitivity = 0.1f;
public override void OnEvent(Event e)
{
if (e is MouseMovedEvent moveEvent)
{
HandleMouseLook(moveEvent);
}
else if (e is MouseScrolledEvent scrollEvent)
{
HandleZoom(scrollEvent);
}
}
private void HandleMouseLook(MouseMovedEvent e)
{
var mouseDelta = Input.GetMouseDelta();
_rotationY += mouseDelta.X * _sensitivity;
_rotationX -= mouseDelta.Y * _sensitivity;
_rotationX = Math.Clamp(_rotationX, -89f, 89f);
var scene = Application.Instance.ActiveScene;
var transform = scene.Registry.GetComponent<TransformComponent>(_camera);
transform.Rotation = new Vector3(_rotationX, _rotationY, 0);
}
private void HandleZoom(MouseScrolledEvent e)
{
var scene = Application.Instance.ActiveScene;
var camera = scene.Registry.GetComponent<Camera3DComponent>(_camera);
camera.FieldOfView -= e.OffsetY * 2f;
camera.FieldOfView = Math.Clamp(camera.FieldOfView, 20f, 120f);
}
}
Best Practices¶
✅ Do's¶
- Use
e.Handled = trueto stop propagation - Check
!e.Handledbefore processing in lower layers - Use type-safe pattern matching with
is - Create focused event handlers
- Handle gamepad disconnect gracefully
❌ Don'ts¶
- Don't perform heavy computation in event handlers
- Don't modify the event object (except
Handled) - Don't forget to check for null when casting
- Don't block the event dispatch thread
Performance Tips¶
- Early exit - Check event type before casting
- Minimize allocations - Reuse event objects where possible
- Batch processing - Handle similar events together
- Profile handlers - Identify slow event processing
See Also¶
- Input Handling - Input system documentation
- Layers - Layer system
- Application - Application lifecycle