The tiny, hackable, Arduino-based video game system

About  \  Buy  \  Build  \  Games  \  FAQ  \  Forum  \  Design+License         



Game Development

You can write your own games to run on Hackvision by using the TVout library and Controllers library. The wonderful TVout library was developed by Myles Metzler. The Hackvision controllers library supports button controllers (like the one onboard), nunchuk, paddles, and SuperNES controllers.

Downloads and Installation

Games and required libraries can be downloaded from the Games page. The general approach is that you Install game code in your Arduino sketch folder, and install the TVout and Controllers libraries in your Arduino libraries folder.
Arduino
    |
    +--some_game
    |    |
    |    +--some_game.pde
    |    +--other files
    |
    +--libraries
              |
              +--TVout
              |     |
              |     +--...many files
              |
              +--Controllers
                    |
                    +--...many files
When compiling/uploading the code using the Arduino IDE, choose "Arduino Uno" from the Tools->Boards menu. If uploading doesn't work, then you may have an older kit with the Duemilanove bootloader, so choose "Arduino Duemilanove w/ ATmega328" from the menu.

Using TVout

The TVout library is used to draw to the screen. Text, lines, boxes, individual pixels, etc. can be drawn to the screen using simple drawing primitives. The library is fully documented on the TVout Google Code site. You can look at the Hackvision firmware preloaded games to see how the library is used to draw to the screen.

To initialize TVout, you must specify a resolution and whether to use the NTSC standard (US, Canada, Japan, parts of South America) or the PAL standard (Europe, Australia, Africa, Asia, parts of South America). Arduino digital pin 12 is connected to the PAL jumper on the Hackvision board and can be used to initialize TVout properly in the setup() method of your game sketch:

TVout tv;

void setup() {
  pinMode(12, INPUT);     // Set pin 12 to INPUT
  digitalWrite(12, HIGH); // Enable pull-up resistor

  // If pin 12 is pulled LOW, then the PAL jumper is shorted.
  if (digitalRead(12) == LOW) {
    tv.begin(_PAL, 136, 96);
  } else {
    tv.begin(_NTSC, 136, 96);
  }
}

Using the Controllers Library

The Controllers library provides support for button controllers, wii nunchuk, paddles, and SuperNES controllers.

Button Controllers

Five-button controllers like the one onboard Hackvision are implemented by the ButtonController class. Each button has a method to check whether it is pressed.

leftPressed() : returns 1 if left button pressed
rightPressed() : returns 1 if right button pressed
upPressed() : returns 1 if up button pressed
downPressed() : returns 1 if down button pressed
firePressed() : returns 1 if fire button pressed

The Controllers library supplies a pre-instantiated ButtonController object representing the onboard controller. This object is called Controller and can be used like this:

if (Controller.leftPressed()) {
  // move left...
}
if (Controller.rightPressed()) {
  // move right...
}
You can add other button controllers for your own hardware by instantiating a ButtonController object with the pins that your controller is connected to. The constructor is:
ButtonController(uint8_t left, uint8_t right, uint8_t up, uint8_t down, uint8_t fire)
The Arduino pins associated with the controller are set to be input pins with the pull-up resistors enabled. So pressing the button should connect it to ground. If your connect some hardware to digital pins 1, 6, 8, 12, and 13 then I would create a button controller like this:
ButtonController myController = ButtonController(1, 6, 8, 12, 13);


Wii Nunchuk Controller

A Wii nunchuk can be connected to your Hackvision as described here. The Wii nunchuk communicates using I2C and it is a bit tricky to use I2C in conjunction with TVout since the TVout library does not work well if hardware interrupts occur during the rendering of the screen. This can be solved by only communicating with the nunchuk during the vertical blanking interval, or VBI. The VBI is the period of time when the television is scanning lines below the picture and retracing the scan line back to the top of the screen. There are quite a few scan lines that are not rendered on the active screen, so it gives us enough time to communicate with the nunchuk device (which takes over 1 millisecond). As a programmer, you don't have to worry about these details as long as you initialize the nunchuk like this:
boolean useNunchuk;

