Chapter 7

Nintendo DS Input Systems

Overview

The Nintendo DS has many different user input systems, including buttons, touch screen, and a microphone. Most video game systems only have buttons and an analog stick or two. While the Nintendo DS does not have an analog stick, it does have an amazing touch screen which has millions of different creative uses. We will only cover the touch screen and buttons, though. If you wish to learn more about the microphone, I'd recommend reading, although a bit outdated, the only microphone resource I know of http://www.double.co.nz/nintendo_ds/nds_develop9.html.

Key Input

libnds provides us with a very nice abstraction for key input. Instead of having to AND registers with cryptic masks to discover which keys we are pressing, we simply call scanKeys(), then check one of three input functions, keysDown(), keysHeld(), or keysUp(). In order to see which keys have been recently pressed, use keysDown(). To see which keys are currently held, use keysHeld(). To see which keys have just been released, use keysUp(). libnds provides us with defines for some key masks as well. How they are set up is explained in Table 8.1, “libnds Key Defines”.

Table 8.1. libnds Key Defines

Key DefineMask BitAssociated Input
KEY_A1 << 0A Button
KEY_B1 << 1B Button
KEY_SELECT1 << 2Select Button
KEY_START1 << 3Start Button
KEY_RIGHT1 << 4Right D-pad
KEY_LEFT1 << 5Left D-pad
KEY_UP1 << 6p D-pad
KEY_DOWN1 << 7Down D-pad
KEY_R1 << 8R Button
KEY_L1 << 9L Button
KEY_X1 << 10X Button
KEY_Y1 << 11Y Button
KEY_TOUCH1 << 12Pen Touching Screen (no coordinates)
KEY_LID1 << 13Lid shutting (useful for sleeping)

Touch!

