Documentation

Learn Simulant

Everything you need to know to build games with Simulant

Input Axes

The InputAxis system is Simulant's abstraction for configurable input mappings. Instead of reading raw keyboard, mouse, or gamepad state directly, you define named axes that can be driven by any combination of devices. This makes it straightforward to support keyboard, mouse, and gamepad input with a single code path.

Overview

An InputAxis represents a single control input that produces a value between -1.0 and 1.0. Each axis has a name (e.g. "Horizontal", "Fire1", "MouseX") and can be backed by:

  • Keyboard keys (positive and negative)
  • Mouse buttons (positive and negative)
  • Mouse movement axes
  • Gamepad/joystick buttons (positive and negative)
  • Gamepad/joystick analog axes (sticks, triggers)
  • Gamepad/joystick hat (D-pad) positions

Multiple axes can share the same name. When reading a value by name, Simulant returns the strongest (highest absolute) value among all axes with that name. This lets you define separate keyboard and gamepad axes with the same name and have them "just work" without any conditional logic.

Creating Axes

Axes are created through the InputManager using new_axis(name). On creation the axis is non-functional; its type() is AXIS_TYPE_UNSET until you configure a source.

// In your Scene's on_start() or similar setup method:
auto horizontal = input->new_axis("Horizontal");
horizontal->set_positive_keyboard_key(KEYBOARD_CODE_D);
horizontal->set_negative_keyboard_key(KEYBOARD_CODE_A);

auto vertical = input->new_axis("Vertical");
vertical->set_positive_keyboard_key(KEYBOARD_CODE_S);
vertical->set_negative_keyboard_key(KEYBOARD_CODE_W);

auto fire = input->new_axis("Fire1");
fire->set_positive_keyboard_key(KEYBOARD_CODE_SPACE);

Each call to set_positive_* or set_negative_* also sets the axis type() automatically.

Gamepad Axes

For gamepad sticks and buttons you configure the axis similarly:

// Left stick X-axis
auto horizontal_js = input->new_axis("Horizontal");
horizontal_js->set_type(AXIS_TYPE_JOYSTICK_AXIS);
horizontal_js->set_joystick_axis(JOYSTICK_AXIS_XL);

// Left stick Y-axis
auto vertical_js = input->new_axis("Vertical");
vertical_js->set_type(AXIS_TYPE_JOYSTICK_AXIS);
vertical_js->set_joystick_axis(JOYSTICK_AXIS_YL);

// A button as Fire1
auto fire_js = input->new_axis("Fire1");
fire_js->set_type(AXIS_TYPE_JOYSTICK_BUTTON);
fire_js->set_positive_joystick_button(JOYSTICK_BUTTON_A);

Mouse Axes

Mouse movement can be captured as analog axes. This is useful for camera control or custom cursor movement.

auto mouse_x = input->new_axis("MouseX");
mouse_x->set_type(AXIS_TYPE_MOUSE_AXIS);
mouse_x->set_mouse_axis(MOUSE_AXIS_X);

auto mouse_y = input->new_axis("MouseY");
mouse_y->set_type(AXIS_TYPE_MOUSE_AXIS);
mouse_y->set_mouse_axis(MOUSE_AXIS_Y);

Mouse axis values represent the delta in pixels that the cursor moved since the previous frame, rather than a normalised -1.0 to 1.0 range.

Hat (D-Pad) Axes

Gamepad D-pad hat inputs can also be mapped to axes:

auto dpad_horizontal = input->new_axis("Horizontal");
dpad_horizontal->set_type(AXIS_TYPE_JOYSTICK_HAT);
dpad_horizontal->set_joystick_hat_axis(0, JOYSTICK_HAT_AXIS_X);

Reading Axis Values

Once axes are defined, you read their values by name through the InputManager:

float h = input->axis_value("Horizontal");
float v = input->axis_value("Vertical");

// Apply to movement
actor->translate(h * speed * dt, 0, v * speed * dt);

The returned value is the strongest (highest absolute) value among all axes sharing that name. Values are typically in the range -1.0 to 1.0, except for mouse movement axes which return pixel deltas.

Hard Values

For cases where you need a discrete -1, 0, or +1 value (e.g. menu navigation), use axis_value_hard():

int8_t direction = input->axis_value_hard("Horizontal");
// Returns -1, 0, or +1

This rounds the axis value with the dead zone applied, returning 0 for anything within the dead zone.

Detecting Presses and Releases

To detect when an axis transitions from inactive to active (or vice versa) use axis_was_pressed() and axis_was_released():

if (input->axis_was_pressed("Fire1")) {
    // Fire weapon
}

if (input->axis_was_released("Fire1")) {
    // Stop charging
}

