Skip to content

Reference: Input System

The @rbx/input package provides a unified input abstraction for keyboard, gamepad, and touch controls.

Installation

pnpm add @rbx/input

Quick Start

import { initInputManager, getMovementState, isActionActive, registerAction } from "@rbx/input";

// Initialize on client
initInputManager();

// Check movement every frame
RunService.Heartbeat.Connect(() => {
  const movement = getMovementState();

  if (movement.isMoving) {
    character.move(movement.direction);
  }

  if (isActionActive("jump")) {
    character.jump();
  }
});

Device Detection

Automatically detect and respond to input device changes.

import { getCurrentDevice, onDeviceChange, getPlatformInfo } from "@rbx/input";

// Get current device type
const device = getCurrentDevice(); // "keyboard" | "gamepad" | "touch"

// React to device changes
onDeviceChange((newDevice) => {
  if (newDevice === "gamepad") {
    showGamepadPrompts();
  } else if (newDevice === "touch") {
    showTouchControls();
  } else {
    showKeyboardPrompts();
  }
});

// Get detailed platform info
const platform = getPlatformInfo();
// { platform: "Windows", isMobile: false, hasGamepad: true, hasTouchScreen: false }

Device Types

Type Description
keyboard Keyboard and mouse input (desktop)
gamepad Xbox/PlayStation controller
touch Touch screen (mobile/tablet)

Action System

Define named actions that can be triggered by various inputs.

Registering Actions

import { registerAction, registerCommonActions } from "@rbx/input";

// Register a single action
registerAction({
  name: "attack",
  displayName: "Attack",
  category: "combat",
});

registerAction({
  name: "interact",
  displayName: "Interact",
  category: "gameplay",
  cooldown: 0.5, // 500ms cooldown
});

// Register common actions (jump, sprint, crouch, interact)
registerCommonActions();

Querying Action State

import { isActionActive, isActionJustPressed, getActionValue, getActionState } from "@rbx/input";

// Check if action is currently held
if (isActionActive("sprint")) {
  player.setSprinting(true);
}

// Check if action was just pressed this frame
if (isActionJustPressed("jump")) {
  player.jump();
}

// Get analog value (0-1 for triggers, buttons)
const throttle = getActionValue("accelerate");
car.setThrottle(throttle);

// Get full action state
const state = getActionState("attack");
// { active: true, lastTriggered: 123.456, value: 1, delta: 0 }

Action Callbacks

import { onAction, offAction } from "@rbx/input";

// Subscribe to action events
const unsubscribe = onAction("jump", (actionName, state) => {
  if (state.active) {
    print("Jump pressed!");
  } else {
    print("Jump released!");
  }
});

// Later: unsubscribe
unsubscribe();

// Or manually
offAction("jump", callback);

Input Bindings

Map physical inputs to actions.

Default Bindings

import { initDefaultBindings } from "@rbx/input";

// Set up default bindings for common actions
initDefaultBindings();

// Default keyboard bindings:
// - WASD / Arrow keys → movement
// - Space → jump
// - Shift → sprint
// - E → interact
// - Escape → pause

// Default gamepad bindings:
// - Left stick → movement
// - A button → jump
// - Left trigger → sprint
// - X button → interact
// - Start → pause

Custom Bindings

import { addBinding, removeBinding, clearBindings, getBindingsForAction } from "@rbx/input";

// Add a keyboard binding
addBinding({
  action: "attack",
  primary: { type: "mouse", button: "MouseButton1" },
  secondary: { type: "key", key: "F" },
  priority: 1,
});

// Add a gamepad binding
addBinding({
  action: "attack",
  primary: { type: "gamepad", button: "ButtonR2" },
  priority: 1,
});

// Get all bindings for an action
const bindings = getBindingsForAction("attack");

// Remove specific binding
removeBinding("attack", { type: "key", key: "F" });

// Clear all custom bindings
clearBindings();

Input Sources

// Keyboard keys
{ type: "key", key: "W" }
{ type: "key", key: "Space" }
{ type: "key", key: "LeftShift" }

// Mouse buttons
{ type: "mouse", button: "MouseButton1" }  // Left click
{ type: "mouse", button: "MouseButton2" }  // Right click

