A simple one-level platform game. Developed as part of ELEC2645 at University of Leeds, spring 2015.

Dependencies:   N5110 PinDetect PowerControl mbed

An ARM mbed LPC1768 microcontroller have been used to develop a handheld arcade game in the style of an old-school platformer. This project is entirely my own independent work in all stages of the development; including design, defining project specifications, breadboard prototyping, schematic and PCB layout using CAD, assembly, testing and software development. Due to this being part of the ELEC2645 Embedded Systems Project module at University of Leeds, spring 2015, limitations were given on the available hardware components. Credit is due to the authors of the dependent libraries (N5110, Pin Detect, PowerControl and mbed). I would also like to thank the author of Game Programming Patterns as well as the authors of SFML Game Development for providing me with useful sources for programming design patterns.

/media/uploads/Siriagus/game_assembled.jpg

Project aims

  • Implement simple gameplay:
    • A single, fixed (no scrolling) level.
    • Player can move left to right, jump and shoot.
    • Enemies will drop from the top of the screen.
    • The player gets points for shooting enemies.
    • The player dies when it gets hits by an enemy.
  • Implement a simple menu system.
  • Enable the user to adjust the brightness of the display.
  • Output sound to enhance the user experience.

Software

The program flow is controlled by a finite state machine. The implemented design was inspired by the State design pattern from the books Game Programming Patterns and SFML Game Development. The StateManager class is responsible for updating and rendering the current selected state. It also changes the state based on request from the current state. The framework built for the state machine used in this project makes it easy to add new screens. The different main states (indicated by the background colour) and how the user interaction is shown below: /media/uploads/Siriagus/arcadegameuserinteraction.png

Hardware

Schematic:

/media/uploads/Siriagus/schematic.png

Printed circuit board (PCB):

/media/uploads/Siriagus/pcb.png

Images

A seperate program was written to convert images (png) to text-representation of the maps. Enemies and numbers on the screen are also collected from a sprite-sheet created in the same manner.

/media/uploads/Siriagus/unileeds3.png /media/uploads/Siriagus/newmap2.png

Committer:
Siriagus
Date:
Fri May 08 23:51:26 2015 +0000
Revision:
12:8178fad5e660
Parent:
11:adb68da98262
Child:
13:7ab71c7c311b
Added template drawImage function which can draw images of any dimension.

Who changed what in which revision?

