In this post we will see how to read the input from the touch screen and we will begin to lay out the structure of a small vertical-scrolling shoot ’em up game.

 

The touchscreen library

To control the touchscreen integrated into the TFT FeatherWing, we will use the Adafruit STMPE610 controller library which handles all the low level SPI communication with the STMPE610 driver chip. The library installation procedure depends on your IDE, for more information see the related page on the Adafruit website.

Using this library is very easy. First, you need to include the library in main.cpp and create the touchscreen object:

#include <Adafruit_STMPE610.h>
	
// Touch screen
#define STMPE_CS 32
Adafruit_STMPE610 touchScreen = Adafruit_STMPE610(STMPE_CS);

where STMPE_CS is the Chip Select pin for the touch screen driver chip.
Then, in the Arduino setup function, you can start the touchscreen library with:

// Start touch screen library
touchScreen.begin();

As touches are detected, the library stores them sequentially in a buffer; you can ask the library if this buffer is empty using the bufferEmpty() function. If there is data in the buffer, you can get the oldest point in it with the getPoint() function. This function return a TS_Point object, that has x, y and z coordinates. x and y are the coordinate of the touched point, and range from 0 to 4095. The z point correspond to touch pressure and ranges from 0 to 255 (for now, we will ignore it).

The STMPE610 return raw data and doesn’t know anything about screen resolution and rotation, so we will have to take this into account when we calculate the actual coordinates on the screen.

In our game, we will process the touchscreen inputs in the Arduino loop function. We are interested in processing only the last touch detected, so we will read the buffer until we get the last input point. Since there may be no new inputs from the last reading, we will store in a Boolean variable whether there is a new input to be processed or not. Insert this code in the loop function:

// Get last touched screen point
bool screenTouched = false;
TS_Point touchPoint;
while(!touchScreen.bufferEmpty()) {
    screenTouched = true;
    touchPoint = touchScreen.getPoint();
}

 

The game screen

In our game, the screen will be divided into two parts: at the bottom an input area where the player controls his starship, and at the top the actual game screen. Let’s start drawing the input area. First of all, we need to copy the graphic library we developed in the previous posts, include it in our new project and create the GFX object:

#include "GFX.h"

...
	
// GFX library
GFX gfx;

Then, in the setup function, add the code to start the graphic library and to draw the input area:

// Start graphics library
gfx.begin();

// Draw input area
gfx.drawFilledRectangle(0, 320, 320, 160, 13);
gfx.drawFilledRectangle(2, 322, 316, 156, 14);
	
// Draw first frame
gfx.update();

and in the loop function, add the code to update following frames:

// Update screen
gfx.update();

Now, we want to draw a starship in the game screen area. First of all, let’s create a new bitmap.h file where we will store all the game bitmaps, and copy this code:

/* bitmaps.h */

#ifndef _BITMAPS_H
#define _BITMAPS_H

#include <stdint.h>