useNunchuk = Nunchuk.init(tv, 4);
The return value stored in useNunchuk will be true if a nunchuk device is connected. We need to pass the TVout object to the nunchuk initialization routine so that TVout can tell the Nunchuk object when it is "safe" to retrieve data from the nunchuk. The second argument tells the Nunchuk object how often data should be retrieved from the nunchuk. In the code above, we are saying that it should only communicate with the nunchuk at most every 4 frames. That is 15 times per second (TVout renders 60 frames per second), which is often enough.

Here's how to get fresh data from the nunchuk. Actual communication will occur only if it a flag has been set in the Nunchuk object by the vertical blanking interval. If we ask for data often enough (in video games you want to ask for user input all the time), then it is highly likely that communication with the nunchuk will happen during the VBI and there will be no video disturbance. Here's how you get data:

if (useNunchuk) {
  Nunchuk.getData();
}
Then to find out what is going on with the nunchuk after asking for fresh data, use these methods defined in the Nunchuk class:

getButtonC() : returns int 1 if C button is pressed, 0 if not pressed
getButtonZ() : returns int 1 if Z button is pressed, 0 if not pressed
getJoystickX() : returns int value. full left is about 25, full right is about 225
getJoystickY() : returns int value. full down is about 25, full up is about 225
getAccelerometerX() : returns int value. lower value if tilted left, higher if tilted right
getAccelerometerY() : returns int value. lower value if tilted back, higher if tilted forward
getAccelerometerZ() : returns int value. lower value if less gravity downward (upside-down controller)



Paddle Controller

Paddle controllers which have a button and a potentiometer can easily be added to your Hackvision as described here. The Controllers library includes a Paddle class which makes it easy to read input from a paddle controller:

buttonPressed : returns 1 if button is pressed
getPosition() : returns value between 0 and 1023 depending on position of potentiometer

The Controllers library supplies pre-instantiated Paddle objects representing paddles connected to the connections marked "PADDLE A" and "PADDLE B" on the Hackvision board. These objects are called PaddleA and PaddleB and can be used like this:

if (PaddleA.buttonPressed()) {
  // do something
}
int x = PaddleB.getPosition();
You can instantiate your own Paddle objects using any Arduino pins using the constructor:
Paddle(uint8_t button, uint8_t potentiometer)
For example, to create a Paddle that uses digital pin 13 for the button and analog pin 0 for the potentiometer:
Paddle myPaddle = Paddle(13, 0);


Tips and Tricks for Game Development

Conserve Memory!

The memory that stores what is drawn on the screen ("frame buffer") consumes a lot of SRAM memory. Don't confuse SRAM with flash memory where your program is stored. There are 32K of flash memory to store your game code, and this is quite a lot. But there are only 2K of SRAM in the ATmega328 chip, and with a resolution of 128x96 pixels, TVout uses 1.5K of that! The preloaded games on Hackvision use a slightly higher resolution of 136x96 pixels, so this consumes 1632 bytes.

What do we need SRAM for? Storage for variables you declare in your game, the program stack (where variables local to a function are stored), numerical constants, and string constants. If you aren't careful, these can add up and use up all your memory. You won't get a nice warning, either. Your TV screen will just go haywire.

So here are my tips for conserving memory:

  • Don't use high resolution. Stay with 128x96 or 136x96.
  • Use only the largest data type for a variable that you need. An int uses 2 bytes, but a char or byte are one byte.
  • Don't let your call stack get too deep. That is, don't write code that calls 10 or 20 functions deep.
  • Delcare text used in strings in the flash memory instead. By using the special Arduino modifier PROGMEM you can store constants in flash memory. See this page on the Arduino site for details.

Saving Scores in EEPROM

The ATmega328 microcontroller has 1K of EEPROM which will retain data even with the power disconnected. 1K is plenty of memory to store high score information. The preloaded game Space Invaders uses EEPROM to store and retrieve high scores. I chose a memory layout that uses 50 bytes per game "file" in EEPROM. Each file contains records for 10 high scores, and each record consists of 5 bytes: 3 bytes for player initials, and 2 bytes for the score. Take a look at the Hackvision firmware source code and copy the approach in your own games. The method enterHighScore() looks in EEPROM to see if your score at the end of a game is high enough to allow you to enter your initials and store it. The method enterInitials() allows entry of player initials. The method displayHighScores() shows them on the screen.