UserRevisionLine numberNew contents of line
Siriagus 7:678873947b29 1 #include "Game.h"
Siriagus 7:678873947b29 2
Siriagus 8:9ac6a428fa26 3 Serial pc(USBTX, USBRX); // TO DELETE - DEBUGGING ONLY
Siriagus 8:9ac6a428fa26 4
Siriagus 8:9ac6a428fa26 5 void Game::init()
Siriagus 8:9ac6a428fa26 6 {
Siriagus 12:8178fad5e660 7 // Set initial values for the player
Siriagus 11:adb68da98262 8 player.x = 40;
Siriagus 11:adb68da98262 9 player.y = 5;
Siriagus 9:da608ae65df9 10 player.width = player.height = 5;
Siriagus 12:8178fad5e660 11 player.onGround = false;
Siriagus 8:9ac6a428fa26 12 }
Siriagus 8:9ac6a428fa26 13
Siriagus 8:9ac6a428fa26 14 // Functions
Siriagus 7:678873947b29 15 void Game::update(float dt)
Siriagus 7:678873947b29 16 {
Siriagus 8:9ac6a428fa26 17 // Handle input, should be its own function
Siriagus 8:9ac6a428fa26 18 switch(input->joystick->getDirection())
Siriagus 8:9ac6a428fa26 19 {
Siriagus 8:9ac6a428fa26 20 case LEFT:
Siriagus 8:9ac6a428fa26 21 case UP_LEFT:
Siriagus 8:9ac6a428fa26 22 case DOWN_LEFT:
Siriagus 8:9ac6a428fa26 23 player.vx = -2;
Siriagus 11:adb68da98262 24 player.facingLeft = true;
Siriagus 8:9ac6a428fa26 25 break;
Siriagus 8:9ac6a428fa26 26
Siriagus 8:9ac6a428fa26 27 case RIGHT:
Siriagus 8:9ac6a428fa26 28 case UP_RIGHT:
Siriagus 8:9ac6a428fa26 29 case DOWN_RIGHT:
Siriagus 8:9ac6a428fa26 30 player.vx = 2;
Siriagus 11:adb68da98262 31 player.facingLeft = false;
Siriagus 8:9ac6a428fa26 32 break;
Siriagus 8:9ac6a428fa26 33
Siriagus 8:9ac6a428fa26 34
Siriagus 8:9ac6a428fa26 35 case UP:
Siriagus 8:9ac6a428fa26 36 //player.vy = -4;
Siriagus 8:9ac6a428fa26 37 break;
Siriagus 8:9ac6a428fa26 38 case DOWN:
Siriagus 8:9ac6a428fa26 39 player.vy += 1;
Siriagus 8:9ac6a428fa26 40 break;
Siriagus 8:9ac6a428fa26 41
Siriagus 8:9ac6a428fa26 42 case CENTER:
Siriagus 8:9ac6a428fa26 43 player.vx = 0;
Siriagus 8:9ac6a428fa26 44 break;
Siriagus 8:9ac6a428fa26 45 }
Siriagus 7:678873947b29 46
Siriagus 11:adb68da98262 47 // Ground, temporary solution, to be fixed with real map
Siriagus 11:adb68da98262 48 player.vy += 1;
Siriagus 8:9ac6a428fa26 49
Siriagus 11:adb68da98262 50 // Check if player is trying to jump. Player can only jump if it's on the ground
Siriagus 12:8178fad5e660 51 if (input->read(Input::ButtonA) && player.onGround)
Siriagus 8:9ac6a428fa26 52 {
Siriagus 8:9ac6a428fa26 53 player.vy = -4;
Siriagus 12:8178fad5e660 54 player.onGround = false;
Siriagus 8:9ac6a428fa26 55 }
Siriagus 8:9ac6a428fa26 56
Siriagus 11:adb68da98262 57 // Terminal velocity 3 px/update
Siriagus 8:9ac6a428fa26 58 if (player.vy > 3) player.vy = 3;
Siriagus 8:9ac6a428fa26 59
Siriagus 11:adb68da98262 60 // Update player position - should be done step wise! TODO!!
Siriagus 11:adb68da98262 61
Siriagus 11:adb68da98262 62 // Find direction of player
Siriagus 11:adb68da98262 63
Siriagus 11:adb68da98262 64 // Collision player with map
Siriagus 11:adb68da98262 65
Siriagus 11:adb68da98262 66 int x = player.x;
Siriagus 11:adb68da98262 67 int y = player.y;
Siriagus 11:adb68da98262 68 int steps = abs(player.vx); // how many units (pixels) the player should move in said direction
Siriagus 11:adb68da98262 69 bool collision; // true if colliding
Siriagus 11:adb68da98262 70
Siriagus 11:adb68da98262 71 // Check x-axis
Siriagus 11:adb68da98262 72 if (player.vx > 0) // moving right
Siriagus 11:adb68da98262 73 {
Siriagus 11:adb68da98262 74 int playerRight = x + player.width - 1; // Need to check right border of player, since it is moving right
Siriagus 11:adb68da98262 75
Siriagus 11:adb68da98262 76 while(steps--) // While it still have more movement left
Siriagus 11:adb68da98262 77 {
Siriagus 11:adb68da98262 78 collision = false;
Siriagus 11:adb68da98262 79 for (int i = 0; i < player.height; ++i) // Loop through all vertical points on the right hand side of the player (y+i)
Siriagus 11:adb68da98262 80 {
Siriagus 11:adb68da98262 81 if (map[y+i][playerRight+1]) // If moving to the right leads to collision for given y+i
Siriagus 11:adb68da98262 82 {
Siriagus 11:adb68da98262 83 collision = true; // Then collision is true
Siriagus 11:adb68da98262 84 break; // Skip the for loop, no need for further testing
Siriagus 11:adb68da98262 85 }
Siriagus 11:adb68da98262 86 }
Siriagus 11:adb68da98262 87
Siriagus 11:adb68da98262 88 if (collision) // If collision
Siriagus 11:adb68da98262 89 break; // skip the while loop, player can not move further, even though its velocity is higher
Siriagus 11:adb68da98262 90 else
Siriagus 11:adb68da98262 91 ++playerRight; // Move player one px to the right
Siriagus 11:adb68da98262 92 }
Siriagus 11:adb68da98262 93
Siriagus 11:adb68da98262 94 player.x = playerRight - (player.width - 1); // Update player's position. Need to set upper-left pixel.
Siriagus 11:adb68da98262 95 }
Siriagus 11:adb68da98262 96 else // moving left
Siriagus 11:adb68da98262 97 {
Siriagus 11:adb68da98262 98 while(steps--) // While still movement left
Siriagus 11:adb68da98262 99 {
Siriagus 11:adb68da98262 100 collision = false;
Siriagus 11:adb68da98262 101
Siriagus 11:adb68da98262 102
Siriagus 11:adb68da98262 103 for (int i = 0; i < player.height; ++i) // Check for all y-positions
Siriagus 11:adb68da98262 104 {
Siriagus 11:adb68da98262 105 if (map[y+i][x-1]) // If solid block
Siriagus 11:adb68da98262 106 {
Siriagus 11:adb68da98262 107 collision = true;
Siriagus 11:adb68da98262 108 break; // Collision detected, no further testing required
Siriagus 11:adb68da98262 109 }
Siriagus 11:adb68da98262 110 }
Siriagus 11:adb68da98262 111
Siriagus 11:adb68da98262 112 if (collision)
Siriagus 11:adb68da98262 113 break;
Siriagus 11:adb68da98262 114 else
Siriagus 11:adb68da98262 115 --x; // Move to the left if no collision is detected
Siriagus 11:adb68da98262 116 }
Siriagus 11:adb68da98262 117
Siriagus 11:adb68da98262 118 player.x = x;
Siriagus 11:adb68da98262 119 }
Siriagus 8:9ac6a428fa26 120
Siriagus 11:adb68da98262 121 // Check collision with map in y-direction - works the same way as the x-axis, except for other axis
Siriagus 11:adb68da98262 122
Siriagus 11:adb68da98262 123 x = player.x;
Siriagus 11:adb68da98262 124 y = player.y;
Siriagus 11:adb68da98262 125 steps = abs(player.vy);
Siriagus 11:adb68da98262 126
Siriagus 11:adb68da98262 127 if (player.vy > 0) // downwards
Siriagus 11:adb68da98262 128 {
Siriagus 11:adb68da98262 129 int playerBottom = y + player.height - 1; // Need to check if bottom part collides
Siriagus 11:adb68da98262 130 while(steps--) // Still movement left
Siriagus 11:adb68da98262 131 {
Siriagus 11:adb68da98262 132 collision = false;
Siriagus 11:adb68da98262 133 for (int i = 0; i < player.width; ++i) // Loop through all x-position on lower part of player
Siriagus 11:adb68da98262 134 {
Siriagus 11:adb68da98262 135 if (map[playerBottom+1][x+i]) // If moving the player one step down for a given (x+i)-position gives a collision
Siriagus 11:adb68da98262 136 {
Siriagus 11:adb68da98262 137 collision = true;
Siriagus 11:adb68da98262 138 break; // No further testing required
Siriagus 11:adb68da98262 139 }
Siriagus 11:adb68da98262 140 }
Siriagus 11:adb68da98262 141
Siriagus 11:adb68da98262 142 if (collision) // If collision
Siriagus 11:adb68da98262 143 {
Siriagus 11:adb68da98262 144 player.vy = 0; // Set vertical velocity to 0 (playe
Siriagus 12:8178fad5e660 145 player.onGround = true; // Player has hit ground
Siriagus 11:adb68da98262 146 break; // Skip the while loop as the player can not move further downwards
Siriagus 11:adb68da98262 147 }
Siriagus 11:adb68da98262 148 else // Can safely move player without collision
Siriagus 11:adb68da98262 149 ++playerBottom; // Move player one step down
Siriagus 11:adb68da98262 150 }
Siriagus 11:adb68da98262 151
Siriagus 11:adb68da98262 152 player.y = playerBottom - (player.height - 1); // Update position when done moving, remember that player.y refers to upper part of the player
Siriagus 11:adb68da98262 153 }
Siriagus 11:adb68da98262 154 else // moving up, check collision from top
Siriagus 11:adb68da98262 155 {
Siriagus 11:adb68da98262 156 while(steps--) // Still movement left
Siriagus 11:adb68da98262 157 {
Siriagus 11:adb68da98262 158 collision = false;
Siriagus 11:adb68da98262 159
Siriagus 11:adb68da98262 160 for (int i = 0; i < player.width; ++i) // Check for all x-positions
Siriagus 11:adb68da98262 161 {
Siriagus 11:adb68da98262 162 if (map[y-1][x+i]) // If moving upwards gives collision for a given x+i
Siriagus 11:adb68da98262 163 {
Siriagus 11:adb68da98262 164 collision = true; // Then we have a collision
Siriagus 11:adb68da98262 165 break; // No further testing needed, skip for loop
Siriagus 11:adb68da98262 166 }
Siriagus 11:adb68da98262 167 }
Siriagus 11:adb68da98262 168
Siriagus 11:adb68da98262 169 if (collision) // If collision was detected
Siriagus 11:adb68da98262 170 {
Siriagus 11:adb68da98262 171 player.vy = 0; // Set vertical velocity to zero
Siriagus 11:adb68da98262 172 break; // Skip while loop as player can not move further up
Siriagus 11:adb68da98262 173 }
Siriagus 11:adb68da98262 174 else // If safe to move for all x-values
Siriagus 11:adb68da98262 175 --y; // Move player one step up
Siriagus 11:adb68da98262 176 }
Siriagus 11:adb68da98262 177
Siriagus 11:adb68da98262 178 player.y = y; // Update vertical position of player
Siriagus 11:adb68da98262 179 }
Siriagus 11:adb68da98262 180
Siriagus 11:adb68da98262 181 // Check if bullet should be fired
Siriagus 9:da608ae65df9 182 if (input->read(Input::ButtonB) && releasedBtnB)
Siriagus 8:9ac6a428fa26 183 {
Siriagus 11:adb68da98262 184 // Create a new bullet and give it initial values
Siriagus 8:9ac6a428fa26 185 Point* bullet = new Point;
Siriagus 11:adb68da98262 186 bullet->x = (player.facingLeft) ? (player.x-1) : (player.x + player.width);
Siriagus 8:9ac6a428fa26 187 bullet->y = player.y + 2;
Siriagus 11:adb68da98262 188 bullet->vx = (player.facingLeft) ? -4 : 4;
Siriagus 9:da608ae65df9 189 bullet->vy = 0;
Siriagus 8:9ac6a428fa26 190
Siriagus 8:9ac6a428fa26 191 bullets.push_back(bullet);
Siriagus 9:da608ae65df9 192 releasedBtnB = false;
Siriagus 8:9ac6a428fa26 193 }
Siriagus 9:da608ae65df9 194 else if (!input->read(Input::ButtonB))
Siriagus 9:da608ae65df9 195 releasedBtnB = true;
Siriagus 8:9ac6a428fa26 196
Siriagus 8:9ac6a428fa26 197 // Loop through bullets and move them
Siriagus 9:da608ae65df9 198 for (std::vector<Point*>::iterator it = bullets.begin(); it != bullets.end();)
Siriagus 8:9ac6a428fa26 199 {
Siriagus 9:da608ae65df9 200 (*it)->x += (*it)->vx;
Siriagus 9:da608ae65df9 201
Siriagus 9:da608ae65df9 202 // Check if outside
Siriagus 9:da608ae65df9 203 int x = (*it)->x;
Siriagus 9:da608ae65df9 204 if (x < 0 || x > WIDTH)
Siriagus 9:da608ae65df9 205 {
Siriagus 9:da608ae65df9 206 delete (*it);
Siriagus 9:da608ae65df9 207 it = bullets.erase(it); // go to next element
Siriagus 9:da608ae65df9 208 }
Siriagus 9:da608ae65df9 209 else
Siriagus 9:da608ae65df9 210 ++it; // go to next element
Siriagus 8:9ac6a428fa26 211
Siriagus 8:9ac6a428fa26 212 // TODO: Check for collisions
Siriagus 8:9ac6a428fa26 213 // TODO: Go both ways
Siriagus 8:9ac6a428fa26 214 }
Siriagus 7:678873947b29 215 }
Siriagus 7:678873947b29 216
Siriagus 7:678873947b29 217 void Game::render()
Siriagus 7:678873947b29 218 {
Siriagus 11:adb68da98262 219 // Draw map
Siriagus 11:adb68da98262 220 drawImage(map);
Siriagus 11:adb68da98262 221
Siriagus 12:8178fad5e660 222 // Draw player
Siriagus 12:8178fad5e660 223 drawImage(Image::Player, player.x, player.y);
Siriagus 12:8178fad5e660 224
Siriagus 12:8178fad5e660 225 /*
Siriagus 8:9ac6a428fa26 226 // Draw player - TODO: Make this a part of sprite class (so they can draw themselves)
Siriagus 8:9ac6a428fa26 227 int x0, x1, y0, y1;
Siriagus 8:9ac6a428fa26 228 x0 = (player.x < 0) ? 0 : player.x; // x0 = max(0,x);
Siriagus 8:9ac6a428fa26 229 y0 = (player.y < 0) ? 0 : player.y; // y0 = max(0,y);
Siriagus 8:9ac6a428fa26 230 x1 = (player.width + player.x > WIDTH) ? WIDTH : player.width + player.x; //x1 = min(WIDTH, width);
Siriagus 8:9ac6a428fa26 231 y1 = (player.height + player.y > HEIGHT) ? HEIGHT : player.height + player.y; //y1 = min(HEIGHT, height);
Siriagus 8:9ac6a428fa26 232
Siriagus 8:9ac6a428fa26 233 for (int i = y0; i < y1; ++i)
Siriagus 8:9ac6a428fa26 234 {
Siriagus 8:9ac6a428fa26 235 for (int j = x0; j < x1; ++j)
Siriagus 8:9ac6a428fa26 236 {
Siriagus 9:da608ae65df9 237 // If player is going right, obtain data from sprite in reverse order => render in reverse
Siriagus 11:adb68da98262 238 int xIndex = (player.facingLeft) ? (j-x0) : (player.width - 1 - (j-x0));
Siriagus 9:da608ae65df9 239
Siriagus 9:da608ae65df9 240 if (Image::Player[i-y0][xIndex])
Siriagus 8:9ac6a428fa26 241 lcd->setPixel(j,i);
Siriagus 8:9ac6a428fa26 242 }
Siriagus 12:8178fad5e660 243 }*/
Siriagus 8:9ac6a428fa26 244
Siriagus 9:da608ae65df9 245 // Render bullets
Siriagus 8:9ac6a428fa26 246 for (std::vector<Point*>::iterator it = bullets.begin(); it != bullets.end(); ++it)
Siriagus 8:9ac6a428fa26 247 {
Siriagus 8:9ac6a428fa26 248 int x, y;
Siriagus 8:9ac6a428fa26 249 x = (*it)->x;
Siriagus 8:9ac6a428fa26 250 y = (*it)->y;
Siriagus 8:9ac6a428fa26 251
Siriagus 8:9ac6a428fa26 252 if (x >= 0 && x < WIDTH && y >= 0 && y < HEIGHT) // Boundary check
Siriagus 8:9ac6a428fa26 253 lcd->setPixel(x,y);
Siriagus 8:9ac6a428fa26 254 }
Siriagus 7:678873947b29 255 }