diff --git a/CHANGELOG.md b/CHANGELOG.md index ff16b78a1..4befe3617 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Fixed +- Fixed issue where non-standard gamepad buttons would not be emitted by Excalibur - Fixed issue where Realistic solver would not sort contacts by distance causing some artifacts on seams - Fixed issue with CompositeCollider where large TileMaps would sometimes causes odd collision behavior in the Realistic Solver when the body & collider components are far apart in a TileMap. - Fixed crash on Xiaomi Redmi Phones by lazy loading the GPU particle renderer, GPU particles still do not work on these phones diff --git a/sandbox/tests/input/gamepad.ts b/sandbox/tests/input/gamepad.ts index c26b4dd11..affa6ba95 100644 --- a/sandbox/tests/input/gamepad.ts +++ b/sandbox/tests/input/gamepad.ts @@ -18,7 +18,9 @@ function start() { }); // Log when pads disconnect and connect - game.input.gamepads.on('connect', (evet: ex.GamepadConnectEvent) => { + game.input.gamepads.on('connect', (evt: ex.GamepadConnectEvent) => { + evt.gamepad.on('button', console.log); + evt.gamepad.on('axis', console.log); console.log('Gamepad connect'); }); @@ -49,7 +51,9 @@ function start() { [ex.Buttons.DpadUp, 255, 166], [ex.Buttons.DpadDown, 255, 222], [ex.Buttons.DpadLeft, 227, 193], - [ex.Buttons.DpadRight, 284, 193] + [ex.Buttons.DpadRight, 284, 193], + [17, 420, 228], + [16, 388, 228] ]; var buttons: { [key: number]: CircleActor } = {}; @@ -92,14 +96,16 @@ function start() { const actor = buttons[btn]; if (pad1.wasButtonPressed(btnIndex, 0.1) || pad1.wasButtonReleased(btnIndex)) { actor.actions.clearActions(); - actor.actions.scaleTo(ex.Vector.One.scale(1.25), ex.Vector.One.scale(5)).scaleTo(ex.Vector.One.scale(1), ex.Vector.One.scale(5)); + actor.actions.scaleTo({ scale: ex.vec(1.25, 1.25), duration: 500 }).scaleTo({ scale: ex.vec(1, 1), duration: 500 }); } - if (pad1.isButtonPressed(btnIndex, 0.1)) { - actor.color = new ex.Color(255, 0, 0, 0.8); + if (pad1.isButtonHeld(btnIndex, 0.1)) { + actor.color.r = 255; + actor.color.a = 0.8; actor.value = pad1.getButton(btnIndex); } else { - actor.color = new ex.Color(0, 0, 0, 0.7); + actor.color.r = 0; + actor.color.a = 0.7; actor.value = 0; } } @@ -109,6 +115,7 @@ function start() { class CircleActor extends ex.Actor { private _value = 0; + private _circle: ex.Circle; public get value(): number { return this._value; } @@ -121,13 +128,18 @@ class CircleActor extends ex.Actor { }); constructor(args: ex.ActorArgs) { super(args); + this._circle = new ex.Circle({ + radius: this.width, + color: this.color + }); this.graphics.add( - new ex.Circle({ - radius: this.width, - color: this.color + new ex.GraphicsGroup({ + members: [this._circle, this._text] }) ); - this.graphics.add(this._text); + } + onPostUpdate(engine: ex.Engine, elapsed: number): void { + this._circle.color = this.color; this._text.color = this.color; } } diff --git a/src/engine/Input/Gamepad.ts b/src/engine/Input/Gamepad.ts index 04b8111b2..e1d9bef9a 100644 --- a/src/engine/Input/Gamepad.ts +++ b/src/engine/Input/Gamepad.ts @@ -145,7 +145,6 @@ export class Gamepads { this.init(); const gamepads = this._navigator.getGamepads(); - for (let i = 0; i < gamepads.length; i++) { if (!gamepads[i]) { const gamepad = this.at(i); @@ -177,37 +176,31 @@ export class Gamepads { this.at(i).navigatorGamepad = gamepads[i]; // Buttons - let b: string, bi: number, a: string, ai: number, value: number; - - for (b in Buttons) { - bi = Buttons[b]; - if (typeof bi === 'number') { - if (gamepads[i].buttons[bi]) { - value = gamepads[i].buttons[bi].value; - if (value !== this._oldPads[i].getButton(bi)) { - if (gamepads[i].buttons[bi].pressed) { - this.at(i).updateButton(bi, value); - this.at(i).events.emit('button', new GamepadButtonEvent(bi, value, this.at(i))); - } else { - this.at(i).updateButton(bi, 0); - } + + const gamepad = gamepads[i]; + // gamepads are a list that might be null + if (gamepad) { + for (let buttonIndex = 0; buttonIndex < gamepad.buttons.length; buttonIndex++) { + const button = gamepad.buttons[buttonIndex]; + const value = button?.value; + if (value !== this._oldPads[i]?.getButton(buttonIndex)) { + if (button?.pressed) { + this.at(i).updateButton(buttonIndex, value); + this.at(i).events.emit('button', new GamepadButtonEvent(buttonIndex, value, this.at(i))); + } else { + this.at(i).updateButton(buttonIndex, 0); } } } - } - // Axes - for (a in Axes) { - ai = Axes[a]; - if (typeof ai === 'number') { - value = gamepads[i].axes[ai]; - if (value !== this._oldPads[i].getAxes(ai)) { - this.at(i).updateAxes(ai, value); - this.at(i).events.emit('axis', new GamepadAxisEvent(ai, value, this.at(i))); + for (let axesIndex = 0; axesIndex < gamepad.axes.length; axesIndex++) { + const axis = gamepad.axes[axesIndex]; + if (axis !== this._oldPads[i]?.getAxes(axesIndex)) { + this.at(i).updateAxes(axesIndex, axis); + this.at(i).events.emit('axis', new GamepadAxisEvent(axesIndex, axis, this.at(i))); } } } - this._oldPads[i] = this._clonePad(gamepads[i]); } } @@ -315,7 +308,7 @@ export class Gamepad { * @param button The button to query * @param threshold The threshold over which the button is considered to be pressed */ - public isButtonPressed(button: Buttons, threshold: number = 1) { + public isButtonPressed(button: Buttons | number, threshold: number = 1) { return this._buttons[button] >= threshold; } @@ -324,7 +317,7 @@ export class Gamepad { * @param button The button to query * @param threshold The threshold over which the button is considered to be pressed */ - public isButtonHeld(button: Buttons, threshold: number = 1) { + public isButtonHeld(button: Buttons | number, threshold: number = 1) { return this._buttons[button] >= threshold; } @@ -333,7 +326,7 @@ export class Gamepad { * @param button Test whether a button was just pressed * @param threshold The threshold over which the button is considered to be pressed */ - public wasButtonPressed(button: Buttons, threshold: number = 1) { + public wasButtonPressed(button: Buttons | number, threshold: number = 1) { return this._buttonsDown[button] >= threshold; } @@ -341,14 +334,14 @@ export class Gamepad { * Tests if a certain button was just released this frame. This is cleared at the end of the update frame. * @param button Test whether a button was just released */ - public wasButtonReleased(button: Buttons) { + public wasButtonReleased(button: Buttons | number) { return Boolean(this._buttonsUp[button]); } /** * Gets the given button value between 0 and 1 */ - public getButton(button: Buttons) { + public getButton(button: Buttons | number) { return this._buttons[button]; } @@ -356,7 +349,7 @@ export class Gamepad { * Gets the given axis value between -1 and 1. Values below * {@apilink MinAxisMoveThreshold} are considered 0. */ - public getAxes(axes: Axes) { + public getAxes(axes: Axes | number) { const value = this._axes[axes]; if (Math.abs(value) < Gamepads.MinAxisMoveThreshold) { @@ -413,6 +406,10 @@ export class Gamepad { * Gamepad Buttons enumeration */ export enum Buttons { + /** + * Any button that isn't explicity known by excalibur + */ + Unknown = -1, /** * Face 1 button (e.g. A) */ @@ -476,7 +473,16 @@ export enum Buttons { /** * D-pad right */ - DpadRight = 15 + DpadRight = 15, + /** + * Center button (e.g. the Nintendo Home Button) + */ + CenterButton = 16, + /** + * Misc button 1 (e.g. Xbox Series X share button, PS5 microphone button, Nintendo Switch Pro capture button, Amazon Luna microphone button) + * defacto standard not listed on the w3c spec for a standard gamepad https://w3c.github.io/gamepad/#dfn-standard-gamepad + */ + MiscButton1 = 17 } /** @@ -505,7 +511,7 @@ export enum Axes { * @internal */ export interface NavigatorGamepads { - getGamepads(): NavigatorGamepad[]; + getGamepads(): (NavigatorGamepad | undefined)[]; } /**