A client-side JavaScript module one can import to add good gamepad support to web-powered games or other gamepad-powered web applications.
GameinputJS's src and img folders need to be accessible in the distributed version of your website.
I would recommend setting up a package.json script to help update a distributable copy of the library outside node_modules:
{
"name": "@me/my-client-webapp",
"scripts": {
"deps2lib": "shx rm -rf lib && shx mkdir lib && shx cp -r node_modules/gameinputjs lib/"
}
...
}Which would allow you to run:
npm i --save-dev shx
npm i gameinputjs
npm run deps2libImport from within a Javascript module, construct it, and then use either the events or make a game loop that uses the properties.
import { GameInput, DetectedOS } from './lib/gameinputjs/src/gameinput.js'
import { GameInputSchemaSectionNames, GameInputSchemaButtonNames } from './lib/gameinputjs/src/gameinput-schema.js'
const gameInput = new GameInput()You can configure GameInput by passing options to the constructor:
const gameInput = new GameInput({
debugStatements: false, // Enable debug console logs (default: false)
maxPlayers: 8 // Force max players (normally auto-detected, 4-8 on supported browsers)
})GameInput supports at least 4 players, up to 8 when detected on supported browsers. The maxPlayers option forces a specific maximum, overriding auto-detection.
Example:
// Auto-detect (recommended)
const gameInput = new GameInput()
// Force a specific maximum
const gameInput = new GameInput({ maxPlayers: 16 })
// Access all detected players
for (const player of gameInput.Players) {
if (player.model) {
console.log(`Player ${player.number} connected`)
}
}gameInput
.onReinitialize(() => {
console.debug("Players updated")
const firstPlayer = gameInput.getPlayer(0)
if (!firstPlayer?.model) {
noPlayers()
return
}
displayButtons(firstPlayer)
document.querySelector('img.gamepad').src = `img/${firstPlayer?.model?.iconName ?? 'generic'}.png`
})
.onButtonDown((playerIndex, sectionName, buttonName) => {
const player = gameInput.getPlayer(playerIndex)
console.debug(`Player ${player} pushed ${player.getButtonText(sectionName, buttonName)} (${buttonName})`)
})
.onButtonUp((playerIndex, sectionName, buttonName) => {
const player = gameInput.getPlayer(playerIndex)
console.debug(`Player ${player} released ${player.getButtonText(sectionName, buttonName)} (${buttonName})`)
if (sectionName === 'center' && buttonName === 'menu') {
console.debug('menu requested')
return
}
if (sectionName === 'face' && buttonName === player.schema.ordinalButton(0)) {
console.debug('Jump / Confirm pushed')
}
})function gameLoop() {
for (const player of gameInput.Players) {
if (!player)
continue
if (player.state.face.ordinal(0))
player.rumble({ duration: 200, weakMagnitude: 1.0, strongMagnitude: 0.25 })
const leftStick = player.getStickVector('left')
console.debug(`Player left stick vector is ${leftStick.toString()}`)
requestAnimationFrame(() => gameLoop())
}
}
requestAnimationFrame(() => gameLoop()) // kick offGameInputJS provides proper cleanup APIs to prevent memory leaks in Single Page Applications (SPAs).
Event listener methods (onButtonDown, onButtonUp, onReinitialize) return an unsubscribe function:
// Subscribe to an event
const unsubscribe = gameInput.onButtonDown((playerIndex, sectionName, buttonName) => {
console.debug(`Button ${buttonName} pressed`)
})
// Later: unsubscribe when no longer needed
unsubscribe()When you're done with a GameInput instance (e.g., navigating away from a game page), call destroy() to clean up all resources:
// Clean up everything
gameInput.destroy()This will:
- Stop the update loop and connection watch loop
- Remove all event listeners (both custom and window events)
- Clear all gamepad and player references
Example: Cleanup in a SPA
// In your route setup
function initGame() {
const gameInput = new GameInput()
gameInput.onButtonDown((player, section, button) => {
// Handle input
})
return gameInput
}
// When navigating away
function cleanupGame(gameInput) {
gameInput.destroy()
}