npm.io
3.2.0 • Published 2 weeks ago

@rbuljan/gamepad

Licence
MIT
Version
3.2.0
Deps
0
Size
127 kB
Vulns
0
Weekly
0
Stars
11

Gamepad

Live demo · npm

Virtual multi-touch joysticks, buttons, and D-pads for JavaScript games, apps, and IoT. Works with mouse, pen, and touch. Includes JSDoc types for editor hints and type checking.

JavaScript virtual gamepad

Install

npm install @rbuljan/gamepad

Usage

Create a container with a position and height:

<div id="controls" style="position: relative; height: 300px;"></div>

Add your controls. Gamepad initializes them automatically:

import { Gamepad } from "@rbuljan/gamepad";

const gamepad = new Gamepad([
    {
        id: "move",
        type: "joystick",
        parent: "#controls",
        position: { left: "20%", top: "50%" },
        onInput() {
            console.log("Strength:", this.value, "Angle:", this.angle);
        },
    },
    {
        id: "navigate",
        type: "dpad",
        parent: "#controls",
        position: { left: "50%", top: "50%" },
        onInput() {
            console.log("Direction:", this.value ? this.direction : "idle");
        },
    },
    {
        id: "fire",
        type: "button",
        parent: "#controls",
        position: { left: "80%", top: "50%" },
        text: "Fire",
        onInput() {
            if (this.value) console.log("Fire!");
        },
    },
]);

// Read the latest input from your game loop.
console.log(gamepad.controllers.move.value);

Use method syntax for onInput() as shown, so this refers to the controller. It runs when input changes through a press, drag, or release, rather than on every animation frame.

Standalone controls

Each controller also works on its own. These independent examples use the same #controls container; call init() to display the control and destroy() to remove it.

IDs are generated automatically and are available as controller.id. Set id when you want a name for lookup, such as gamepad.controllers.move.

Horizontal joystick
import { Joystick } from "@rbuljan/gamepad";

const joystick = new Joystick({
    parent: "#controls",
    axis: "x",
    onInput() {
        console.log("Rotation:", this.value); // -1 (left) to 1 (right)
    },
});

joystick.init();
Toggle button
import { Button } from "@rbuljan/gamepad";

const button = new Button({
    parent: "#controls",
    text: "Menu",
    spring: false,
    onInput() {
        console.log("Menu open:", Boolean(this.value));
    },
});

button.init();
Horizontal D-pad
import { DPad } from "@rbuljan/gamepad";

const dpad = new DPad({
    parent: "#controls",
    axis: "x",
    onInput() {
        const horizontal = this.value ? (this.direction === 0 ? 1 : -1) : 0;
        console.log("Steering:", horizontal); // -1 left, 0 idle, 1 right
    },
});

dpad.init();

Use axis: "y" for a vertical D-pad (2 down, 6 up), or axis: "all" for all eight directions. Its value is always 0 or 1; check it before reading direction.

Options

Pass these options to Gamepad entries or standalone controllers:

Option Default Description
id Auto-generated Optional unique controller ID. Omitted or empty IDs are generated once and retained on reinitialization.
type "joystick" "joystick", "button", or "dpad". Standalone classes set this automatically.
parent "body" CSS selector of the container.
position { left: "50%", top: "50%" } CSS position inside the container.
radius 50 Radius in pixels.
axis "all" "all", "x", or "y" for joysticks and D-pads.
fixed true Set to false to reposition a joystick or button where the press starts. D-pads always stay fixed.
spring true Reset input on release. With false, joysticks and D-pads retain input, and buttons toggle on each press.
deadZone 0.2 for an all-axis D-pad; otherwise 0 Inactive fraction of the D-pad radius, from 0 to 1. Applies only to D-pads.
directionsTot 8 Number of direction regions for joysticks and D-pads.
text "" Label or HTML content, typically for buttons.
style {} CSS styles, e.g. { color: "#3498db" }.
onInput() No action Input callback; this is the controller.

With spring: false, releasing does not call onInput(). For multiple non-fixed controls, use separate positioned containers so their touch areas do not overlap.

Input values

Read these inside onInput() using this, or through gamepad.controllers[id]:

Property Meaning
value Joystick: 0 to 1 on all axes, or -1 to 1 on a single axis. Button and D-pad: 0 or 1.
angle Joystick/D-pad angle in radians, clockwise from right, from 0 up to (but excluding) 2π.
direction Joystick/D-pad direction index, clockwise from right.
angleDirection Angle in radians snapped to the current direction.
isPress Whether a pointer is currently held down.
isDrag Whether a joystick or D-pad has moved during the current press.
isActive Active state; for toggle buttons, whether the button is on.

A single-axis joystick is positive to the right on X and up on Y. With eight directions, direction is:

Right Down-right Down Down-left Left Up-left Up Up-right
0 1 2 3 4 5 6 7

A D-pad has value: 0 inside its dead zone and, with the default spring: true, on release. Its last direction is retained on release.

Methods

new Gamepad([...]) accepts controller options or existing Joystick, Button, and DPad instances. The array is optional.

Gamepad method Description
add(...controllers) Add and initialize controller options or instances.
remove(...idsOrControllers) Remove and destroy controllers by ID or instance.
destroy() Remove all controllers and the fullscreen listener. Pass an ID or instance to remove only that controller.
requestFullScreen() Enable fullscreen requests on clicks/taps.
exitFullScreen() Exit fullscreen.
isVibrationSupported() Return whether browser vibration is supported.
vibrate(100) Vibrate for a duration in milliseconds, or pass a pattern such as [100, 30, 100].

Gamepad methods return the gamepad for chaining, except isVibrationSupported(), which returns a boolean. Fullscreen and vibration depend on browser support.

Standalone controllers have init() to create their UI and destroy() to remove it and its listeners. To let a gamepad manage them instead, use gamepad.add(controller).

Styling

Use style for individual controls, or CSS for their pressed appearance:

.Gamepad-controller.is-active {
    box-shadow: 0 0 20px currentColor;
}

Development

npm install
npm run dev        # Run the example locally; open the URL printed by Vite
npm run typecheck  # Check library JSDoc types
npm test
npm run build      # Build the library and example
npm run build:pages # Build the GitHub Pages demo

See live demo

License

MIT

Keywords