// Starship 32x32 pixels
uint8_t starshipBitmap[1024] = {
    15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
    15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
    15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,  0,  0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
    15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,  0,  0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
    15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,  0,  0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
    15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 12,  0,  0, 12, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
    15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 12,  0,  0, 12, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
    15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 12,  0,  0, 12, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
    15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,  0, 15, 15, 12,  0,  0, 12, 15, 15,  0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
    15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,  0,  0, 15, 12,  0,  0, 12, 15,  0,  0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
    15, 15,  8, 15, 15, 15, 15, 15, 15, 15, 15, 12,  0,  0, 12,  0,  0, 12,  0,  0, 12, 15, 15, 15, 15, 15, 15, 15, 15,  8, 15, 15,
    15,  8,  8, 15, 15, 15, 15, 15, 15, 15, 15, 15, 12,  0, 12,  0,  0, 12,  0, 12, 15, 15, 15, 15, 15, 15, 15, 15, 15,  8,  8, 15,
    15,  8, 13, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 12, 12,  0,  0, 12, 12, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 13,  8, 15,
    15, 13, 13, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 12,  0,  0, 12, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 13, 13, 15,
    15, 13, 13, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 12,  0,  0, 12, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 13, 13, 15,
    15, 13, 13, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 12,  0, 12, 12,  0, 12, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 13, 13, 15,
    15, 13, 13,  0,  0, 15, 15, 15, 15, 15, 15, 15, 15,  0,  0,  7,  7,  0,  0, 15, 15, 15, 15, 15, 15, 15, 15,  0,  0, 13, 13, 15,
    15, 13, 13,  0,  0,  0,  0, 15, 15, 15, 15, 15,  3,  0, 12,  7,  7, 12,  0,  3, 15, 15, 15, 15, 15,  0,  0,  0,  0, 13, 13, 15,
    15, 14, 13, 12, 12,  0,  0,  0,  0, 15, 15,  3,  0,  0,  7,  7,  7,  7,  0,  0,  3, 15, 15,  0,  0,  0,  0, 12, 12, 13, 14, 15,
    15, 14, 14, 15, 15, 12, 12,  0,  0, 15, 15,  3,  0,  0,  7,  7,  7,  7,  0,  0,  3, 15, 15,  0,  0, 12, 12, 15, 15, 14, 14, 15,
    15, 15, 14, 15, 15, 15, 15, 12, 12,  0,  0,  3,  0,  0,  6,  7,  7,  6,  0,  0,  3,  0,  0, 12, 12, 15, 15, 15, 15, 14, 15, 15,
    15, 15, 15, 15, 15, 15, 15, 15, 15, 12,  0,  3,  0,  0,  0,  6,  6,  0,  0,  0,  3,  0, 12, 15, 15, 15, 15, 15, 15, 15, 15, 15,
    15, 15, 15, 15, 15, 15, 15, 15,  0,  0, 12,  3,  0,  0,  0,  0,  0,  0,  0,  0,  3, 12,  0,  0, 15, 15, 15, 15, 15, 15, 15, 15,
    15, 15, 15, 15, 15, 15, 15,  0,  0, 12, 15, 15,  3, 12, 12,  0,  0, 12, 12,  3, 15, 15, 12,  0,  0, 15, 15, 15, 15, 15, 15, 15,
    15, 15, 15, 15, 15, 15,  0,  0, 12, 15, 15, 15, 15, 15, 15, 12, 12, 15, 15, 15, 15, 15, 15, 12,  0,  0, 15, 15, 15, 15, 15, 15,
    15, 15, 15, 15, 15,  0,  0, 12, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 12,  0,  0, 15, 15, 15, 15, 15,
    15, 15, 15, 15, 15, 12, 12, 15, 15, 15, 15, 15, 15, 15, 15,  2,  2, 15, 15, 15, 15, 15, 15, 15, 15, 12, 12, 15, 15, 15, 15, 15,
    15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,  3,  2,  2,  3, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
    15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,  3,  2,  2,  3, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
    15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,  3,  3,  3,  3, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
    15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,  3,  3, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
    15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15
};

#endif

Let’s go back to main.cpp and include this new file. Then we will create a structure to store the starship’s coordinates, set the initial values and draw the bitmap in setup:

#include "bitmaps.h"

struct Starship {
  float x;
  float y;
};

// Game data
Starship starship;

...

void setup() {
  // Set initial starship position
  starship.x = 160;
  starship.y = 230;

  // Start touch screen library
  touchScreen.begin();

  // Start graphics library
  gfx.begin();

  // Draw input area
  gfx.drawFilledRectangle(0, 320, 320, 160, 13);
  gfx.drawFilledRectangle(2, 322, 316, 156, 14);

  // Draw starship
  gfx.drawTransparentBitmap(starshipBitmap, starship.x-16, starship.y-16, 32, 32, 15);

  // Draw first frame
  gfx.update();
}

 

Processing the input

Currently, the touchscreen input is ignored and the starship always remains in the same position. What we need to do is to add, in the loop function, the code that erases the starship from the current position, calculates its new position and redraws it. To update the position we also need to know how much time has elapsed since the last screen update, so we add a global variable that hold the last update time and that we update at each frame:

unsigned long lastUpdateTime;
	
...
	
void setup() {
  ...
	
  lastUpdateTime = micros();
}

Then in the loop function add:

// Calculate delta time
unsigned long now = micros();
float deltaTime = (now - lastUpdateTime) / 1000000.0;
lastUpdateTime = now;

...

// Erase starship
gfx.drawFilledRectangle(starship.x-16, starship.y-16, 32, 32, 15);

// Update starship position
if(screenTouched) {
  // Map from touchscreen coordinates to screen coordinates
  int tx = map(touchPoint.x, TS_MINX, TS_MAXX, 0, 320);
  int ty = map(touchPoint.y, TS_MINY, TS_MAXY, 0, 480);
  // Ignore touches outside input area
  if(ty > 320) {
    // Set target point for starship movement
    int xTarget = map(tx, 30, 280, 16, 304);
    int yTarget = map(ty, 320, 460, 144, 304);
    xTarget = min(max(xTarget, 16), 304);
    yTarget = min(max(yTarget, 144), 304);
    // Move starship a step towards the target
    starship.x += 24.0 * ((float)xTarget - starship.x) * deltaTime;
    starship.y += 24.0 * ((float)yTarget - starship.y) * deltaTime;
  }
}