Utada Hikaru (https://www.youtube.com/watch?v=bYoVeIGl0ao) wants to touch it. You have to code it. Let's go!

Utada Hikaru

The touch screen a big part of what makes the Nintendo DS awesome. The libnds API for using it is cake easy too. We'll be ready for Utada Hikaru in no time. Whenever you want to read the current touch location, simply call the function touchRead(). This function assigns values to a struct that contains the x and y coordinate of the touch. You use it like so.

  /* To read from the touch screen, we can do this stuff. */
  touchPosition touch;
  touchRead(&touch);
  touch->px;  // This contains the x pixel location.
  touch->py;  // This contains the y pixel location.

What is the IPC?

You may see code that uses something called the IPC to interact with the touch screen. Use of the IPC struct is deprecated. This means that you shouldn't use it and should not depend on it being there in the future. The method we use to read values from the touch screen does not use the IPC struct and is safe for future use.

Writing an Input Updating Function

Now that we know a bit about how input is laid out on the Nintendo DS, let's write a function in our main.cpp to collect the input for us. We'll call it updateInput(). We'll use this function as the first thing we call in our complete game loop.

void updateInput(touchPosition *touch) {
  // Update the key registers with current values.
  scanKeys();

  // Update the touch screen values.
  touchRead(touch);
}

Writing an Input Handling Function

This function will be part of our game state updating game loop component. It will react to outside input and modify the game state accordingly. As such, it will have to know about and be able to modify the game state. We'll pass the game state to it as function parameters. Let's call the function handleInput().

First, we want the ship to accelerate when we press up. To do this, we detect when the Nintendo DS has the up key on the D-pad held (which included the initial down press) and accelerate the ship if so. The up key will constantly read as held, so long as it is held. Reading the input does not affect the keys register. We'll do similar things for each of the other keys. See if you can tell what each key does from the code listing below.

void handleInput(Ship *ship, MathVector2D<int> *moonPos, SpriteInfo *moonInfo,
                 touchPosition *touch) {
  /* Handle up and down parts of D-Pad. */
  if (keysHeld() & KEY_UP) {
    // accelerate ship
    ship->accelerate();
  } else if (keysHeld() & KEY_DOWN) {
    // reverse ship direction
    ship->reverseTurn();
  }

  /* Handle left and right parts of D-Pad. */
  if (keysHeld() & KEY_LEFT) {
    // rotate counter clockwise
    ship->turnCounterClockwise();
  } else if (keysHeld() & KEY_RIGHT) {
    // rotate clockwise
    ship->turnClockwise();
  }

  /*
   *  Handle the touch screen.
   *
   *  This is basically some fancy pants junk to enable grabbing and moving
   *  of the moon. It isn't essential to know how this code works to
   *  understand how to reach values from the touch screen, but it was cool
   *  enough that I wanted to put it in the case study.
   */
  static MathVector2D<int> moonGrip;
  if (keysDown() & KEY_TOUCH) {
    /* Record the grip */
    moonGrip.x = touch->px;
    moonGrip.y = touch->py;
  } else if (keysHeld() & KEY_TOUCH) {
    int newX = moonPos->x + touch->px - moonGrip.x;
    int newY = moonPos->y + touch->py - moonGrip.y;

    /* Prevent dragging off the screen */
    if (newX < 0) {
      moonPos->x = 0;
    } else if (newX > (SCREEN_WIDTH - moonInfo->width)) {
      moonPos->x = SCREEN_WIDTH - moonInfo->width;
    } else {
      moonPos->x = newX;
    }
    if (newY < 0) {
      moonPos->y = 0;
    } else if (newY > (SCREEN_HEIGHT - moonInfo->height)) {
      moonPos->y = SCREEN_HEIGHT - moonInfo->height;
    } else {
      moonPos->y = newY;
    }

    /* Record the grip again. */
    moonGrip.x = touch->px;
    moonGrip.y = touch->py;
  }
}

As you've noticed, having that Ship class made input handling extremely easy. Our keys will directly affect various properties of the ship as we press them. This is really amazing, but the true miracle is yet to come.

As for the moon, since we didn't make one class for it, the code came together a little more messily. We also had to pass two parameters to the handleInput() function to represent the moon portion of the game state instead of one. If we had wanted to be more clean, we could have even produced a game state struct that contained all the game state and all operations on it in one place.

Creating the Main Game Loop, Again

Let's check back in on our main() function now. We need to make some adjustments to our game loop since we've now added the ability to collect input and to react to input. The first thing we now want to happen in our game loop is for the key registers and read touch screen coordinates to get updated. We make a call to updateInput() and it all happens for us. Next, we handle the input we just received by calling our recently created handleInput() function, passing in our game state so that it can change it for us. Everything else is as before.

int main() {
  /*  Turn on the 2D graphics core. */
  powerOn(POWER_ALL_2D);

  /*
   *  Configure the VRAM and background control registers.
   *
   *  Place the main screen on the bottom physical screen. Then arrange the
   *  VRAM banks. Next, confiure the background control registers.
   */
  lcdMainOnBottom();
  initVideo();
  initBackgrounds();

  /* Set up a few sprites. */
  SpriteInfo spriteInfo[SPRITE_COUNT];
  OAMTable *oam = new OAMTable();
  iniOAMTable(oam);
  initSprites(oam, spriteInfo);

  /* Display the backgrounds. */
  displayStarField();
  displayPlanet();
  displaySplash();

  /*************************************************************************/

  /* Keep track of the touch screen coordinates */
  touchPosition touch;

  /* Make the ship object */
  static const int SHUTTLE_AFFINE_ID = 0;
  SpriteEntry *shipEntry = &oam->oamBuffer[SHUTTLE_AFFINE_ID];
  SpriteRotation *shipRotation = &oam->matrixBuffer[SHUTTLE_AFFINE_ID];
  Ship *ship = new Ship(&spriteInfo[SHUTTLE_AFFINE_ID]);

  /* Make the moon */
  static const int MOON_AFFINE_ID = 1;
  SpriteEntry *moonEntry = &oam->oamBuffer[MOON_AFFINE_ID];
  SpriteInfo *moonInfo = &spriteInfo[MOON_AFFINE_ID];
  MathVector2D<int> *moonPos = new MathVector2D<int>();
  moonPos->x = moonEntry->x;
  moonPos->y = moonEntry->y;

  for (;;) {
    /* Update the game state. */
    updateInput(&touch);
    handleInput(ship, moonPos, moonInfo, &touch);
    ship->moveShip();

    /* Update ship sprite attributes. */
    MathVector2D<float> position = ship->getPosition();
    shipEntry->x = (int)position.x;
    shipEntry->y = (int)position.y;
    rotateSprite(shipRotation, -ship->getAngleDeg());
    /* Update moon sprite attributes. */
    moonEntry->x = (int)moonPos->x;
    moonEntry->y = (int)moonPos->y;

    /*
     *  Update the OAM.
     *
     *  We have to copy our copy of OAM data into the actual OAM during
     *  VBlank (writes to it are locked during other times).
     */
    swiWaitForVBlank();
    updateOAM(oam);
  }

  return 0;
}

Compiling

Again, we should double check that we have all the proper includes before compiling.

#include <assert.h>
#include <nds.h>

#include "ship.h"
#include "sprites.h"

/* Backgrounds */
#include "planet.h"
#include "splash.h"
#include "starField.h"
/* Sprites */
#include "moon.h"
#include "orangeShuttle.h"

Now we can control our ship with the D-Pad. What fun! The game should now appear as in Figure 8.1, “Flying around in the Orange Shuttle”. Now if only we had some aliens to kill…

Figure 8.1. Flying around in the Orange Shuttle

Splash

Starfield with Moving Orange Shuttle