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:
Sat May 09 14:39:48 2015 +0000
Revision:
14:b4fed570abaf
Parent:
13:7ab71c7c311b
Child:
15:d5eb13c4c1c6
Added function for generating random seed from two unconnected AnalogIn pins.

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