From: Rubén Beltrán del Río Date: Tue, 29 May 2018 05:29:27 +0000 (-0500) Subject: Add gamepad support (#10) X-Git-Url: https://git.r.bdr.sh/rbdr/sumo/commitdiff_plain/43413defcb854c8706a84a6a54a61c7977b52614?ds=sidebyside Add gamepad support (#10) * Update docs * Add gamepad support * Remove console logs * Add factories for controllables * Add both controllables * Add gamepad support to changelog --- diff --git a/CHANGELOG.md b/CHANGELOG.md index c038284..c64a8fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,5 +19,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Scripts to setup hooks, linting, docs, and bundling - Keyboard Support - Dash & grab mechanics +- Add support for Gamepads (Tested using PS4 only) [Unreleased]: https://github.com/rbdr/sumo/compare/master...develop diff --git a/lib/components/control_map.js b/lib/components/control_map.js index cb447d0..4edf318 100644 --- a/lib/components/control_map.js +++ b/lib/components/control_map.js @@ -16,8 +16,8 @@ import { Component } from '@serpentity/serpentity'; * @typedef tControlSource * @type object * - * @property {string} type type of input, can be keyboard or gamepad (only keyboard supported atm) - * @property {number} gamepadNumber the number of gamepad to use (unsupported) + * @property {string} type type of input, can be keyboard or gamepad + * @property {number} gamepadNumber the number of gamepad to use * @property {string} gamepadInputSource either axes or buttons * @property {number} index the input index or keycode */ diff --git a/lib/factories/pixi.js b/lib/factories/pixi.js index f941ffe..077c5b4 100644 --- a/lib/factories/pixi.js +++ b/lib/factories/pixi.js @@ -19,10 +19,11 @@ export default { createSumo(config) { const radius = config.radius; + const sumoColor = config.color || 0x87c5ea; // The body const body = new Graphics(); - body.beginFill(0x87c5ea) + body.beginFill(sumoColor) .drawCircle(0, 0, radius) .endFill(); diff --git a/lib/factories/sumo.js b/lib/factories/sumo.js index 7b50f8a..18e9d72 100644 --- a/lib/factories/sumo.js +++ b/lib/factories/sumo.js @@ -71,9 +71,12 @@ export default { // RENDERING const radius = 25; + const pixiConfig = Object.assign({ + radius + }, config.pixi); const container = config.container || { - container: PixiFactory.createSumo({ radius }) + container: PixiFactory.createSumo(pixiConfig) }; container.container.position.x = position.x; container.container.position.y = position.y; @@ -195,74 +198,304 @@ export default { const entity = this.createSumo(null, config); - entity.addComponent(new ControlMapComponent({ - map: [ - { - source: { - type: 'keyboard', - index: 37 // left arrow + entity.addComponent(new ControlMapComponent(config.controlMap)); + + if (engine) { + engine.addEntity(entity); + } + + return entity; + }, + + /** + * Creates a controllable sumo entity and adds it to the engine. Can override + * position in the config object. Has contrrol scheme defaults for + * player 1 + * + * @function createPlayer1Sumo + * @memberof SumoFactory + * @param {external:Serpentity} [engine] the serpentity engine to attach + * to. If not sent, it will not be attached. + * @param {object} [config] the config to override the entity, accepts + * the key `position` as an object with an x and y property. + * @return {external:Serpentity.Entity} the created entity + */ + createPlayer1Sumo(engine, config = {}) { + + const playerConfig = Object.assign({ + controlMap: { + map: [ + { + source: { + type: 'keyboard', + index: 65 // a + }, + target: { + component: ForceComponent, + property: 'x', + value: (value) => -Number(value) + } }, - target: { - component: ForceComponent, - property: 'x', - value: (value) => -Number(value) - } - }, - { - source: { - type: 'keyboard', - index: 39 // right arrow + { + source: { + type: 'keyboard', + index: 68 // d + }, + target: { + component: ForceComponent, + property: 'x', + value: (value) => Number(value) + } }, - target: { - component: ForceComponent, - property: 'x', - value: (value) => Number(value) - } - }, - { - source: { - type: 'keyboard', - index: 38 // up arrow + { + source: { + type: 'keyboard', + index: 87 // w + }, + target: { + component: ForceComponent, + property: 'y', + value: (value) => -Number(value) + } }, - target: { - component: ForceComponent, - property: 'y', - value: (value) => -Number(value) - } - }, - { - source: { - type: 'keyboard', - index: 40 // down arrow + { + source: { + type: 'keyboard', + index: 83 // s + }, + target: { + component: ForceComponent, + property: 'y', + value: (value) => Number(value) + } }, - target: { - component: ForceComponent, - property: 'y', - value: (value) => Number(value) - } - }, - { - source: { - type: 'keyboard', - index: 90 // Z + { + source: { + type: 'keyboard', + index: 90 // Z + }, + target: { + component: DashComponent, + property: 'dashing' + } + }, + { + source: { + type: 'keyboard', + index: 88 // X + }, + target: { + component: GrabComponent, + property: 'grabbing' + } + }, + { + source: { + type: 'gamepad', + gamepadNumber: 0, + gamepadInputSource: 'axes', + index: 0 // left stick horizontal + }, + target: { + component: ForceComponent, + property: 'x', + value: (value) => Number(value) + } + }, + { + source: { + type: 'gamepad', + gamepadNumber: 0, + gamepadInputSource: 'axes', + index: 1 // left stick vertical + }, + target: { + component: ForceComponent, + property: 'y', + value: (value) => Number(value) + } }, - target: { - component: DashComponent, - property: 'dashing' + { + source: { + type: 'gamepad', + gamepadNumber: 0, + gamepadInputSource: 'buttons', + index: 2 // left face button + }, + target: { + component: DashComponent, + property: 'dashing', + value: (value) => value.value + } + }, + { + source: { + type: 'gamepad', + gamepadNumber: 0, + gamepadInputSource: 'buttons', + index: 0 // bottom face button + }, + target: { + component: GrabComponent, + property: 'grabbing', + value: (value) => value.value + } } - }, - { - source: { - type: 'keyboard', - index: 88 // X + ] + } + }, config); + + const entity = this.createControllableSumo(null, playerConfig); + + if (engine) { + engine.addEntity(entity); + } + + return entity; + }, + + /** + * Creates a controllable sumo entity and adds it to the engine. Can override + * position in the config object. Has contrrol scheme defaults for + * player 2 + * + * @function createPlayer2Sumo + * @memberof SumoFactory + * @param {external:Serpentity} [engine] the serpentity engine to attach + * to. If not sent, it will not be attached. + * @param {object} [config] the config to override the entity, accepts + * the key `position` as an object with an x and y property. + * @return {external:Serpentity.Entity} the created entity + */ + createPlayer2Sumo(engine, config = {}) { + + const playerConfig = Object.assign({ + pixi: { + color: 0xeaacac + }, + controlMap: { + map: [ + { + source: { + type: 'keyboard', + index: 37 // left arrow + }, + target: { + component: ForceComponent, + property: 'x', + value: (value) => -Number(value) + } + }, + { + source: { + type: 'keyboard', + index: 39 // right arrow + }, + target: { + component: ForceComponent, + property: 'x', + value: (value) => Number(value) + } + }, + { + source: { + type: 'keyboard', + index: 38 // up arrow + }, + target: { + component: ForceComponent, + property: 'y', + value: (value) => -Number(value) + } + }, + { + source: { + type: 'keyboard', + index: 40 // down arrow + }, + target: { + component: ForceComponent, + property: 'y', + value: (value) => Number(value) + } + }, + { + source: { + type: 'keyboard', + index: 188 // , + }, + target: { + component: DashComponent, + property: 'dashing' + } }, - target: { - component: GrabComponent, - property: 'grabbing' + { + source: { + type: 'keyboard', + index: 190 // . + }, + target: { + component: GrabComponent, + property: 'grabbing' + } + }, + { + source: { + type: 'gamepad', + gamepadNumber: 1, + gamepadInputSource: 'axes', + index: 0 // left stick horizontal + }, + target: { + component: ForceComponent, + property: 'x', + value: (value) => Number(value) + } + }, + { + source: { + type: 'gamepad', + gamepadNumber: 1, + gamepadInputSource: 'axes', + index: 1 // left stick vertical + }, + target: { + component: ForceComponent, + property: 'y', + value: (value) => Number(value) + } + }, + { + source: { + type: 'gamepad', + gamepadNumber: 1, + gamepadInputSource: 'buttons', + index: 2 // left face button + }, + target: { + component: DashComponent, + property: 'dashing', + value: (value) => value.value + } + }, + { + source: { + type: 'gamepad', + gamepadNumber: 1, + gamepadInputSource: 'buttons', + index: 0 // bottom face button + }, + target: { + component: GrabComponent, + property: 'grabbing', + value: (value) => value.value + } } - } - ] - })); + ] + } + }, config); + + const entity = this.createControllableSumo(null, playerConfig); if (engine) { engine.addEntity(entity); diff --git a/lib/sumo.js b/lib/sumo.js index d39c055..cdf8cc6 100644 --- a/lib/sumo.js +++ b/lib/sumo.js @@ -237,14 +237,14 @@ internals.Sumo = class Sumo { } }); - const sumoA = SumoFactory.createSumo(null, { + const sumoA = SumoFactory.createPlayer1Sumo(null, { position: { x: this.horizontalResolution / 2 - 100, y: this.verticalResolution / 2 } }); - const sumoB = SumoFactory.createControllableSumo(null, { + const sumoB = SumoFactory.createPlayer2Sumo(null, { position: { x: this.horizontalResolution / 2 + 100, y: this.verticalResolution / 2 diff --git a/lib/systems/control_mapper.js b/lib/systems/control_mapper.js index e91ecc6..c39aea3 100644 --- a/lib/systems/control_mapper.js +++ b/lib/systems/control_mapper.js @@ -2,9 +2,11 @@ import { System } from '@serpentity/serpentity'; import ControllableNode from '../nodes/controllable'; -/* global window */ +/* global navigator window */ const internals = { + gamepadState: { + }, keyboardState: { } }; @@ -32,6 +34,7 @@ export default class ControlMapperSystem extends System { this.controllables = null; this._initializeKeyboard(); + this._initializeGamepad(); } /** @@ -71,11 +74,21 @@ export default class ControlMapperSystem extends System { */ update(currentFrameDuration) { + this._updateGamepads(); + for (const controllable of this.controllables) { for (const map of controllable.controlMap.map) { if (map.source.type === 'keyboard') { this._setValue(controllable.entity, map.target, !!internals.keyboardState[map.source.index]); } + + if (map.source.type === 'gamepad') { + const gamepad = internals.gamepadState[map.source.gamepadNumber]; + if (gamepad) { + const source = gamepad[map.source.gamepadInputSource]; + source && this._setValue(controllable.entity, map.target, source[map.source.index]); + } + } } } } @@ -95,6 +108,32 @@ export default class ControlMapperSystem extends System { }); } + // Requests gamepad access and binds to the events + + _initializeGamepad() { + + window.addEventListener('gamepadconnected', (event) => { + + internals.gamepadState[event.gamepad.index] = event.gamepad; + window.gamepad = event.gamepad; + }); + + window.addEventListener('gamepaddisconnected', (event) => { + + delete internals.gamepadState[event.gamepad.index]; + }); + } + + // Update Gamepad + + _updateGamepads() { + + const gamepads = navigator.getGamepads(); + for (const index of Object.keys(internals.gamepadState)) { + internals.gamepadState[index] = gamepads[index]; + } + } + // Sets the value to a target _setValue(entity, target, value) { diff --git a/lib/systems/grab.js b/lib/systems/grab.js index 43e6246..c50d1d9 100644 --- a/lib/systems/grab.js +++ b/lib/systems/grab.js @@ -131,8 +131,6 @@ export default class GrabSystem extends System { Body.setPosition(grabber.grabArea.area, grabber.body.body.position); - console.log('Grab!'); - for (const grabbable of this.grabbables) { if (grabbable.entity === grabber.entity) { @@ -142,7 +140,6 @@ export default class GrabSystem extends System { const collision = SAT.collides(grabber.grabArea.area, grabbable.body.body); if (collision.collided) { grab.constraint = this._createConstraint(grabber.body.body, grabbable.body.body); - console.log('Grabbing', grab.constraint); } } } @@ -158,7 +155,6 @@ export default class GrabSystem extends System { _release(grabber) { - console.log('Releasing', grabber.grab.constraint); World.remove(this.engine.world, grabber.grab.constraint); grabber.grab.currentCooldown = 0; grabber.grab.constraint = null;