Skip to content
This repository was archived by the owner on Dec 14, 2021. It is now read-only.
David Lenaerts edited this page Aug 26, 2018 · 6 revisions

General Input

While you could handle user input using the native Javascript APIs, Helix provides a useful API to make things easier. It wraps a lot of the default boilerplate code you'd normally encounter in a 3D/game project and it makes mapping multiple input types to the same responses much easier. Initializing controllers can happen once, while the rest of the app can just listen to actions rather than polling and handling several input devices. Enter the HX.Input class.

The basic idea is this:

  • You enable one or more input types, such as HX.Mouse, HX.Keyboard, or HX.Touch.
  • For each input type, you map their inputs to so-called "actions". For example: both a keyboard's UpArrow keycode and the mouse's vertical motion may be mapped to a "moveY" action.
  • You listen to the actions and respond to them.

For example, initialising the input types and their associated actions goes as follows:

var input = new HX.Input();

var mouse = new HX.Mouse();
mouse.map(HX.Mouse.BUTTON_LEFT, "shoot_main");
mouse.map(HX.Mouse.BUTTON_RIGHT, "shoot_alt");
input.enable(mouse);

var keyboard = new HX.Keyboard();

// map using the new keyboard location mechanism
keyboard.map("Space", "shoot_main");
keyboard.map("ShiftLeft", "shoot_alt");

// In case the above is unsupported by the browser, provide the old keycodes as fallback
keyboard.map(32, "shoot_main");
keyboard.map(16, "shoot_alt");

input.enable(keyboard);

Every action will then have its associated value depending on what the input did. This value can be queried:

input.getValue("shoot_main");

For buttons or keys, this is generally 0 or 1. Analog sticks or pressure-sensitive buttons can be in between 0 and 1. The same goes for mouse movement with default settings, although their sensitivity settings can change the range.

So how do we respond when an action occurs? There are two ways:

  • continuously: You request the current value for an action using getValue. This is useful to update the walking speed every frame.
  • triggered: You respond whenever the value of an action is changed. This can be useful for things like jumping. This can be done by binding to the onAction signal:
input.onAction.bind(function(name, value) {
    if (name === "shoot" && value) {
        console.log("Bang!");
    }
});

This is dispatched every time the value changes, so also when a button is released (in which case it will be 0), so you should check for this.

There are several types of input plugins available:

  • Keyboard
  • Mouse
  • MouseLock: Similar to Mouse, but it locks the pointer to give you a game-like mouse controller.
  • Touch: For mobile screens
  • Gamepad: Using the experimental Javascript gamepad API.

All of these work similar to above, except Gamepad. Refer to OrbitController and FloatController for concrete examples in Components. (note that these components create the Input object inside the object. Often you'd want to pass in a configured object from the outside so you don't tie your input setup to your controller type (you may want to switch).

Gamepads

(Disclaimer: the native Gamepad API is an experimental browser feature, and as a result, Helix' Gamepad support must be treated as such as well)

Gamepads work a bit differently because of the way browsers handle them. There may be none, one or many gamepads connected to your PC, and most of the time, they're not available until you interact with them (for security reasons). Instead, we need to query the available gamepads and listen for newly connected ones:


function onGamepadConnected(gamepad)
{
   console.log("enabling gamepad " + gamepad.id);
   input.enable(gamepad);

   // map the right trigger
   gamepad.map(HX.Gamepad.RT | HX.Gamepad.PRESSED, "shoot");
   gamepad.map(HX.Gamepad.STICK_LX, "walkX");
   gamepad.map(HX.Gamepad.STICK_LY, "walkY");
   gamepad.map(HX.Gamepad.STICK_RX, "lookX");
   gamepad.map(HX.Gamepad.STICK_RY, "lookY");
}

function onGamepadDisconnected(gamepad)
{
   input.disable(gamepad);
}

var input = new HX.Input();

var gamepads = HX.getGamepads();
gamepads.forEach(onGamepadConnected);
HX.onGamepadConnected.bind(onGamepadConnected);
HX.onGamepadDisconnected.bind(onGamepadDiconnected);

input.onAction.bind((action, value) => {
   if (action === "shoot" && value)
       console.log("shoot!");
});

// on every frame, we could also poll for the walk states using
input.getValue("walkX");

This example is a bit silly in that it enables all gamepads. Perhaps, you'd want the user to select which gamepad s/he wants to use or assign each Gamepad object to a different Input object: one per user.

Also, peculiar to Gamepad, is the following:

gamepad.map(HX.Gamepad.RT | HX.Gamepad.PRESSED, "shoot");

Since gamepad buttons can be pressure sensitive, mapping a button like this:

gamepad.map(HX.Gamepad.RT, "shoot");

Would map to the pressure sensitive values, meaning the action will be triggered when the pressure changes, not when the pressed state changes. Adding HX.Gamepad.RT | HX.Gamepad.PRESSED specifies that we're only interested in the pressed state. Similarly, you could specify HX.Gamepad.A | HX.Gamepad.TOUCHED to indicate you're interested in the touch-sensitivity. This is however not commonly supported (even less commonly than the rest of the Gamepad API).