DEV Community

Cover image for Virtual Gamepad in JavaScript
Alvaro Montoro
Alvaro Montoro

Posted on

Virtual Gamepad in JavaScript

This past week, there was a first for me. I did something I've never done before: I ran a workshop as part of a conference. The CodeLand:Distributed conference.

It was titled "Rocking the Gamepad API", and we developed a Rock Band-styled game in HTML and JavaScript that could be controlled with the PlayStation drumkit connected to the computer. Then in separate groups, the attendees could develop their own game/app using the Gamepad API.

I had different game controllers that I could use during the presentation, but what happened if the people attending the workshop didn't have one? What if they couldn't complete the tasks because they missed a gamepad?

Picture of controllers for NES, SNES, Nintendo64, PlayStation 2, PlayStation 3, drumset, and DDR mat

Different gamepads ready for the workshop... I ended up only using one (or two).

With that in mind, I created a soft Gamepad Simulator. A small JavaScript snippet that generates an on-screen controller that triggers and behaves just like a physical gamepad connected to the browser:

  • It triggers the gamepadconnected and gamepaddisconnected events.
  • It generates a gamepad object with all the standard properties.
  • It updates the values of the gamepad object when the user interacts with the virtual gamepad.

Here is a demo of the Gamepad Simulator at work:

It may be a basic thing at the moment, but it could be helpful for people that want to try and develop with the Gamepad API but don't have a physical gamepad available for any reason.

...which was the case in the presentation.

How it works

The initial code is fairly simple. It can be found on this Codepen –although this demo maybe better to appreciate the behavior–, and the project continues on GitHub.

The module creates an object named gamepadSimulator that has 4 methods:

create()

This method sets the whole environment in place to use the gamepad:

  • It generates an SVG picture of a generic-looking gamepad and places it on the screen.
  • It adds an id to the image so it can be styled and customized.
  • It generates fake information for a standard 17-button gamepad.
  • It associates all the events to the buttons and axes, so the information of the gamepad is updated upon user action.
  • It replaces the navigator.getGamepad() with its own method that will return the fake gamepad.

Gamepad with buttons 0-16 and 2 axes

Screenshot of the virtual keyboard on a web page

It is necessary to call this function at first... which may seem unnecessary; why not directly run all these actions when importing the module?

We could do that, but the idea is to expand the module in the future and allow for some customization of the gamepad (which would require this create() method.

connect()

This method will trigger the gamepadconnected event with the gamepad information generated in the create() function.

To do so, we make use of the Event interface to create a custom event of the type gamepadconnected, and then we dispatch it with dispatchEvent:

const event = new Event("gamepadconnected");
// update fakecontroller initial information
event.gamepad = gamepadSimulator.fakeController;
window.dispatchEvent(event);
Enter fullscreen mode Exit fullscreen mode

The dispatched event will be treated as the regular event triggered after connecting a gamepad to the browser.

disconnect()

This method will trigger the gamepaddisconnected event with the current information from the gamepad generated in the create() function. It is created and dispatched/triggered in the same way as the gamepadconnected event described in the previous section.

It is important to call this function after completing the tests. Otherwise, the gamepad won't be disconnected and the app –not the module– may keep running affecting performance.

destroy()

This method performs necessary clean-up after the gamepad has been used:

  • Calls the disconnect() method (just in case).
  • Removes the gamepad image from the screen.
  • Reinstantiates the original navigator.getGamepad() function.

What's next?

As of right now, the code and functionality are fairly basic. They get the job done, but they lack many options and customizations.

I would like to improve some things like:

  • Allowing for diagonal moves of the axes (and different intensities).
  • Adding options to generate non-standard gamepads.
  • Adding customization so users could create a gamepad that fits their needs (e.g. number of buttons, number of axes, etc.)
  • Having different gamepad faces and not just the generic one.
  • Making it easier to export/import the project.

As I mentioned above, the project is on GitHub (and at a really early stage), so any suggestion/recommendation/help will be welcomed.

Top comments (0)