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:
Tue Apr 28 15:12:42 2015 +0000
Revision:
2:0ae5ac8b0cac
Parent:
1:0cfe2255092a
Child:
3:4e3f342a135c
More work on the menu system

Who changed what in which revision?

UserRevisionLine numberNew contents of line
Siriagus 0:1f92de30d43e 1 /**
Siriagus 0:1f92de30d43e 2 @brief Simple platform game developed for ELEC2645 Embedded Systems Project at University of Leeds
Siriagus 0:1f92de30d43e 3
Siriagus 0:1f92de30d43e 4 @author Andreas Garmannslund
Siriagus 0:1f92de30d43e 5 **/
Siriagus 0:1f92de30d43e 6
Siriagus 0:1f92de30d43e 7 #include "mbed.h"
Siriagus 0:1f92de30d43e 8 #include "N5110.h"
Siriagus 1:0cfe2255092a 9 #include "PowerControl.h"
Siriagus 1:0cfe2255092a 10 #include "PinDetect.h"
Siriagus 1:0cfe2255092a 11
Siriagus 0:1f92de30d43e 12 #include "Joystick.h"
Siriagus 1:0cfe2255092a 13 #include <string>
Siriagus 1:0cfe2255092a 14
Siriagus 1:0cfe2255092a 15 #include "State.h"
Siriagus 1:0cfe2255092a 16 #include "MainMenu.h"
Siriagus 0:1f92de30d43e 17 #include "map.h"
Siriagus 1:0cfe2255092a 18 #include "images.h"
Siriagus 1:0cfe2255092a 19
Siriagus 0:1f92de30d43e 20 #include <ctime>
Siriagus 0:1f92de30d43e 21
Siriagus 1:0cfe2255092a 22 // Redefine pin names for simple access.
Siriagus 1:0cfe2255092a 23 #define JOY_H p17
Siriagus 1:0cfe2255092a 24 #define JOY_V p16
Siriagus 1:0cfe2255092a 25 #define JOY_BTN p15
Siriagus 1:0cfe2255092a 26
Siriagus 1:0cfe2255092a 27 #define LED_POT p20
Siriagus 1:0cfe2255092a 28
Siriagus 2:0ae5ac8b0cac 29 #define BUTTON_A p28
Siriagus 2:0ae5ac8b0cac 30 #define BUTTON_B p27
Siriagus 1:0cfe2255092a 31 #define BUTTON_C p29
Siriagus 1:0cfe2255092a 32
Siriagus 2:0ae5ac8b0cac 33 enum MenuState {SELECT_MENU, HIGH_SCORES, CONTROLS, LOAD_GAME};
Siriagus 2:0ae5ac8b0cac 34 MenuState currentState; // current state
Siriagus 1:0cfe2255092a 35
Siriagus 0:1f92de30d43e 36 // Components
Siriagus 0:1f92de30d43e 37 N5110 *lcd; // VCC, SCE, RST, D/C, MOSI, SCLK and LED
Siriagus 0:1f92de30d43e 38 Joystick *joystick;
Siriagus 0:1f92de30d43e 39
Siriagus 0:1f92de30d43e 40 // Brightness potentiometer
Siriagus 1:0cfe2255092a 41 AnalogIn ledPot(LED_POT);
Siriagus 0:1f92de30d43e 42
Siriagus 1:0cfe2255092a 43 /*
Siriagus 0:1f92de30d43e 44 // Buttons
Siriagus 0:1f92de30d43e 45 DigitalIn btnA(p27);
Siriagus 0:1f92de30d43e 46 DigitalIn btnB(p28);
Siriagus 0:1f92de30d43e 47 DigitalIn btnC(p29);
Siriagus 1:0cfe2255092a 48 */
Siriagus 0:1f92de30d43e 49
Siriagus 0:1f92de30d43e 50 // Debugging
Siriagus 0:1f92de30d43e 51 Serial pc(USBTX, USBRX);
Siriagus 0:1f92de30d43e 52 BusOut leds(LED1, LED2, LED3, LED4);
Siriagus 0:1f92de30d43e 53
Siriagus 1:0cfe2255092a 54
Siriagus 1:0cfe2255092a 55 // @brief Clears the screen and fill it with the image in the argument.
Siriagus 1:0cfe2255092a 56 void clearAndDrawImage(const int img[BANKS][WIDTH])
Siriagus 0:1f92de30d43e 57 {
Siriagus 0:1f92de30d43e 58 for (int i = 0; i < BANKS; ++i)
Siriagus 0:1f92de30d43e 59 {
Siriagus 0:1f92de30d43e 60 for (int j = 0; j < WIDTH; ++j)
Siriagus 0:1f92de30d43e 61 {
Siriagus 1:0cfe2255092a 62 lcd->buffer[j][i] = img[i][j];
Siriagus 0:1f92de30d43e 63 }
Siriagus 0:1f92de30d43e 64 }
Siriagus 0:1f92de30d43e 65 lcd->refresh();
Siriagus 0:1f92de30d43e 66 }
Siriagus 0:1f92de30d43e 67
Siriagus 1:0cfe2255092a 68 State *stateObj; // current state object
Siriagus 1:0cfe2255092a 69 void init()
Siriagus 0:1f92de30d43e 70 {
Siriagus 0:1f92de30d43e 71 // Init LCD
Siriagus 0:1f92de30d43e 72 lcd = new N5110(p7, p8, p9, p10, p11, p13, p26);
Siriagus 0:1f92de30d43e 73 lcd->init();
Siriagus 2:0ae5ac8b0cac 74 //lcd->normalMode();
Siriagus 0:1f92de30d43e 75 lcd->setBrightness(1.0 - ledPot); // Update brightness of screen
Siriagus 0:1f92de30d43e 76
Siriagus 0:1f92de30d43e 77 // Init joystick
Siriagus 1:0cfe2255092a 78 joystick = new Joystick(JOY_H, JOY_V, JOY_BTN);
Siriagus 0:1f92de30d43e 79 joystick->calibrate();
Siriagus 0:1f92de30d43e 80
Siriagus 1:0cfe2255092a 81 // Set initial state
Siriagus 1:0cfe2255092a 82 // currentState = MENU;
Siriagus 2:0ae5ac8b0cac 83
Siriagus 2:0ae5ac8b0cac 84 currentState = SELECT_MENU;
Siriagus 1:0cfe2255092a 85 // stateObj = new MainMenu(lcd, BUTTON_A, BUTTON_B, BUTTON_C);
Siriagus 1:0cfe2255092a 86 }
Siriagus 1:0cfe2255092a 87
Siriagus 1:0cfe2255092a 88 void cleanUp()
Siriagus 1:0cfe2255092a 89 {
Siriagus 1:0cfe2255092a 90 delete lcd;
Siriagus 1:0cfe2255092a 91 delete joystick;
Siriagus 1:0cfe2255092a 92 delete stateObj;
Siriagus 1:0cfe2255092a 93 }
Siriagus 1:0cfe2255092a 94
Siriagus 2:0ae5ac8b0cac 95 // States
Siriagus 2:0ae5ac8b0cac 96
Siriagus 2:0ae5ac8b0cac 97
Siriagus 1:0cfe2255092a 98 const int NUM_CHOICES = 3;
Siriagus 1:0cfe2255092a 99 std::string choices[NUM_CHOICES] = {"Play", "High Score", "Controls"};
Siriagus 1:0cfe2255092a 100 int selectedChoice = 0;
Siriagus 1:0cfe2255092a 101
Siriagus 2:0ae5ac8b0cac 102 PinDetect btnA(BUTTON_A);
Siriagus 2:0ae5ac8b0cac 103 PinDetect btnB(BUTTON_B);
Siriagus 1:0cfe2255092a 104 PinDetect btnC(BUTTON_C);
Siriagus 2:0ae5ac8b0cac 105
Siriagus 2:0ae5ac8b0cac 106 void btnAPressed()
Siriagus 2:0ae5ac8b0cac 107 {
Siriagus 2:0ae5ac8b0cac 108 switch(currentState)
Siriagus 2:0ae5ac8b0cac 109 {
Siriagus 2:0ae5ac8b0cac 110 case SELECT_MENU:
Siriagus 2:0ae5ac8b0cac 111 switch(selectedChoice)
Siriagus 2:0ae5ac8b0cac 112 {
Siriagus 2:0ae5ac8b0cac 113 case 0:
Siriagus 2:0ae5ac8b0cac 114 currentState = LOAD_GAME;
Siriagus 2:0ae5ac8b0cac 115 break;
Siriagus 2:0ae5ac8b0cac 116
Siriagus 2:0ae5ac8b0cac 117 case 1:
Siriagus 2:0ae5ac8b0cac 118 currentState = HIGH_SCORES;
Siriagus 2:0ae5ac8b0cac 119 break;
Siriagus 2:0ae5ac8b0cac 120
Siriagus 2:0ae5ac8b0cac 121 case 2:
Siriagus 2:0ae5ac8b0cac 122 currentState = CONTROLS;
Siriagus 2:0ae5ac8b0cac 123 break;
Siriagus 2:0ae5ac8b0cac 124 }
Siriagus 2:0ae5ac8b0cac 125 break;
Siriagus 2:0ae5ac8b0cac 126
Siriagus 2:0ae5ac8b0cac 127 case CONTROLS:
Siriagus 2:0ae5ac8b0cac 128 case HIGH_SCORES:
Siriagus 2:0ae5ac8b0cac 129 currentState = SELECT_MENU;
Siriagus 2:0ae5ac8b0cac 130 break;
Siriagus 2:0ae5ac8b0cac 131 }
Siriagus 2:0ae5ac8b0cac 132
Siriagus 2:0ae5ac8b0cac 133 }
Siriagus 2:0ae5ac8b0cac 134 void btnBPressed()
Siriagus 2:0ae5ac8b0cac 135 {
Siriagus 2:0ae5ac8b0cac 136 switch(currentState)
Siriagus 2:0ae5ac8b0cac 137 {
Siriagus 2:0ae5ac8b0cac 138 case HIGH_SCORES:
Siriagus 2:0ae5ac8b0cac 139 case CONTROLS:
Siriagus 2:0ae5ac8b0cac 140 currentState = SELECT_MENU;
Siriagus 2:0ae5ac8b0cac 141 break;
Siriagus 2:0ae5ac8b0cac 142 }
Siriagus 2:0ae5ac8b0cac 143
Siriagus 2:0ae5ac8b0cac 144 }
Siriagus 2:0ae5ac8b0cac 145
Siriagus 2:0ae5ac8b0cac 146 void btnCPressed()
Siriagus 2:0ae5ac8b0cac 147 {
Siriagus 2:0ae5ac8b0cac 148 if (currentState == SELECT_MENU)
Siriagus 2:0ae5ac8b0cac 149 selectedChoice = (selectedChoice + 1) % NUM_CHOICES;
Siriagus 2:0ae5ac8b0cac 150 }
Siriagus 2:0ae5ac8b0cac 151
Siriagus 2:0ae5ac8b0cac 152 // Processes button input.
Siriagus 2:0ae5ac8b0cac 153 int buttons = 0; // Three first bit corresponds the buttons
Siriagus 2:0ae5ac8b0cac 154 void processButtonInput(int buttonPressed)
Siriagus 2:0ae5ac8b0cac 155 {
Siriagus 2:0ae5ac8b0cac 156 switch(buttonPressed)
Siriagus 2:0ae5ac8b0cac 157 {
Siriagus 2:0ae5ac8b0cac 158 case 0: // Button A
Siriagus 2:0ae5ac8b0cac 159 btnAPressed();
Siriagus 2:0ae5ac8b0cac 160 break;
Siriagus 2:0ae5ac8b0cac 161
Siriagus 2:0ae5ac8b0cac 162 case 1: // Button B
Siriagus 2:0ae5ac8b0cac 163 btnBPressed();
Siriagus 2:0ae5ac8b0cac 164 break;
Siriagus 2:0ae5ac8b0cac 165
Siriagus 2:0ae5ac8b0cac 166 case 2: // Button C
Siriagus 2:0ae5ac8b0cac 167 btnCPressed();
Siriagus 2:0ae5ac8b0cac 168 break;
Siriagus 2:0ae5ac8b0cac 169 }
Siriagus 2:0ae5ac8b0cac 170 }
Siriagus 1:0cfe2255092a 171
Siriagus 1:0cfe2255092a 172 int main()
Siriagus 1:0cfe2255092a 173 {
Siriagus 1:0cfe2255092a 174 init();
Siriagus 1:0cfe2255092a 175
Siriagus 1:0cfe2255092a 176 // button
Siriagus 2:0ae5ac8b0cac 177 btnA.attach_asserted(&btnAPressed);
Siriagus 2:0ae5ac8b0cac 178 btnB.attach_asserted(&btnBPressed);
Siriagus 2:0ae5ac8b0cac 179 btnC.attach_asserted(&btnCPressed);
Siriagus 2:0ae5ac8b0cac 180
Siriagus 2:0ae5ac8b0cac 181 btnA.setSampleFrequency();
Siriagus 2:0ae5ac8b0cac 182 btnB.setSampleFrequency();
Siriagus 1:0cfe2255092a 183 btnC.setSampleFrequency();
Siriagus 1:0cfe2255092a 184 // Game loop, fixed-time step, updates game logic with regular intervals, rendering happens as fast as possible
Siriagus 0:1f92de30d43e 185 while(true)
Siriagus 0:1f92de30d43e 186 {
Siriagus 1:0cfe2255092a 187 lcd->clear();
Siriagus 0:1f92de30d43e 188
Siriagus 1:0cfe2255092a 189 //stateObj->run();
Siriagus 1:0cfe2255092a 190
Siriagus 0:1f92de30d43e 191 joystick->update();
Siriagus 0:1f92de30d43e 192 lcd->setBrightness(1.0 - ledPot); // Update brightness of screen
Siriagus 0:1f92de30d43e 193
Siriagus 2:0ae5ac8b0cac 194 // Process events
Siriagus 2:0ae5ac8b0cac 195
Siriagus 2:0ae5ac8b0cac 196
Siriagus 2:0ae5ac8b0cac 197 // Update
Siriagus 2:0ae5ac8b0cac 198 switch (currentState)
Siriagus 1:0cfe2255092a 199 {
Siriagus 2:0ae5ac8b0cac 200 case SELECT_MENU:
Siriagus 2:0ae5ac8b0cac 201 for (int i = 0; i < 3; ++i)
Siriagus 2:0ae5ac8b0cac 202 {
Siriagus 2:0ae5ac8b0cac 203 std::string str = (selectedChoice == i) ? ("> " + choices[i]) : choices[i];
Siriagus 2:0ae5ac8b0cac 204 lcd->printString(str.c_str(), 10, i+1);
Siriagus 2:0ae5ac8b0cac 205 }
Siriagus 2:0ae5ac8b0cac 206 break;
Siriagus 2:0ae5ac8b0cac 207
Siriagus 2:0ae5ac8b0cac 208 case HIGH_SCORES:
Siriagus 2:0ae5ac8b0cac 209 // Placeholder TODO: Actually high scores
Siriagus 2:0ae5ac8b0cac 210 lcd->printString("High Scores", 10, 0);
Siriagus 2:0ae5ac8b0cac 211 lcd->printString("AND 1000000", 10, 1);
Siriagus 2:0ae5ac8b0cac 212 lcd->printString("AND 500000", 10, 2);
Siriagus 2:0ae5ac8b0cac 213 lcd->printString("AND 100", 10, 3);
Siriagus 2:0ae5ac8b0cac 214 lcd->printString("> Back", 10, 4);
Siriagus 2:0ae5ac8b0cac 215 break;
Siriagus 2:0ae5ac8b0cac 216
Siriagus 2:0ae5ac8b0cac 217 case CONTROLS:
Siriagus 2:0ae5ac8b0cac 218 lcd->printString("Controls", 10, 0);
Siriagus 2:0ae5ac8b0cac 219 lcd->printString("A: Jump", 10, 1);
Siriagus 2:0ae5ac8b0cac 220 lcd->printString("B: Shoot", 10, 2);
Siriagus 2:0ae5ac8b0cac 221 lcd->printString("C: Pause", 10, 3);
Siriagus 2:0ae5ac8b0cac 222 lcd->printString("> Back", 10, 4);
Siriagus 2:0ae5ac8b0cac 223 break;
Siriagus 2:0ae5ac8b0cac 224
Siriagus 2:0ae5ac8b0cac 225 case LOAD_GAME:
Siriagus 2:0ae5ac8b0cac 226 lcd->inverseMode();
Siriagus 2:0ae5ac8b0cac 227 clearAndDrawImage(logo);
Siriagus 2:0ae5ac8b0cac 228 break;
Siriagus 2:0ae5ac8b0cac 229
Siriagus 1:0cfe2255092a 230 }
Siriagus 1:0cfe2255092a 231
Siriagus 1:0cfe2255092a 232 lcd->refresh();
Siriagus 2:0ae5ac8b0cac 233 Sleep();
Siriagus 0:1f92de30d43e 234 }
Siriagus 0:1f92de30d43e 235
Siriagus 1:0cfe2255092a 236 cleanUp();
Siriagus 0:1f92de30d43e 237
Siriagus 0:1f92de30d43e 238 return 0;
Siriagus 1:0cfe2255092a 239 }