sc0ttj / component

A tiny library for isomorphic JavaScript components
MIT License
2 stars 1 forks source link

Feature: input devices add-on #42

Open sc0ttj opened 3 years ago

sc0ttj commented 3 years ago

Create an add-on that make it easy to map controls to keyboard, mouse, touchscreen and gamepad.

Once setup, pressing the gamepad or touchscreen buttons (etc) should run the given functions or "actions" - which will usually receive some props, set a new state, then call render to update the page.

Should have the following features:

Example usage

Could be something like:

// extract out the available controls
const { useGamepad, useKeyboard, useGyro, useMidiDevice } = useControls;

// use gamepad controls
const pad1 = useGamepad({
  profile: 'generic', // optional, re-maps & rename buttons, can be 'generic', 'xbox360', 'ps3', 'ps4', 'ps2', 'psx', 'logitech-f160', 'saitek-p2500', etc ..
  deadzone: 0.0,
  threshhold: 0.0,
  precision: 1.0,
  analog: true,
  rumble: {
    startDelay: 50,
    duration: 400,
    weakMagnitude: 0.5,
    strongMagnitude: 1.0,
  },
  onScreen: true, // for touchscreen devices
});

// Now define your controls:
// you can use events `onPress`, `onHold` `onRelease`, `onMove`, or the "catch-all", `on*`.

pad1.onPress({
  up(e) {
    // do stuff
  },
  down(e) {
    // do stuff
  },
  btn0(e) {
    // do stuff
  },
});

pad1.onMove({
  dpad(e) {
    // do stuff with e (which will contain e.state, e.x, e.y)
  },
  leftStick(e) {
    // do stuff with e (which will contain e.state, e.x, e.y)
  },
  rightStick(e) {
    // do stuff with e (which will contain e.state, e.x, e.y)
  },
});

pad1.onHold({
  up(e) {
    // do stuff
  },
  btn0(e) {
    // do stuff
  },
});

pad1.onRelease({
  up(e) {
    // do stuff
  },
  btn0(e) {
    // do stuff
  },
});

// or simply use `on()` to catch any event
pad1.on({
  up(e) {
    if (e.state === 'pressed')  // do stuff
    if (e.state === 'holding')  // do stuff
    if (e.state === 'released') // do stuff
    // ..or something like this:
    //someFunc[state](dt);
  },
  down(e) {
    // do stuff with e.state (which will be either 'pressed', 'holding', 'released', or null)
  },
  left(e) {
    // do stuff with e.state (which will be either 'pressed', 'holding', 'released', or null)
  },
  right(e) {
    // do stuff with e.state (which will be either 'pressed', 'holding', 'released', or null)
  },
  // or combine the above into 'dpad'
  dpad(e) {
    // do stuff with e (which will contain e.state, e.x, e.y)
  },
  btn0(e) {
    // do stuff, like trigger a 1 second rumble
    pad1.rumble({
      startDelay: 0,
      duration: 1000,
      weakMagnitude: 0.5,
      strongMagnitude: 1.0,
    });
  },
  leftStick(e) {
    // do stuff with e (which will contain e.state, e.x, e.y)
  },
  rightStick(e) {
    // do stuff with e (which will contain e.state, e.x, e.y)
  },
});

Other Libraries

General libraries:

For applying konami style key sequences:

sc0ttj commented 1 year ago

https://bengsfort.github.io/articles/making-a-js-game-part-1-game-engine/

^ nice example of using Object.defineProperty to check which keys are pressed

sc0ttj commented 1 year ago

Alternative API

Better than the above as it de-couples defining the controls and responding to them much better

// useDevice

import { Gamepad, Mouse, Keyboard } from '@scottjarvis/component/useDevice';

const pad1 = new Gamepad({
    // general settings
    options: {
        // analog axes
        axisMovementThreshold: 0.3,
        // digital axes and buttons
        repeatDelay: 120,
        holdDuration: 2000,
        // vibration settings
        startDelay: 500,
        duration: 120,          // duration of vibration effects
        weakMagnitude: 0.35,
        strongMagnitude: 1,
    },
    // optional - remap buttons, based on the players gamepad hardware device info
    remap: (deviceInfo) => {
        return {
            btn1: deviceInfo.foo ? 0 : 4,
            axis1: 7,
            axis2: 8,
        };
    },
    // bind names events to specific buttons
    controls: {
        jump: 'btn1',                // can also be an array of buttons
        left: 'axis1',               // can also be an array of buttons
        right:  'axis2',             // can also be an array of buttons
        moveCamera:  'analog2',      // can also be an array of buttons
    }
});

// now use it

// each prop in `controls` is a getter, which runs a function when you access it,
// which returns the current state of the button:
//
// dpad & buttons states can be null, 'pressed', or 'held'
// analog sticks return objects containing their x/y values, or return null if x and y both = 0

const main = function loop() {

    // enable the on screen controls overlay, passing in some style options, pass false to disable it
    // the overlay updates itself to show which buttons are currently pressed
    pad1.overlay({ ... })

    // dpad and buttons - will equal null, 'pressed' or 'held'
    if (pad1.left) {}
    if (pad1.right) {}
    if (pad1.jump) {}

    // easily check buttons combinations (both buttons presses at same time)
    if (pad1.left && pad1.jump) {}

    // analog sticks will equal an object { x: num, y: num }, or null if `x` and `y` equal zero (stick is centered)
    // ...where `num` is a float between -1 and 1
    if (pad1.moveCamera.x || pad1.moveCamera.y) {}

    // track previous key presses (good for logging special moves, cheat codes, etc)
    // ...cleared after 200ms of no activity
    // ...max 10 items in list
    // ...newest items listed first, they push older items out the list
    if (pad1.keyCombo === 'left+right+left+left+right+right+jump+jump') {
        // cheat code was entered
    }

    // can also do it all this way:
    const { left, right, jump } = pad1;
    if (left) {}
    if (jump) {}

    console.log(pad1.state) // returns object with all buttons/axes and their states

    // continue the loop
    requestAnimationFrame(loop);
};

main();