Introduction: Run and Jump Arduino LCD Game

Picture of Run and Jump Arduino LCD Game

The Arduino is a really easy way to get started in electronics, it is simple to design a circuit around the arduino boards and easy to program using the arduino IDE. The arduino may not be as powerful as the raspberry pi or Intel Edison but it is cheap and easy to design projects.

The arduino can be connected to different types of LCD displays and OLED displays, this gives the possibility of a large number of projects that display the sensor outputs on the LCD or OLED. But what's more is that these displays even support low resolution graphics which enable us to design low resolution pictures and even sprites and objects for games.

In this instructable I'm going to show you How to build an arduino powered low resolution game with sprites and objects. The game is simple and involves running and jumping over objects or walls by clicking a button.

Step 1: Tools and Components

Picture of Tools and Components

For this project you will need,

  • Arduino Nano
  • LCD Display
  • I2C LCD Driver
  • BreadBoard
  • Push Button
  • Connecting Wires
  • Mini USB Cable

Step 2: LCD

Picture of LCD

Arduino is capable of working with the LCD with the help of pre-compiled librarys built by the large arduino community. For this Instructable we will be using a 16x2 LCD, the 16x2 indicates 16 columns and 2 rows where any characters can be displayed.

You could directly connect the LCD to arduino nano, but this is not recommended as it uses a lot of I/O pins. A more simpler option would be to use an I2C LCD Driver, which does the same job as connecting the LCD Directly to arduino but uses only two analog pin instead.

The I2C LCD driver which I used is by Hitachi it is based on the PCF8574, which is a general purpose bidirectional 8 bit I/O port expander that uses the I2C protocol. If you solder bridges over the A0, A1, A2 jumpers on the board, this would change the I2C address of the board, so you can connect multiple I2C LCD with different addresses.

Step 3: Circuit

Picture of Circuit

The circuit for this project is simple it has an arduino nano connected to an LCD over I2C and in the arduino nano the analog pin a4 indicates SDA pin of I2C and analog pin a5 indicates SCL pin of I2C. A button is connected to the arduino Digital pin 2 which acts as an interrupt and causes the player to jump and to start the game over.

If you riged up the circuit correctly and see nothing on your LCD screen, then you may have to change the pot on the I2C driver which changes the contrast level of the LCD and if you still do not see anything try checking the address of the I2C driver.

Step 4: Code

Picture of Code

The code for this project can be found below you will need to install the arduino IDE and install it, to upload programs to the board. Furthermore, you need to download the arduino I2C library and then copy the code from below and paste it in the arduino IDE. You may also want to change the I2C address based on what your board uses the default is 0x3F.

#include "Wire.h" // For I2C
#include "LCD.h" // For LCD #include "LiquidCrystal_I2C.h" // Added library* #define PIN_BUTTON 2 #define PIN_AUTOPLAY 1 #define PIN_READWRITE 10 #define PIN_CONTRAST 12

#define SPRITE_RUN1 1 #define SPRITE_RUN2 2 #define SPRITE_JUMP 3 #define SPRITE_JUMP_UPPER '.' // Use the '.' character for the head #define SPRITE_JUMP_LOWER 4 #define SPRITE_TERRAIN_EMPTY ' ' // User the ' ' character #define SPRITE_TERRAIN_SOLID 5 #define SPRITE_TERRAIN_SOLID_RIGHT 6 #define SPRITE_TERRAIN_SOLID_LEFT 7

#define HERO_HORIZONTAL_POSITION 1 // Horizontal position of hero on screen

#define TERRAIN_WIDTH 16 #define TERRAIN_EMPTY 0 #define TERRAIN_LOWER_BLOCK 1 #define TERRAIN_UPPER_BLOCK 2

#define HERO_POSITION_OFF 0 // Hero is invisible #define HERO_POSITION_RUN_LOWER_1 1 // Hero is running on lower row (pose 1) #define HERO_POSITION_RUN_LOWER_2 2 // (pose 2)

#define HERO_POSITION_JUMP_1 3 // Starting a jump #define HERO_POSITION_JUMP_2 4 // Half-way up #define HERO_POSITION_JUMP_3 5 // Jump is on upper row #define HERO_POSITION_JUMP_4 6 // Jump is on upper row #define HERO_POSITION_JUMP_5 7 // Jump is on upper row #define HERO_POSITION_JUMP_6 8 // Jump is on upper row #define HERO_POSITION_JUMP_7 9 // Half-way down #define HERO_POSITION_JUMP_8 10 // About to land

#define HERO_POSITION_RUN_UPPER_1 11 // Hero is running on upper row (pose 1) #define HERO_POSITION_RUN_UPPER_2 12 // (pose 2)

