1

I am trying to write a web page where I can detect button presses on a Xbox controller and show a user a boolean value based on the button pressed. Right now, I can detect a controller being connected and show that as a string. The documentation says to use this code to detect a button press here:

var isPressed = navigator.getGamepads()[0].pressed;

but Chrome shows this error when using it:

Uncaught TypeError: Cannot read property 'pressed' of null

The error is linked to the .pressed part of the above line of code. All the documentation is on the Mozilla site, so I'm assuming they used FireFox when writing the tutorials.

What I ideally want to end up with is this:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<h id="button"></h>
<h id="gpInfo"></h>
<script>
var i = 1; 
window.addEventListener("gamepadconnected", function(e) {
  var gp = navigator.getGamepads()[e.gamepad.index];
  document.getElementById("gpInfo").innerHTML = ("A " + gp.id + " was successfully detected! There are a total of " + gp.buttons.length + " buttons.")
  //alert("A " + gp.id + " was successfully detected!")
});
var isPressed = navigator.getGamepads()[0].pressed;
document.getElementById("button").innerHTML = isPressed;
</script>
<!-- <script type="text/javascript" src="gamepadtest.js"></script> -->
</head>
</html>

The code would print a boolean value on the screen for users to see when they press a button. This is my first time working with JavaScript and HTML. If you could make your answer noob-friendly that would be great! Documentation for Gamepad API and for GamepadButton

Nathan
  • 65
  • 1
  • 4
  • 14
  • The error is telling you that `navigator.getGamepads()[0]` is nothing, so: verify that there even _is_ a gamepad, in code. E.g. `const gamepads = navigator.getGamepads(); if (gamepads.length > 0) { const gamepad = ...; /* more code here */ } else { console.warn("there are no gamepads"); }` – Mike 'Pomax' Kamermans Mar 22 '20 at 21:12
  • @Mike'Pomax'Kamermans Okay, will try in a sec. The gamepad doesn't connect till the user presses a button on the gamepad. – Nathan Mar 22 '20 at 21:15

2 Answers2

2

You shouldn't reference the Gamepad object until the gamepadconnected event has been thrown. Also, you'll need a loop to poll the button value. Here's some revised code:

var i = 1; 
window.addEventListener("gamepadconnected", function(e) {
  var gp = navigator.getGamepads()[e.gamepad.index];
  document.getElementById("gpInfo").innerHTML = ("A " + gp.id + " was successfully detected! There are a total of " + gp.buttons.length + " buttons.")
  //alert("A " + gp.id + " was successfully detected!")

  setInterval(function(){
    isPressed = gp.buttons[0].pressed;
    document.getElementById("button").innerHTML = isPressed;
  }, 100)
});
Caleb Denio
  • 1,465
  • 8
  • 15
  • This works great, it detects if the button is pressed. How can I get it to keep checking if the button is pressed? – Nathan Mar 22 '20 at 21:25
  • 1
    It should keep checking anyway, because of the `setInterval`. It runs every 100 milliseconds. If it's still not updating, I'd look for errors in the console. – Caleb Denio Mar 22 '20 at 21:33
  • There are no errors in the console but in Elements I can see it updating, however its not recognising my presses. I am pressing button 1 to get it to show the remote and then I press button 0 for the variable to change. But this is not the case, it is not picking it up? Let me know if you need my full code – Nathan Mar 23 '20 at 14:52
  • 1
    This code only responds to changes on button 0. I'm not sure exactly what you want to do. – Caleb Denio Mar 23 '20 at 15:30
  • I have used your example to listen for 20 buttons and i can detect each button, but once i have pressed one, it will not change another result for a different button. You can see the code live [here](http://www.hissmarthome.uk/speechonlinelivedemo/) – Nathan Mar 23 '20 at 16:35
2

I don't have enough reputation to just add a comment to Caleb Denio's answer, but regarding Nathan's comment on that answer:

I have used your example to listen for 20 buttons and i can detect each button, but once i have pressed one, it will not change another result for a different button.

I see the same behaviour on Chrome 90. Specifically, a new GamepadList instance, containing new Gamepad instances, all seem to be created each time the state of any gamepad changes (e.g. which of its buttons are pressed).

You can test it with this:

var gamepads = null;

function callback() {
    var new_gamepads = navigator.getGamepads();
    if(new_gamepads !== gamepads) {
        console.log('New GamepadList!', new_gamepads);
        gamepads = new_gamepads;
    }
}

var interval = setInterval(callback, 100);

...for me, that logs 'New GamepadList!' each time I press/release a button.

Long story short, you need to poll navigator.getGamepads() each frame in order to detect changes in gamepad state. A minimalist fix for Caleb Denio's answer would therefore be:

var i = 1; 
window.addEventListener("gamepadconnected", function(e) {
  var gp = navigator.getGamepads()[e.gamepad.index];
  document.getElementById("gpInfo").innerHTML = ("A " + gp.id + " was successfully detected! There are a total of " + gp.buttons.length + " buttons.")
  //alert("A " + gp.id + " was successfully detected!")

  setInterval(function(){

    // ===> Get a fresh GamepadList! <===
    var gp = navigator.getGamepads()[e.gamepad.index];

    isPressed = gp.buttons[0].pressed;
    document.getElementById("button").innerHTML = isPressed;
  }, 100)
});