Skip to content

Input

An action is a named input like "Jump", "Interact", or "MoveForward", defined once in project settings and bound to whatever keys you like. Scripts react to the action, not to the key, so controls stay data.

There are three ways to read input in a script.

  • Bind to an action with an SInputAction or SInputAxis field. You pick the action from a dropdown in the inspector and subscribe to its events. This is the usual choice.
  • Poll state through World.Input. You ask each frame whether a key or action is down. Good for continuous input like movement.
  • React to raw events with OnInput. The engine hands you each key and mouse event as it happens, before any action mapping.

All three work only in play mode, for the viewport that has input focus.

A fourth thing shapes all of them: mapping contexts, layers you push to change what input means right now. A pause menu pushes one so gameplay stops hearing the movement keys. See Mapping contexts.

Actions live in File > Settings > Engine > Input, and are saved to the project’s Config/InputSettings.json. Add an action, name it, then add the bindings that feed it.

Action fieldWhat it does
NameThe name scripts refer to.
TypeDigital is on or off. Axis1D produces a value. Axis2D produces a value per channel, for a movement stick.
Runs In UIKeep firing while the viewport is in UI input mode. Superseded by mapping contexts, but still honoured when no context is pushed.
Dead ZoneInput below this magnitude reads as zero, and the rest is rescaled so full deflection still reaches its old value. Applied radially on Axis2D.
SensitivityMultiplies the value. The usual home for mouse look sensitivity.
InvertFlips the sign.
Hold TimeSeconds the action must stay down before it counts as held. 0 means held from the first frame.
Tap TimeA press released within this many seconds also reports a tap.
Binding fieldWhat it does
KeyThe key or mouse button, with an optional Ctrl, Shift, or Alt chord.
ScaleWhat this binding contributes while held, for example +1 and -1 for a pair. Also multiplies a mouse source.
SourceKey reads the key above. MouseX, MouseY, and MouseWheel read this frame’s motion instead, and ignore the key.
ChannelWhich channel of an Axis2D action this binding drives, X or Y. Ignored by the other types.

So a movement stick is one Axis2D action with four key bindings, A at -1 on X, D at +1 on X, S at -1 on Y, W at +1 on Y. Mouse look is an Axis1D action with a single MouseX binding and sensitivity to taste.

Actions are keyboard and mouse only. Gamepads are not supported yet.

Declare an SInputAction (digital) or SInputAxis (analog) field, mark it [Property], and the inspector shows a dropdown of the project’s actions. The field stores the action’s name, so rebinding a key never touches the script.

public sealed class Player : EntityScript
{
[Property] public SInputAction Jump = new("Jump");
[Property] public SInputAxis Move = new("MoveForward");
public override void OnReady()
{
Jump.Pressed += () => Launch();
Jump.Held += Seconds => ChargeJump(Seconds);
}
public override void OnUpdate(float DeltaTime)
{
Transform.Translate(Transform.GetForward() * (Move.Value * 5.0f * DeltaTime));
}
}

The constructor argument is just the default shown in the inspector, and you can leave it out. Declaring a binding is enough on its own, the entity gets its input component automatically.

Bindings are updated once per frame, before OnUpdate, so a handler and a poll in the same frame agree.

SInputAction members.

MemberWhat it is
PressedEvent, raised on the frame the action goes down.
ReleasedEvent, raised on the frame it comes up.
HeldEvent, raised every frame once it has been down for Hold Time. Carries seconds held.
TappedEvent, raised when a press shorter than Tap Time is released.
IsDown / WasPressed / WasReleased / IsHeldThe same state, polled.
HeldTimeSeconds the current press has lasted, 0 while up.

SInputAxis members.

MemberWhat it is
ChangedEvent, raised when the value differs from last frame, including the frame it returns to zero.
Changed2DThe same, for both channels of an Axis2D action.
ValueThe value this frame, the X channel of an Axis2D action.
Value2DBoth channels of an Axis2D action.
IsMovingtrue while the axis is off zero.

Both also expose Name, which you can assign at runtime to point the binding at a different action, and IsBound, which is false when the name matches no action in the settings.

Polling asks “is this down right now?” each frame, through World.Input. It is scoped to the world, not to one entity, so it needs no component and no setup.

