Giter Site home page Giter Site logo

bfollington / use-control Goto Github PK

View Code? Open in Web Editor NEW
11.0 2.0 0.0 1.69 MB

(⌨️ /🖱 / 🎮 ) keyboard, mouse and gamepad input via react hooks

Home Page: https://use-control.vercel.app

License: MIT License

HTML 8.24% JavaScript 6.10% TypeScript 85.27% CSS 0.38%
reactjs input gamepad keyboard-events mouse-events hooks

use-control's Introduction


Version Twitter ETH Language License Bundle Size Build

⌨️🖱🎮 use-control is an elegant, typesafe input management system for React supporting keyboard, mouse and gamepad.

👁  Live Demo (source in packages/example)

Installation

npm i use-control
yarn add use-control

Example

First, we set up an input mapping. Inputs come in two flavours:

  • buttons: discrete inputs like keyboard presses, mouse clicks and gamepad buttons
  • axes: continuous inputs like mouse position, gamepad joysticks and triggers
const inputMap = {
  buttons: {
    left: [
      keycode(KEYS.left_arrow),
      mouseButton('left'),
      gamepadButton(0, GAMEPADS.XBOX_ONE.D_LEFT),
    ],
    right: [
      keycode(KEYS.right_arrow),
      mouseButton('right'),
      gamepadButton(0, GAMEPADS.XBOX_ONE.D_RIGHT),
    ],
  },
  axes: {
    x: [mouseAxis('x'), gamepadAxis(0, GAMEPADS.XBOX_ONE.STICK_R_X)],
    y: [mouseAxis('y'), gamepadAxis(0, GAMEPADS.XBOX_ONE.STICK_R_Y)],
  },
}

Then we can wire up our input listeners within a component using various hooks.

const MyComponent = () => {
  const [count, setCount] = useState(0)

  useButtonPressed(inputMap, "left", () => {
    setCount(count - 1)
  })

  useButtonPressed(inputMap, "right", () => {
    setCount(count + 1)
  })

  useAxis(inputMap, "x", v => {
    console.log("x-axis", v)
  })

  return <div>{count}</div>
}

Check out the full example for more details.

Note: if you want to use gamepad as an input source you need to call gamepadInit() in the entry point of your app to set up the listeners

API Overview

Bootstrap

If you want to use gamepad input you'll need to attach the listeners when your app starts up, this probably means you want to call gamepadInit once in index.js or App.js but you can turn the feature on and off at your leisure.

  • gamepadInit()
  • gamepadTeardown()

Hooks

  • useButtonPressed(inputMap, actionName, callback)
  • useButtonReleased(inputMap, actionName, callback)
  • useButtonHeld(inputMap, actionName, throttleInterval, callback)
  • useAxis(inputMap, axisName, callback)

Input Sources

These functions can be use to construct bindings for input maps:

  • mouseButton('left' | 'right' | 'middle')
  • mouseAxis('x' | 'y')
  • keycode(code)
  • gamepadButton(controllerIndex, buttonIndex)
  • gamepadAxis(controllerIndex, buttonIndex)

Primitive Hooks

If you need to dig down and specifically target one form of input it might be more useful to pick from this list:

import { useKeyDown, useKeyUp, useKeyHeld } from 'use-control/lib/input/keyboard'

  • useKeyDown(keyCode, callback)
  • useKeyUp(keyCode, callback)
  • useKeyHeld(keyCode, callback)

import { useMouseMove, useMouseMoveNormalised, useMouseDelta } from 'use-control/lib/input/keyboard'

  • useMouseMove(callback)
  • useMouseMoveNormalised(callback)
  • useMouseDelta(callback)

import { useGamepadButtonPressed, useGamepadAxis } from 'use-control/lib/input/keyboard'

  • useGamepadButtonPressed(controllerIndex, buttonIndex, callback)
  • useGamepadAxis(controllerIndex, axisIndex, callback)

Roadmap

  • Virtual joystick support
  • Accelerometer input support
  • Controller button mappings for
    • PS4
    • Xbox 360
    • Xbox One
    • (and any others contributed)

Why use-control?

Personally, I'm just tired of writing useEffect with document.addEventListener('keydown', ...).

use-control is the API I've always dreamed of for dealing with input events, it's heavily inspired by my experience with input systems in game development. It's a tiny, batteries-included library for focusing on the actual user interactions rather than boilerplate and ochestration.

Usage

use-control relies on the core concept of an Input Mapping of keycodes, mouse buttons and gamepad buttons into Input Actions (i.e. "left", "right", "jump", "select"), declared as a JS object:

const inputMap = {
  buttons: {
    left: [
      keycode(KEYS.left_arrow),
      gamepadButton(0, GAMEPADS.XBOX_ONE.D_LEFT),
    ],
    right: [
      keycode(KEYS.right_arrow),
      gamepadButton(0, GAMEPADS.XBOX_ONE.D_RIGHT),
    ],
    jump: [
      keycode(KEYS.space)
    ]
  },
  axes: {
    x: [mouseAxis('x'), gamepadAxis(0, GAMEPADS.XBOX_ONE.STICK_R_X)],
    y: [mouseAxis('y'), gamepadAxis(0, GAMEPADS.XBOX_ONE.STICK_R_Y)],
  },
}

You should consider declaring this statically and sharing the mapping across your app but it can be dynamically updated at runtime and different mappings can be used in different components as needed.

These mappings allow us to think at a higher level when consuming input, instead of asking "what events do I need to bind to?" or "what keycode am I listening for?" we can simply ask "what happens when the user presses the jump button?"

useButtonPressed(inputMap, "jump", () => {
  player.addForce(0, -10)
})

Running this repo

Bootstrap

yarn
yarn bootstrap

Running the examples

cd packages/use-control
yarn build
cd ../example
yarn start

use-control's People

Contributors

bfollington avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

use-control's Issues

Interval based input streams (i.e. useButtonHeld) do not work with react-three-fiber

Even if we set the interval period to 16.666666ms the desync between r3f's update loop and our own leads to stuttering in practice.

We need to handle two different kinds of polling, interval based polling & useFrame based polling. How can we switch between the two modes depending on context?

  1. Create a second set of hooks specifically designed for r3f
  2. Abstract the interval source to a stream and swap between the kinds of interval based on config
  3. Introduce a synchronous isButtonDown function that can be called in useFrame

I think 2 and 3 together would be the best option from a UX POV, but if we add 3 we can technically sidestep doing the work for 2.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.