// Gamepad buttons
{ type: "gamepad", button: "ButtonA" }
{ type: "gamepad", button: "ButtonX" }
{ type: "gamepad", button: "ButtonR1" }    // Right bumper
{ type: "gamepad", button: "ButtonR2" }    // Right trigger

// Gamepad axes
{ type: "gamepad", axis: "Thumbstick1" }   // Left stick
{ type: "gamepad", axis: "Thumbstick2" }   // Right stick

Movement State

Get unified movement input regardless of device.

import { getMovementState } from "@rbx/input";

RunService.Heartbeat.Connect(() => {
  const movement = getMovementState();

  // movement.direction: { x: number, y: number } - normalized
  // movement.magnitude: number (0-1)
  // movement.isMoving: boolean
  // movement.raw: { x: number, y: number } - unnormalized

  if (movement.isMoving) {
    const moveVector = new Vector3(movement.direction.x, 0, movement.direction.y);
    humanoid: Move(moveVector.mul(movement.magnitude));
  }
});

Movement Sources

The movement state automatically combines input from:

  • Keyboard: WASD and arrow keys
  • Gamepad: Left thumbstick
  • Touch: Virtual joystick (when implemented)

Lifecycle

import { initInputManager, shutdownInputManager, initDeviceDetection } from "@rbx/input";

// Initialize (call once on client startup)
initInputManager();

// Or initialize components separately
initDeviceDetection();
initDefaultBindings();
registerCommonActions();

// Cleanup (call on client shutdown)
shutdownInputManager();

Display Names

Get device-appropriate display names for bindings.

import { getBindingDisplayName, getActionDisplayHint } from "@rbx/input";

// Get display name for current device
const hint = getActionDisplayHint("jump");
// Keyboard: "Space"
// Gamepad: "A"
// Touch: "Jump"

// Use in UI
promptLabel.Text = `Press ${hint} to jump`;

Common Actions

Pre-defined actions for typical game controls:

import { CommonActions } from "@rbx/input";

// CommonActions includes:
// - moveForward, moveBackward, moveLeft, moveRight
// - jump, sprint, crouch
// - interact, attack, secondary
// - pause, inventory

Best Practices

1. Initialize Early

// In your client entry point
import { initInputManager } from "@rbx/input";

initInputManager();

2. Use Actions, Not Raw Input

// Good - device agnostic
if (isActionActive("jump")) {
  player.jump();
}

// Avoid - device specific
if (UserInputService.IsKeyDown(Enum.KeyCode.Space)) {
  player.jump();
}

3. Handle Device Changes

onDeviceChange((device) => {
  // Update UI prompts
  updateButtonPrompts(device);

  // Show/hide touch controls
  touchControls.Visible = device === "touch";
});

4. Support Rebinding

// Let players customize bindings
function rebindAction(action: string, newInput: InputSource) {
  removeBinding(action, getCurrentBinding(action).primary);
  addBinding({ action, primary: newInput, priority: 10 });
  saveBindingsToDataStore();
}

Integration Example

import {
  initInputManager,
  getMovementState,
  isActionActive,
  isActionJustPressed,
  onDeviceChange,
  registerAction,
} from "@rbx/input";

// Initialize
initInputManager();

// Register game-specific actions
registerAction({ name: "ability1", displayName: "Primary Ability", category: "combat" });
registerAction({ name: "ability2", displayName: "Secondary Ability", category: "combat" });
registerAction({ name: "ultimate", displayName: "Ultimate", category: "combat", cooldown: 1 });

// Handle device changes
onDeviceChange((device) => {
  gui.updatePrompts(device);
  touchControls.Visible = device === "touch";
});

// Game loop
RunService.Heartbeat.Connect((dt) => {
  // Movement
  const movement = getMovementState();
  if (movement.isMoving) {
    character.move(movement.direction, movement.magnitude);
  }

  // Actions
  if (isActionJustPressed("jump") && character.canJump()) {
    character.jump();
  }

  if (isActionActive("sprint") && character.canSprint()) {
    character.sprint();
  }

  if (isActionJustPressed("ability1")) {
    character.useAbility(1);
  }

  if (isActionJustPressed("ultimate") && character.hasUltimate()) {
    character.useUltimate();
  }
});