// Draw starship
gfx.drawTransparentBitmap(starshipBitmap, starship.x-16, starship.y-16, 32, 32, 15);

// Update screen
gfx.update();

TS_MINX, TS_MAXX, TS_MINY and TS_MAXY are the touchscreen calibration constants. These numbers work for me:

#define TS_MINX 3750
#define TS_MINY 3800
#define TS_MAXX 360
#define TS_MAXY 270

Now we can pilot our starship!

 

Adding a starfield background

The black background on which our starship moves is quite boring. What we want is to add a starfield that move downwards, to give the illusion that our starship is moving in space. In particular, we want to create two levels of stars that move at different speeds, to give a three-dimensional parallax effect.

To begin, add a struct that contains the position and color of the star:

struct Star {
  float x;
  float y;
  uint8_t color;
};
	
...
	
#define FARSTAR_COUNT   60
#define NEARSTAR_COUNT  20
Star farStar[FARSTAR_COUNT];
Star nearStar[NEARSTAR_COUNT];

then, in setup, add the code to randomly generate the position and color of the stars:

// Generate random far stars
for(int i=0; i<FARSTAR_COUNT; i++) {
  farStar[i].x = esp_random() % 320;
  farStar[i].y = esp_random() % 320;
  int rnd = esp_random() % 3;
  if(rnd == 0)
    farStar[i].color = 1;
  else if(rnd == 1)
    farStar[i].color = 7;
  else
    farStar[i].color = 12;
}

// Generate random near stars
for(int i=0; i<NEARSTAR_COUNT; i++) {
  nearStar[i].x = esp_random() % 317;
  nearStar[i].y = esp_random() % 317;
}

Near stars don’t use color because they are drawn with a bitmap, so add to bitmap.h this code:

uint8_t starBitmap[9] = {
  15, 7, 15,
  7,  0, 7,
  15, 7, 15
};

So, back in setup, add the code to draw the stars at initial position:

// Draw far stars
for(int i=0; i<FARSTAR_COUNT; i++)
  gfx.drawPixel(farStar[i].x, farStar[i].y, farStar[i].color);

// Draw near stars
for(int i=0; i<NEARSTAR_COUNT; i++)
  gfx.drawTransparentBitmap(starBitmap, nearStar[i].x, nearStar[i].y, 3, 3, 15);

To update the stars position in the following frame, we will use the same technique we used for the starship: first we erase the stars in the current position, then we recalculate the position and finally we redraw them in the new position. In the loop function, after erasing the starship, add this code:

...

// Erase near stars
for(int i=0; i<NEARSTAR_COUNT; i++)
  gfx.drawFilledRectangle(nearStar[i].x, nearStar[i].y, 3, 3, 15);

// Erase far stars
for(int i=0; i<FARSTAR_COUNT; i++)
  gfx.drawPixel(farStar[i].x, farStar[i].y, 15);
  
// Update stars position
for(int i=0; i<FARSTAR_COUNT; i++) {
  farStar[i].y += 12.0 * deltaTime;
  if(farStar[i].y >= 320) {
    farStar[i].y = 0;
    farStar[i].x = esp_random() % 320;
  }
}
for(int i=0; i<NEARSTAR_COUNT; i++) {
  nearStar[i].y += 36 * deltaTime;
  if(nearStar[i].y >= 318) {
    nearStar[i].y -= 320;
    nearStar[i].x = esp_random() % 317;
  }
}

// Draw far stars
for(int i=0; i<FARSTAR_COUNT; i++)
  gfx.drawPixel(farStar[i].x, farStar[i].y, farStar[i].color);

// Draw near stars
for(int i=0; i<NEARSTAR_COUNT; i++)
  gfx.drawTransparentBitmap(starBitmap, nearStar[i].x, nearStar[i].y, 3, 3, 15);

...

This is the final result:

 

You can find the complete code of this post here.


0 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

By continuing to browse this Website, you consent to the use of cookies. More information

This Website uses:

By continuing to browse this Website without changing the cookies settings of your browser or by clicking "Accept" below, you consent to the use of cookies.

Close