Skip to content

Commit

Permalink
fix: [#3317] gamepad support non standard buttons (#3325)
Browse files Browse the repository at this point in the history
Closes #3317

Tested on a Nintendo Switch Pro controller

https://github.com/user-attachments/assets/e331d58f-defc-42ab-9d7b-1cd8a0e91ef5

## Changes:

- Enumerates all buttons not just the ones in the enum
- Update visual integration test
  • Loading branch information
eonarheim authored Dec 31, 2024
1 parent 543c1df commit 9b2ea06
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 43 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
32 changes: 22 additions & 10 deletions sandbox/tests/input/gamepad.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
});

Expand Down Expand Up @@ -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 } = {};

Expand Down Expand Up @@ -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;
}
}
Expand All @@ -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;
}
Expand All @@ -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;
}
}
72 changes: 39 additions & 33 deletions src/engine/Input/Gamepad.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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 = <any>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 = <any>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]);
}
}
Expand Down Expand Up @@ -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;
}

Expand All @@ -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;
}

Expand All @@ -333,30 +326,30 @@ 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;
}

/**
* 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];
}

/**
* 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) {
Expand Down Expand Up @@ -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)
*/
Expand Down Expand Up @@ -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
}

/**
Expand Down Expand Up @@ -505,7 +511,7 @@ export enum Axes {
* @internal
*/
export interface NavigatorGamepads {
getGamepads(): NavigatorGamepad[];
getGamepads(): (NavigatorGamepad | undefined)[];
}

/**
Expand Down

0 comments on commit 9b2ea06

Please sign in to comment.