Input Handling¶
The Input system in Evoker Engine provides comprehensive input handling for keyboard, mouse, and gamepad/controller input with both event-based and polling-based approaches.
Overview¶
The input system supports:
- Keyboard: Key press/release detection and polling
- Mouse: Button clicks, movement, and scroll wheel
- Gamepad: Button input, analog sticks, triggers, and vibration
- Hot-plug: Automatic gamepad connect/disconnect detection
Input Polling (Input Class)¶
Poll input state at any time during update:
public static class Input
{
// Keyboard
public static bool IsKeyPressed(Key key);
// Mouse
public static bool IsMouseButtonPressed(MouseButton button);
public static Vector2 GetMousePosition();
public static float GetMouseX();
public static float GetMouseY();
public static Vector2 GetMouseDelta();
public static Vector2 GetMouseScroll();
}
Keyboard Input¶
Key Polling¶
using EvokerEngine.Core;
using Silk.NET.Input;
public override void OnUpdate(float deltaTime)
{
// Check if key is pressed
if (Input.IsKeyPressed(Key.W))
{
MoveForward(deltaTime);
}
if (Input.IsKeyPressed(Key.Space))
{
Jump();
}
// Multiple keys
if (Input.IsKeyPressed(Key.ShiftLeft) && Input.IsKeyPressed(Key.W))
{
Sprint(deltaTime);
}
}
Key Events¶
public override void OnEvent(Event e)
{
if (e is KeyPressedEvent keyEvent)
{
// Triggered once when key is pressed
if (keyEvent.KeyCode == Key.E)
{
OpenInventory();
}
// Check for key repeat (holding key down)
if (keyEvent.KeyCode == Key.R && !keyEvent.IsRepeat)
{
Reload(); // Only trigger once, not while held
}
}
else if (e is KeyReleasedEvent releaseEvent)
{
// Triggered when key is released
if (releaseEvent.KeyCode == Key.W)
{
StopMoving();
}
}
}
Common Keys¶
// Arrow keys
Key.Up, Key.Down, Key.Left, Key.Right
// WASD
Key.W, Key.A, Key.S, Key.D
// Function keys
Key.F1, Key.F2, ..., Key.F12
// Modifiers
Key.ShiftLeft, Key.ShiftRight
Key.ControlLeft, Key.ControlRight
Key.AltLeft, Key.AltRight
// Special keys
Key.Space, Key.Enter, Key.Escape, Key.Tab
Key.Backspace, Key.Delete
// Number keys
Key.Number0, Key.Number1, ..., Key.Number9
Mouse Input¶
Mouse Buttons¶
public override void OnUpdate(float deltaTime)
{
// Check mouse button state
if (Input.IsMouseButtonPressed(MouseButton.Left))
{
FireWeapon();
}
if (Input.IsMouseButtonPressed(MouseButton.Right))
{
Aim();
}
}
public override void OnEvent(Event e)
{
if (e is MouseButtonPressedEvent mouseEvent)
{
if (mouseEvent.Button == MouseButton.Left)
{
OnLeftClick();
}
else if (mouseEvent.Button == MouseButton.Right)
{
OnRightClick();
}
else if (mouseEvent.Button == MouseButton.Middle)
{
OnMiddleClick();
}
}
}
Mouse Position¶
public override void OnUpdate(float deltaTime)
{
// Get current mouse position
var mousePos = Input.GetMousePosition();
UpdateCrosshair(mousePos);
// Separate X and Y
float x = Input.GetMouseX();
float y = Input.GetMouseY();
}
Mouse Movement¶
public override void OnUpdate(float deltaTime)
{
// Get mouse delta (movement since last frame)
var delta = Input.GetMouseDelta();
// First-person camera look
_cameraYaw += delta.X * _sensitivity;
_cameraPitch -= delta.Y * _sensitivity;
_cameraPitch = Math.Clamp(_cameraPitch, -89f, 89f);
}
public override void OnEvent(Event e)
{
if (e is MouseMovedEvent moveEvent)
{
// Absolute position
UpdateTooltip(new Vector2(moveEvent.X, moveEvent.Y));
}
}
Mouse Scroll¶
public override void OnUpdate(float deltaTime)
{
var scroll = Input.GetMouseScroll();
if (scroll.Y != 0)
{
// Zoom camera
_cameraZoom += scroll.Y * 0.1f;
}
}
public override void OnEvent(Event e)
{
if (e is MouseScrolledEvent scrollEvent)
{
// Vertical scroll
if (scrollEvent.OffsetY > 0)
ZoomIn();
else if (scrollEvent.OffsetY < 0)
ZoomOut();
// Horizontal scroll (if supported)
if (scrollEvent.OffsetX != 0)
PanHorizontally(scrollEvent.OffsetX);
}
}
Gamepad Input¶
Button Input¶
using EvokerEngine.Core;
using Silk.NET.Input;
public override void OnUpdate(float deltaTime)
{
// Check if gamepad is connected
if (!GamepadInput.IsGamepadConnected(0))
return;
// Check button state
if (GamepadInput.IsButtonPressed(ButtonName.A))
{
Jump();
}
if (GamepadInput.IsButtonPressed(ButtonName.X))
{
Attack();
}
}
public override void OnEvent(Event e)
{
if (e is GamepadButtonPressedEvent gamepadEvent)
{
switch (gamepadEvent.Button)
{
case ButtonName.A: // Xbox A / PlayStation Cross
Jump();
break;
case ButtonName.B: // Xbox B / PlayStation Circle
Dodge();
break;
case ButtonName.Start:
OpenPauseMenu();
break;
}
}
}
Analog Sticks¶
public override void OnUpdate(float deltaTime)
{
if (!GamepadInput.IsGamepadConnected(0))
return;
// Left stick - Movement
var leftStick = GamepadInput.GetLeftStick();
if (leftStick.Length() > 0.1f) // Deadzone
{
var moveDir = new Vector3(leftStick.X, 0, -leftStick.Y);
MovePlayer(moveDir * deltaTime);
}
// Right stick - Camera
var rightStick = GamepadInput.GetRightStick();
if (rightStick.Length() > 0.1f) // Deadzone
{
_cameraYaw += rightStick.X * _sensitivity * deltaTime;
_cameraPitch -= rightStick.Y * _sensitivity * deltaTime;
}
}
Triggers¶
public override void OnUpdate(float deltaTime)
{
if (!GamepadInput.IsGamepadConnected(0))
return;
// Left trigger - Aim
float leftTrigger = GamepadInput.GetLeftTrigger();
if (leftTrigger > 0.1f)
{
SetAimAmount(leftTrigger); // 0.0 to 1.0
}
// Right trigger - Shoot
float rightTrigger = GamepadInput.GetRightTrigger();
if (rightTrigger > 0.5f) // Half-press threshold
{
FireWeapon(rightTrigger);
}
}
D-Pad¶
public override void OnUpdate(float deltaTime)
{
if (!GamepadInput.IsGamepadConnected(0))
return;
// D-pad navigation
if (GamepadInput.IsDPadUpPressed())
NavigateMenuUp();
if (GamepadInput.IsDPadDownPressed())
NavigateMenuDown();
if (GamepadInput.IsDPadLeftPressed())
NavigateMenuLeft();
if (GamepadInput.IsDPadRightPressed())
NavigateMenuRight();
}
Vibration/Rumble¶
// Set vibration on both motors (0.0 to 1.0)
GamepadInput.SetVibration(0.5f, 0.5f);
// Strong rumble
GamepadInput.SetVibration(1.0f, 1.0f);
// Light rumble (high-frequency motor only)
GamepadInput.SetVibration(0.0f, 0.3f);
// Stop vibration
GamepadInput.StopVibration();
// Timed vibration
GamepadInput.SetVibration(0.8f, 0.8f);
await Task.Delay(500); // 0.5 seconds
GamepadInput.StopVibration();
Gamepad Connection Events¶
public override void OnEvent(Event e)
{
if (e is GamepadConnectedEvent connectedEvent)
{
Logger.Info($"Gamepad connected: {connectedEvent.GamepadName} at index {connectedEvent.GamepadIndex}");
ShowControllerUI();
}
else if (e is GamepadDisconnectedEvent disconnectedEvent)
{
Logger.Info($"Gamepad disconnected: {disconnectedEvent.GamepadName}");
PauseGame();
ShowReconnectMessage();
}
}
Complete Examples¶
Example 1: FPS Controller¶
public class FPSController : Layer
{
private Entity _player;
private float _moveSpeed = 5f;
private float _lookSensitivity = 0.1f;
private float _yaw = 0f;
private float _pitch = 0f;
public override void OnUpdate(float deltaTime)
{
var scene = Application.Instance.ActiveScene;
var transform = scene.Registry.GetComponent<TransformComponent>(_player);
// Calculate forward and right vectors
var forward = new Vector3(
MathF.Sin(_yaw * MathHelper.Deg2Rad),
0,
MathF.Cos(_yaw * MathHelper.Deg2Rad)
);
var right = Vector3.Cross(forward, Vector3.UnitY);
// Keyboard movement
var movement = Vector3.Zero;
if (Input.IsKeyPressed(Key.W)) movement += forward;
if (Input.IsKeyPressed(Key.S)) movement -= forward;
if (Input.IsKeyPressed(Key.A)) movement -= right;
if (Input.IsKeyPressed(Key.D)) movement += right;
if (movement.Length() > 0)
{
movement = Vector3.Normalize(movement);
transform.Position += movement * _moveSpeed * deltaTime;
}
// Mouse look
var mouseDelta = Input.GetMouseDelta();
_yaw += mouseDelta.X * _lookSensitivity;
_pitch -= mouseDelta.Y * _lookSensitivity;
_pitch = Math.Clamp(_pitch, -89f, 89f);
transform.Rotation = new Vector3(_pitch, _yaw, 0);
// Gamepad support
if (GamepadInput.IsGamepadConnected(0))
{
// Movement with left stick
var leftStick = GamepadInput.GetLeftStick();
if (leftStick.Length() > 0.1f)
{
var gamepadMove = forward * -leftStick.Y + right * leftStick.X;
transform.Position += gamepadMove * _moveSpeed * deltaTime;
}
// Look with right stick
var rightStick = GamepadInput.GetRightStick();
if (rightStick.Length() > 0.1f)
{
_yaw += rightStick.X * _lookSensitivity * 60f * deltaTime;
_pitch -= rightStick.Y * _lookSensitivity * 60f * deltaTime;
_pitch = Math.Clamp(_pitch, -89f, 89f);
}
}
}
public override void OnEvent(Event e)
{
// Jump
if (e is KeyPressedEvent keyEvent)
{
if (keyEvent.KeyCode == Key.Space)
Jump();
}
else if (e is GamepadButtonPressedEvent gamepadEvent)
{
if (gamepadEvent.Button == ButtonName.A)
Jump();
}
// Shoot
if (e is MouseButtonPressedEvent mouseEvent)
{
if (mouseEvent.Button == MouseButton.Left)
Shoot();
}
if (GamepadInput.GetRightTrigger() > 0.5f)
{
Shoot();
}
}
private void Jump() { /* ... */ }
private void Shoot() { /* ... */ }
}
Example 2: 2D Platformer¶
public class PlatformerController : Layer
{
private Entity _player;
private Vector3 _velocity = Vector3.Zero;
private bool _isGrounded = false;
private float _jumpForce = 10f;
private float _moveSpeed = 5f;
public override void OnUpdate(float deltaTime)
{
var scene = Application.Instance.ActiveScene;
var transform = scene.Registry.GetComponent<TransformComponent>(_player);
// Horizontal movement (keyboard)
float moveInput = 0f;
if (Input.IsKeyPressed(Key.A)) moveInput -= 1f;
if (Input.IsKeyPressed(Key.D)) moveInput += 1f;
// Gamepad support
if (GamepadInput.IsGamepadConnected(0))
{
var leftStick = GamepadInput.GetLeftStick();
moveInput = leftStick.X;
}
_velocity.X = moveInput * _moveSpeed;
// Apply gravity
if (!_isGrounded)
{
_velocity.Y -= 20f * deltaTime;
}
// Apply movement
transform.Position += _velocity * deltaTime;
// Simple ground check
if (transform.Position.Y <= 0)
{
transform.Position = new Vector3(transform.Position.X, 0, 0);
_velocity.Y = 0;
_isGrounded = true;
}
else
{
_isGrounded = false;
}
}
public override void OnEvent(Event e)
{
// Jump (keyboard)
if (e is KeyPressedEvent keyEvent)
{
if (keyEvent.KeyCode == Key.Space && _isGrounded)
{
_velocity.Y = _jumpForce;
}
}
// Jump (gamepad)
if (e is GamepadButtonPressedEvent gamepadEvent)
{
if (gamepadEvent.Button == ButtonName.A && _isGrounded)
{
_velocity.Y = _jumpForce;
GamepadInput.SetVibration(0.3f, 0.3f); // Rumble on jump
Task.Delay(100).ContinueWith(_ => GamepadInput.StopVibration());
}
}
}
}
Best Practices¶
✅ Do's¶
- Use polling for continuous input (movement)
- Use events for discrete actions (jumping, menu navigation)
- Implement deadzone for analog sticks (0.1-0.2)
- Support both keyboard/mouse and gamepad
- Stop gamepad vibration after use
❌ Don'ts¶
- Don't query input in OnEvent (use OnUpdate instead)
- Don't forget to check gamepad connection
- Don't hardcode controls (make them rebindable)
- Don't forget to clamp camera pitch
Input Remapping Example¶
public class InputManager
{
private Dictionary<string, Key> _keyBindings = new()
{
{ "forward", Key.W },
{ "back", Key.S },
{ "left", Key.A },
{ "right", Key.D },
{ "jump", Key.Space }
};
public bool IsActionPressed(string action)
{
if (_keyBindings.TryGetValue(action, out var key))
{
return Input.IsKeyPressed(key);
}
return false;
}
public void RebindKey(string action, Key newKey)
{
_keyBindings[action] = newKey;
}
}
// Usage
if (_inputManager.IsActionPressed("jump"))
{
Jump();
}