These methods operate on the axis name and will fire true if any keyboard key, mouse button, or gamepad button that drives that axis was pressed or released this frame.

Digital Input Behaviour: Force and Return Speed

When an axis is driven by a digital input (keyboard key, mouse button, gamepad button), the value does not jump instantly from 0 to 1. Instead it ramps up and down over time, which provides smoother-feeling controls.

Force

The force property controls how quickly the axis value increases while a digital input is held. It is measured in units per second. The default is 3.0, meaning it takes approximately 1/3 of a second to reach 1.0.

auto fire = input->new_axis("Fire1");
fire->set_positive_keyboard_key(KEYBOARD_CODE_SPACE);
fire->set_force(6.0f); // Reaches 1.0 in ~1/6 of a second (snappier)

Return Speed

The return_speed property controls how quickly the axis value decays back to 0 after the input is released. The default is also 3.0.

fire->set_return_speed(1.5f); // Slower release

Setting force and return speed lets you tune the "feel" of digital inputs to be snappy or gradual depending on your game's needs.

Dead Zones

Analog inputs like gamepad thumbsticks produce small non-zero values even when the player is not touching them. To handle this jitter, each InputAxis has a dead_zone property.

The dead_zone is a value between 0.0 and 1.0. Inputs with an absolute value less than the dead zone are treated as zero. The default is 0.001f.

auto stick = input->new_axis("Horizontal");
stick->set_type(AXIS_TYPE_JOYSTICK_AXIS);
stick->set_joystick_axis(JOYSTICK_AXIS_XL);
stick->set_dead_zone(0.15f); // 15% dead zone

Dead Zone Behaviour

When retrieving an axis value you can choose how the dead zone is applied:

InputAxis* axis = input->new_axis("Test");
axis->set_joystick_axis(JOYSTICK_AXIS_XL);
axis->set_dead_zone(0.1f);

// RADIAL (default) - takes the combined X/Y magnitude into account.
// Best for thumbsticks where you want a circular dead zone.
float radial = axis->value(DEAD_ZONE_BEHAVIOUR_RADIAL);

// AXIAL - applies the dead zone independently per axis.
float axial = axis->value(DEAD_ZONE_BEHAVIOUR_AXIAL);

// NONE - no dead zone filtering at all.
float raw = axis->value(DEAD_ZONE_BEHAVIOUR_NONE);

For joystick axes the default behaviour is DEAD_ZONE_BEHAVIOUR_RADIAL. This means that if the stick is pushed diagonally just outside the dead zone circle, both axes will report a value. This produces more natural-feeling movement than an axial dead zone.

Radial dead zones require access to the linked axis (the X/Y counterpart). Simulant automatically determines the linked axis for common gamepad layouts so this works transparently.

Axis Inversion

You can invert an axis so that positive and negative inputs are swapped:

auto inverted_vertical = input->new_axis("CameraY");
inverted_vertical->set_type(AXIS_TYPE_JOYSTICK_AXIS);
inverted_vertical->set_joystick_axis(JOYSTICK_AXIS_YR);
inverted_vertical->set_inversed(true); // Flipped for "inverted Y" camera

Source Scoping

By default, axes listen to all devices of their type (all keyboards, all mice, all game controllers). You can scope an axis to a specific device:

// Listen only to keyboard ID 0
axis->set_keyboard_source(KeyboardID(0));

// Listen only to mouse ID 0
axis->set_mouse_source(MouseID(0));

// Listen only to game controller index 0
axis->set_joystick_source(GameControllerIndex(0));

The constants ALL_KEYBOARDS, ALL_MICE, and ALL_GAME_CONTROLLERS (all with an internal value of -1) represent the default "listen to all" behaviour.

Iterating and Destroying Axes

You can enumerate all axes or retrieve all axes with a given name:

// Get all axes named "Horizontal"
AxisList h_axes = input->axises("Horizontal");

// Iterate every defined axis
input->each_axis([](InputAxis* axis) {
    // inspect axis
});

// How many axes share this name?
std::size_t count = input->axis_count("Fire1");

To destroy axes:

// Destroy all axes with a given name
input->destroy_axises("Horizontal");

// Destroy a specific axis
input->destroy_axis(specific_axis);

Pre-defined Axes

The InputManager creates a default set of axes on construction. These cover common needs:

Name Keyboard Gamepad Mouse
Horizontal A/D Left stick X -
Vertical W/S Left stick Y -
Fire1 Space Button A (0) Left button (0)
Fire2 Left Ctrl Button B (1) Right button (1)
Start Enter Start button -
MouseX - - X delta
MouseY - - Y delta

You can extend or override these by creating additional axes with the same names.