LiquidCrystal_I2C lcd(0x3f,2,1,0,4,5,6,7); static char terrainUpper[TERRAIN_WIDTH + 1]; static char terrainLower[TERRAIN_WIDTH + 1]; static bool buttonPushed = false;

void initializeGraphics(){ static byte graphics[] = { // Run position 1 B01100, B01100, B00000, B01110, B11100, B01100, B11010, B10011, // Run position 2 B01100, B01100, B00000, B01100, B01100, B01100, B01100, B01110, // Jump B01100, B01100, B00000, B11110, B01101, B11111, B10000, B00000, // Jump lower B11110, B01101, B11111, B10000, B00000, B00000, B00000, B00000, // Ground B11111, B11111, B11111, B11111, B11111, B11111, B11111, B11111, // Ground right B00011, B00011, B00011, B00011, B00011, B00011, B00011, B00011, // Ground left B11000, B11000, B11000, B11000, B11000, B11000, B11000, B11000, }; int i; // Skip using character 0, this allows lcd.print() to be used to // quickly draw multiple characters for (i = 0; i < 7; ++i) { lcd.createChar(i + 1, &graphics[i * 8]); } for (i = 0; i < TERRAIN_WIDTH; ++i) { terrainUpper[i] = SPRITE_TERRAIN_EMPTY; terrainLower[i] = SPRITE_TERRAIN_EMPTY; } }

// Slide the terrain to the left in half-character increments // void advanceTerrain(char* terrain, byte newTerrain){ for (int i = 0; i < TERRAIN_WIDTH; ++i) { char current = terrain[i]; char next = (i == TERRAIN_WIDTH-1) ? newTerrain : terrain[i+1]; switch (current){ case SPRITE_TERRAIN_EMPTY: terrain[i] = (next == SPRITE_TERRAIN_SOLID) ? SPRITE_TERRAIN_SOLID_RIGHT : SPRITE_TERRAIN_EMPTY; break; case SPRITE_TERRAIN_SOLID: terrain[i] = (next == SPRITE_TERRAIN_EMPTY) ? SPRITE_TERRAIN_SOLID_LEFT : SPRITE_TERRAIN_SOLID; break; case SPRITE_TERRAIN_SOLID_RIGHT: terrain[i] = SPRITE_TERRAIN_SOLID; break; case SPRITE_TERRAIN_SOLID_LEFT: terrain[i] = SPRITE_TERRAIN_EMPTY; break; } } }

bool drawHero(byte position, char* terrainUpper, char* terrainLower, unsigned int score) { bool collide = false; char upperSave = terrainUpper[HERO_HORIZONTAL_POSITION]; char lowerSave = terrainLower[HERO_HORIZONTAL_POSITION]; byte upper, lower; switch (position) { case HERO_POSITION_OFF: upper = lower = SPRITE_TERRAIN_EMPTY; break; case HERO_POSITION_RUN_LOWER_1: upper = SPRITE_TERRAIN_EMPTY; lower = SPRITE_RUN1; break; case HERO_POSITION_RUN_LOWER_2: upper = SPRITE_TERRAIN_EMPTY; lower = SPRITE_RUN2; break; case HERO_POSITION_JUMP_1: case HERO_POSITION_JUMP_8: upper = SPRITE_TERRAIN_EMPTY; lower = SPRITE_JUMP; break; case HERO_POSITION_JUMP_2: case HERO_POSITION_JUMP_7: upper = SPRITE_JUMP_UPPER; lower = SPRITE_JUMP_LOWER; break; case HERO_POSITION_JUMP_3: case HERO_POSITION_JUMP_4: case HERO_POSITION_JUMP_5: case HERO_POSITION_JUMP_6: upper = SPRITE_JUMP; lower = SPRITE_TERRAIN_EMPTY; break; case HERO_POSITION_RUN_UPPER_1: upper = SPRITE_RUN1; lower = SPRITE_TERRAIN_EMPTY; break; case HERO_POSITION_RUN_UPPER_2: upper = SPRITE_RUN2; lower = SPRITE_TERRAIN_EMPTY; break; } if (upper != ' ') { terrainUpper[HERO_HORIZONTAL_POSITION] = upper; collide = (upperSave == SPRITE_TERRAIN_EMPTY) ? false : true; } if (lower != ' ') { terrainLower[HERO_HORIZONTAL_POSITION] = lower; collide |= (lowerSave == SPRITE_TERRAIN_EMPTY) ? false : true; } byte digits = (score > 9999) ? 5 : (score > 999) ? 4 : (score > 99) ? 3 : (score > 9) ? 2 : 1; // Draw the scene terrainUpper[TERRAIN_WIDTH] = '\0'; terrainLower[TERRAIN_WIDTH] = '\0'; char temp = terrainUpper[16-digits]; terrainUpper[16-digits] = '\0'; lcd.setCursor(0,0); lcd.print(terrainUpper); terrainUpper[16-digits] = temp; lcd.setCursor(0,1); lcd.print(terrainLower); lcd.setCursor(16 - digits,0); lcd.print(score);

terrainUpper[HERO_HORIZONTAL_POSITION] = upperSave; terrainLower[HERO_HORIZONTAL_POSITION] = lowerSave; return collide; }

// Handle the button push as an interrupt void buttonPush() { buttonPushed = true; }

void setup(){ pinMode(PIN_READWRITE, OUTPUT); digitalWrite(PIN_READWRITE, LOW); pinMode(PIN_CONTRAST, OUTPUT); digitalWrite(PIN_CONTRAST, LOW); pinMode(PIN_BUTTON, INPUT); digitalWrite(PIN_BUTTON, HIGH); pinMode(PIN_AUTOPLAY, OUTPUT); digitalWrite(PIN_AUTOPLAY, HIGH); // Digital pin 2 maps to interrupt 0 attachInterrupt(0/*PIN_BUTTON*/, buttonPush, FALLING); initializeGraphics(); lcd.begin(16, 2); lcd.setBacklightPin(3,POSITIVE); // BL, BL_POL lcd.setBacklight(HIGH); }

void loop(){ static byte heroPos = HERO_POSITION_RUN_LOWER_1; static byte newTerrainType = TERRAIN_EMPTY; static byte newTerrainDuration = 1; static bool playing = false; static bool blink = false; static unsigned int distance = 0; if (!playing) { drawHero((blink) ? HERO_POSITION_OFF : heroPos, terrainUpper, terrainLower, distance >> 3); if (blink) { lcd.setCursor(0,0); lcd.print("Press Start"); } delay(250); blink = !blink; if (buttonPushed) { initializeGraphics(); heroPos = HERO_POSITION_RUN_LOWER_1; playing = true; buttonPushed = false; distance = 0; } return; }

// Shift the terrain to the left advanceTerrain(terrainLower, newTerrainType == TERRAIN_LOWER_BLOCK ? SPRITE_TERRAIN_SOLID : SPRITE_TERRAIN_EMPTY); advanceTerrain(terrainUpper, newTerrainType == TERRAIN_UPPER_BLOCK ? SPRITE_TERRAIN_SOLID : SPRITE_TERRAIN_EMPTY); // Make new terrain to enter on the right if (--newTerrainDuration == 0) { if (newTerrainType == TERRAIN_EMPTY) { newTerrainType = (random(3) == 0) ? TERRAIN_UPPER_BLOCK : TERRAIN_LOWER_BLOCK; newTerrainDuration = 2 + random(10); } else { newTerrainType = TERRAIN_EMPTY; newTerrainDuration = 10 + random(10); } } if (buttonPushed) { if (heroPos <= HERO_POSITION_RUN_LOWER_2) heroPos = HERO_POSITION_JUMP_1; buttonPushed = false; }

if (drawHero(heroPos, terrainUpper, terrainLower, distance >> 3)) { playing = false; // The hero collided with something. Too bad. } else { if (heroPos == HERO_POSITION_RUN_LOWER_2 || heroPos == HERO_POSITION_JUMP_8) { heroPos = HERO_POSITION_RUN_LOWER_1; } else if ((heroPos >= HERO_POSITION_JUMP_3 && heroPos <= HERO_POSITION_JUMP_5) && terrainLower[HERO_HORIZONTAL_POSITION] != SPRITE_TERRAIN_EMPTY) { heroPos = HERO_POSITION_RUN_UPPER_1; } else if (heroPos >= HERO_POSITION_RUN_UPPER_1 && terrainLower[HERO_HORIZONTAL_POSITION] == SPRITE_TERRAIN_EMPTY) { heroPos = HERO_POSITION_JUMP_5; } else if (heroPos == HERO_POSITION_RUN_UPPER_2) { heroPos = HERO_POSITION_RUN_UPPER_1; } else { ++heroPos; } ++distance; digitalWrite(PIN_AUTOPLAY, terrainLower[HERO_HORIZONTAL_POSITION + 2] == SPRITE_TERRAIN_EMPTY ? HIGH : LOW); } delay(100); }

Step 5: Going Further

Picture of Going Further

After you have uploaded the code it is play time. The game is simple all you need to do is run and jump, you can jump over objects by clicking the button at the right time.

Troubleshooting

  • If you made the connections as per the circuit and yet see nothing on the LCD try adjusting the contrast of the LCD by changing the value of the pot with the help of a screw driver.
  • IF you do not see anything on the LCD even by changing the pot check the address of the LCD, the default address is 0x3f.
  • Code doesn't upload, make sure you have selected the right board from the tools menu and the right COM port.

Comments

Brad0Barnes07 (author)2017-08-10

your clever

About This Instructable

946views

19favorites

License:

Bio: I love DIY Electronics
Add instructable to: