Skip to content

Input

There are two ways to read input in a script:

  • React to events with OnInput. The engine hands you each key and mouse event as it happens. Best for discrete actions: jump, fire, open a menu.
  • Poll state with the Input component. You ask each frame whether a key or action is currently held. Best for continuous input: movement, holding to aim.

Both work only in play mode (a Game or Simulation world), for the viewport that has input focus.

Define OnInput and the engine calls it once per keyboard or mouse event. The event is an SInputEvent, and no Input component is required:

function Script:OnInput(Event: SInputEvent)
if Event.Type == "KeyDown" and Event.Key == "Space" then
self:Jump()
elseif Event.Type == "MouseScroll" then
self:Zoom(Event.Scroll)
end
end

The SInputEvent fields, all read-only:

FieldMeaning
Event.TypeThe event: "KeyDown", "KeyUp", "MouseDown", "MouseUp", "MouseMove", or "MouseScroll".
Event.KeyThe key or button, for example "Space", "A", "Left". Empty for move and scroll.
Event.Ctrl / Event.Shift / Event.AltModifier keys held with the event.
Event.RepeatOn KeyDown, true when it is an OS auto-repeat (a held key firing again).
Event.X / Event.YThe cursor position.
Event.DeltaX / Event.DeltaYCursor movement, on MouseMove.
Event.ScrollThe signed wheel amount, on MouseScroll.

The engine type behind the event:

enum class EInputEventType : uint8
{
KeyDown, KeyUp, MouseDown, MouseUp, MouseMove, MouseScroll
};
struct SInputEvent
{
EInputEventType Type;
SKey Key; // key or button, plus Ctrl / Shift / Alt modifiers
bool bRepeat; // OS auto-repeat (KeyDown only)
double MouseX, MouseY; // cursor position
double DeltaX, DeltaY; // mouse-move delta
double Scroll; // wheel delta
};

Polling asks “is this down right now?” each frame. It goes through the entity’s Input component, so it is scoped to the entity that owns it (the player’s pawn, usually). Opt in with self:EnableInput(), then query self.Input:

function Script:OnReady()
self:EnableInput()
end
function Script:OnUpdate(DeltaTime: number)
if self.Input:IsKeyDown("W") then
self.Transform:Translate(self.Transform:GetForward() * (5 * DeltaTime))
end
self.Transform:AddYaw(self.Input:GetMouseDeltaX() * 0.1)
end

self:DisableInput() removes the component again.

These are colon-called on self.Input:

MethodReturns
IsKeyDown(Key) / IsKeyPressed(Key) / IsKeyReleased(Key)boolean
IsActionDown(Name) / IsActionPressed(Name) / IsActionReleased(Name)boolean
GetActionAxis(Name)number
GetMouseDeltaX() / GetMouseDeltaY()Mouse movement this frame
GetMouseX() / GetMouseY()Cursor position
IsInputActive()true only when this world has input focus

Key names are single letters like "W", names like "Space", "Shift", "Ctrl", and mouse buttons "Left", "Right", "Middle".

You can also bind a callback to an action instead of polling it:

local Id = self.Input:BindAction("Fire", function() self:Shoot() end)
self.Input:UnbindAction(Id)

An action is a named binding like "Jump", "Fire", or "MoveX", defined in Tools > Input Actions. Reading an action by name (self.Input:IsActionPressed("Jump")) instead of a raw key lets players rebind controls and supports gamepads, so prefer actions for anything a player triggers.

The global Input table is separate from everything above and only controls the window’s mouse mode, capture it for mouse-look, release it for menus:

Input.SetMouseMode("Captured") -- "Captured", "Hidden", or "Normal"
  • Discrete actions (jump, shoot, interact, toggle a menu): use OnInput, or a bound action callback. You react exactly once per press.
  • Continuous state (movement, holding aim, charging): poll self.Input each frame in OnUpdate.