Creating a Physical Game Controller

by SteevAtBlueDust in Circuits > Arduino

3387 Views, 16 Favorites, 0 Comments

Creating a Physical Game Controller

spacebounce.png
in-play.jpg

When the Nintendo Wii was launched players were encouraged, nay required, to leave the sofa and jump, dance, and jiggle in order to score points in their game of choice. While there is a steep learning curve in building for the Wii, it is easy to build a custom device that lets you control a game by physically jumping on pressure pads at the appropriate time.

This instructable shows how I adapted the game 'Space Bounce' (playable live at https://marquisdegeek.com/spacebounce/ with the source at https://github.com/MarquisdeGeek/SpaceBounce) to use a physical controller.

Supplies

  • Arduino
  • Two pressure mats (mine were from Maplin
  • Two resistors, for the pressure mat (100 K, but most are fine)
  • Two LEDs (optional)
  • Two resistors, for the LEDs (100 K, but most are fine. Also optional)
  • Laptop

Jump Around!

two mats.jpg

I began by designing the jumping interface and, on review of the game, realised that having two mats would best express its core idea. That is, you stand on the left mat to simulate the feeling of holding onto the left wall and, at the appropriate moment, jump across to the right mat, and your on-screen character would do the same.

Connecting the Pads

20190826_093639.jpg
bounce_bb.png

So I bought two mats, and got to work. The pressure mats shown here are the simplest (and cheapest!) I found, at £10 each. They have four wires, two of which act like a simple switch: when you stand on the mat, a connection is made, and when you jump up it is broken. I fed this into an Arduino with this basic circuit.

Tripping the Light Fantastic

20190826_093711.jpg

It worked, but wasn't very inspiring. So, I added some LEDs to indicate the state of each pressure mat.

The LEDs are not required to play the game, but by adding them to the circuit I could easily see what the circuit thought was the current state. Therefore, if the game didn't react correctly, I could work out if the problem was with the circuit, Arduino software, or the game logic.

Starting to Code

Given the original game was in JavaScript, decided to I write a NodeJS program which listens for changes in the pressure mat state, and sends the data via websockets to the game client.

First, install the standard firmata onto your Arduino so that we can run a Node server on the PC and use the Johnny Five library to listen for the state changes from the Arduino. Then add Express to serve up the game content.

The entire server code looks like this:

const express = require('express');
const app = express();
const http = require('http');
const server = http.createServer(app);
const io = require('socket.io').listen(server);

const arduino = require('arduino-controller');

server.listen(3000, function() {
  console.log('Express server listening...');
});

app.use('/', express.static('app'));

const five = require("johnny-five");
const board = new five.Board({
  repl: false
});

board.on("ready", function() {
  let green = new five.Led(5);
  let red = new five.Led(6);
  let left = new five.Pin(2);
  let right = new five.Pin(3);

  io.on('connect', function(socket) {
    console.log('We are connected!');
    let lastLeft = false;
    let lastRight = false;

    five.Pin.read(left, (err, val) => {
      if (val) {
        green.on();
      } else {
        green.off();
      }

      if (val !== lastLeft) {
        lastLeft = val;
        let state = {
          side: 'left',
          state: val ? 'down' : 'up'
        }
        socket.emit('arduino::state', JSON.stringify(state), {
          for: 'everyone'
        });
      }

    })

    five.Pin.read(right, (err, val) => {
      if (val) {
        red.on();
      } else {
        red.off();
      }
      //
      if (val !== lastRight) {
        lastRight = val;
        let state = {
          side: 'right',
          state: val ? 'down' : 'up'
        }
        socket.emit('arduino::state', JSON.stringify(state), {
          for: 'everyone'
        });

      }
    })
  });

});


And is run with:

node server.js

Adapting the Game

The first problem was the interface; how do you 'click' on the play button when all you can do is jump? I solved this by eliminating all the other buttons! I can then trigger the remaining button whenever the player jumps, by listening for either 'up' event.

<script type="text/javascript" src="/socket.io/socket.io.js"></script>

socket = io();

socket.on('arduino::state', function(msg){
  let data = JSON.parse(msg);
  if (data.state === 'up') {
    // we're jumping!
  }
});

From here I was able to get into the game, and use the pads for something more fun - the game itself.

Changing the Player Jump Code

This time I would need to deal with each pad individually, and make the character start jumping whenever the player's foot leaves the pad. The time for the on-screen character to traverse the mine shaft is longer than the time for the player to jump from one side to the side. This is a good thing, since it gives the player a chance to regain their balance, check their footing, and watch the player complete the jump on-screen. Had this not been the case, I would have slowed the player down.

socket = io();

socket.on('arduino::state', function(msg){ let data = JSON.parse(msg); if (data.side === 'left' && data.state === 'up') { // we're jumping up from left side } });

Changing the Output

With the input mechanism working, I needed to work on the output. The game plays well on a tablet or phone, because it fills the screen. But, when you're jumping around, it's too small to see, so the play area on the screen needs to be enlarged. A lot!

Unfortunately, enlarging all the graphical assets is a very time-consuming task. So, I cheated! Since the game doesn't need to understand the X, Y position of a mouse click, or touch event, I can simple re-scale the whole canvas!

This involved a hack on both the CSS and JavaScript so that the existing HTML5 canvas object runs full-screen.

Furthermore, the game is played in portrait mode which meant to make the maximum use of screen real estate we needed to rotate the canvas by 90 degrees.

#SGXCanvas {
    position: absolute;
    z-index: 0;
    transform: rotate(-90deg);
    transform-origin: top right;
    width: auto;
}

It Works!

20190826_133837(0).jpg

For my first game I tilted my laptop on its side, and played like this.

Preparing the Room

20190826_135011.jpg

Building a physical controller is only the start of the journey, not the end. The rest of the physical space needs to be considered.

Firstly, the pressure mats moved around on the floor when you landed on them. This was easily fixed with some double-sided sticky pads. They work well, but probably wouldn't hold up to a lot of wear-and-tear.

Next, the laptop looks a bit silly, which distracts you from the game itself. So, the TV from the lounge was "borrowed" and taken to the local MakerSpace, where it was positioned against the wall and connected.

In the future it'd be nice to add footprints onto the pressure mats (perhaps of Neil Armstrong's first moon print!) to guide the player. Also a better casing and surround for the TV would add to the feel. Perhaps those of you with a lot of time and space could make a paper mache rock face, placed either side of the mats, to mimic the claustrophobic feel of falling down a mine shaft!

It's Complete!

Space Bounce - the physical controller

And there you have it. An easy day project that enhances the original game, and keeps you fit while playing it!

You could also use a Makey Makey which directly simulates the key presses used in the original game, to minimise some of this work. But that is left as an exercise for the reader :)

All the code is in a special branch in the Space Bounce repo: https://github.com/MarquisdeGeek/SpaceBounce/tree/...