public sealed class Player : EntityScript
{
public override void OnUpdate(float DeltaTime)
{
if (World.Input.IsKeyDown(EKey.W))
{
Transform.Translate(Transform.GetForward() * (5.0f * DeltaTime));
}
Transform.AddYaw(World.Input.MouseDelta.X * 0.1f);
}
}
MemberReturns
IsActionDown(name) / WasActionPressed(name) / WasActionReleased(name)bool
IsActionHeld(name)bool, down and past the action’s Hold Time
WasActionTapped(name)bool, on the frame a short press was released
GetActionHeldTime(name)Seconds the current press has lasted
GetActionAxis(name) / GetActionAxis2D(name)The action’s value, and both channels
GetActionState(name)The whole FInputActionState at once, if you want several fields
GetAxis(positive, negative)+1, -1, or 0 from two digital actions
IsKeyDown(EKey) / WasKeyPressed(EKey) / WasKeyReleased(EKey)bool
IsMouseButtonDown(EMouseKey) / WasMouseButtonPressed / WasMouseButtonReleasedbool
MousePosition / MouseDeltaFVector2
MouseWheelfloat, this frame’s signed wheel amount
IsReceivingInputtrue only when this world has input focus

Keys and buttons are enum values, EKey.W, EKey.Space, EKey.LeftShift, EMouseKey.ButtonLeft, so a typo is a compile error rather than a query that silently never fires.

Everything here returns the neutral value when the world does not have input focus, so you rarely need to test IsReceivingInput first.

The raw key and mouse members read the device directly. They are not rebindable and every entity in the world sees the same values, so reach for an action first and keep these for debug keys and prototypes.

Override OnInput and the engine calls it once per keyboard or mouse event, before any action mapping. Use it for text entry, debug keys, and anything that should not go through an action.

public override void OnReady()
{
EnableInput();
}
public override void OnInput(SInputEvent Event)
{
if (Event.IsKeyDown(EKey.Space) && !Event.IsRepeat())
{
Jump();
}
else if (Event.Type == EInputEventType.MouseScroll)
{
Zoom((float)Event.Scroll);
}
}

The SInputEvent fields.

FieldMeaning
TypeKeyDown, KeyUp, MouseDown, MouseUp, MouseMove, or MouseScroll (EInputEventType).
DeviceKeyboard, Mouse, or None for move and scroll (EKeyDevice).
KeyThe EKey, when Device is Keyboard.
ButtonThe EMouseKey, when Device is Mouse.
FlagsShift, Ctrl, Alt, and Repeat, as EInputEventFlags bits. Read them with the helpers below.
MouseX / MouseYThe cursor position.
DeltaX / DeltaYCursor movement, on MouseMove.
ScrollThe signed wheel amount, on MouseScroll.

Helper methods save you unpacking any of that.

Helpertrue when
IsKeyDown(EKey) / IsKeyUp(EKey)This is a key down or up event for that key.
IsMouseDown(EMouseKey) / IsMouseUp(EMouseKey)The same for a mouse button.
IsShiftDown() / IsCtrlDown() / IsAltDown()That modifier was held.
IsRepeat()This KeyDown is an OS auto-repeat, not a fresh press.
IsKeyboard() / IsMouse()Which device the event came from.

OnInput only fires while the entity has an enabled input component, so call EnableInput() in OnReady. DisableInput() removes it again. Action bindings add it for you; polling through World.Input does not need it at all.

A mapping context is a named layer of actions that a world pushes onto a stack. It answers “what does input mean right now”: while the pause menu is up, the movement keys should do nothing and only the menu’s own actions should fire.

Author them in File > Settings > Engine > Input, beside the actions.

FieldWhat it does
NameThe name scripts push and pop.
ActionsThe actions this layer allows.
Block LowerWhen ticked, an action this layer does not list stops here instead of reaching whatever is underneath. Leave it off for a layer that only adds actions.
private void TogglePause()
{
if (World.Input.HasLayer("Menu"))
{
World.Input.PopLayer("Menu");
World.Paused = false;
}
else
{
World.Input.PushLayer("Menu"); // gameplay actions stop firing
World.Paused = true;
}
}
MemberWhat it does
PushLayer(name)Pushes the layer on top. Pushing one already on the stack moves it up rather than duplicating it, so one pop always removes it.
PopLayer(name)Removes it. Returns false if it was not on the stack.
HasLayer(name)Whether it is currently pushed.
ClearLayers()Removes all of them.

How the stack is read, from the top down:

  • The first layer that lists the action allows it.
  • A layer with Block Lower that does not list it stops there, and nothing underneath is consulted.
  • If no layer decides, the action fires.

So an empty stack changes nothing, which is why existing projects behave the same until they push their first layer. A layer whose name is not in the settings is skipped rather than blocking everything.

Layers are per world and are cleared when the world is torn down, so a layer left pushed at the end of a play session does not leak into the next one.

Runs In UI predates mapping contexts and still works: with no layer pushed, UI input mode blocks every action except the ones that opt in. A pushed layer takes precedence over it.

  • Anything a player triggers (jump, fire, interact, movement), define an action and bind to it. Players can rebind it, and the key never appears in your script.
  • Continuous state you check anyway, poll World.Input in OnUpdate.
  • Raw keys (debug shortcuts, text entry), use OnInput.
  • Changing what input means (menus, cutscenes, vehicles), push a mapping context rather than adding if checks to every handler.