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

Files at this revision

API Documentation at this revision

Comitter:
Siriagus
Date:
Mon May 11 04:40:23 2015 +0000
Parent:
17:d6a3b29cab31
Commit message:
Fixed some formatting errors in documentation.

Changed in this revision

Enemy.h Show annotated file Show diff for this revision Revisions of this file
Entity.h Show annotated file Show diff for this revision Revisions of this file
Game.h Show annotated file Show diff for this revision Revisions of this file
GameOver.cpp Show annotated file Show diff for this revision Revisions of this file
GameOver.h Show annotated file Show diff for this revision Revisions of this file
Global.h Show annotated file Show diff for this revision Revisions of this file
InputManager.h Show annotated file Show diff for this revision Revisions of this file
MainMenu.cpp Show annotated file Show diff for this revision Revisions of this file
MainMenu.h Show annotated file Show diff for this revision Revisions of this file
Sound.h Show annotated file Show diff for this revision Revisions of this file
SubmitHighscore.cpp Show annotated file Show diff for this revision Revisions of this file
SubmitHighscore.h Show annotated file Show diff for this revision Revisions of this file
TitleScreen.h Show annotated file Show diff for this revision Revisions of this file
main.cpp Show annotated file Show diff for this revision Revisions of this file
--- a/Enemy.h	Mon May 11 03:52:18 2015 +0000
+++ b/Enemy.h	Mon May 11 04:40:23 2015 +0000
@@ -9,17 +9,22 @@
 class Enemy : public Entity
 {
     public:
-        enum Type{SIMPLE, JUMPER, RUNNER}; /// Different type of enemies
+        /// Different type of enemies
+        enum Type{SIMPLE, JUMPER, RUNNER};
     
     public:
         Enemy(int x, int y, bool facingLeft, Type type = SIMPLE) : Entity(x,y), type(type) {setup();}
         
-        Type type;      /// What type
-        //bool dead;      /// True if enemy has been shot by player
-        int difficulty; /// Multiplier used for giving more points
-        int jumpRate; // Probability (in percent) of trying to jump if at ground.
+        /// What type of enemy it is.
+        Type type;      
+        
+        /// Multiplier used for giving more points
+        int difficulty; 
+        
+        /// Probability (in percent) of trying to jump if at ground.
+        int jumpRate;
         
         private:
-        void setup();
+            void setup();
 };
 #endif
\ No newline at end of file
--- a/Entity.h	Mon May 11 03:52:18 2015 +0000
+++ b/Entity.h	Mon May 11 04:40:23 2015 +0000
@@ -13,18 +13,31 @@
         Entity() {x = y = width = height = vx = vy = 0; facingLeft = true; onGround = false; dead = false;}
         Entity(int x, int y, int w = 0, int h = 0) : x(x), y(y), width(w), height(h) {vx = vy = 0; facingLeft = true; onGround = false; dead = false;}
         
-        int x, y;       /// Position of entity (origin: left upper corner)
-        int vx, vy;     /// Velocity of entity
-        int width;      /// Width of entity
-        int height;     /// Height of entity
+        /// Position of entity (origin: left upper corner)
+        int x, y;
+        
+        /// Velocity of entity
+        int vx, vy;
+        
+        /// Width of entity
+        int width; 
         
-        bool facingLeft; /// True if the entity is facing left
-        bool onGround;  /// True if entity is standing on the ground.
+        /// Height of entity     
+        int height;
+        
+        /// True if the entity is facing left
+        bool facingLeft; 
+        /// True if entity is standing on the ground.
+        bool onGround;
+        
+        /// True if enemy is dead.
         bool dead;
         
-        int getRight() {return x + width - 1;}      /// Returns x-position of the right edge
-        int getBottom() {return y + height - 1;}    /// Returns y-position of the bottom edge
+        /// Returns x-position of the right edge
+        int getRight() {return x + width - 1;}
         
+        /// Returns y-position of the bottom edge   
+        int getBottom() {return y + height - 1;}
 };
 
 #endif
\ No newline at end of file
--- a/Game.h	Mon May 11 03:52:18 2015 +0000
+++ b/Game.h	Mon May 11 04:40:23 2015 +0000
@@ -6,7 +6,6 @@
 */
 
 #include "State.h"
-#include "Resources.h"
 #include "Entity.h"
 #include "Enemy.h"
 #include "map.h"
@@ -37,27 +36,27 @@
         
         
     private:
-        void init();    /// Sets some initial values
-        void spawnEnemy();  /// Spawns a new enemy
-        void moveEnemies(); /// Movement and AI for all enemies
-        void moveWithCollisionTest(Entity* entity, const int map[HEIGHT][WIDTH]); /// Moves entity in map. If collision occurs, entity can not move further.
-        bool hitTestRect(Rectangle r1, Rectangle r2); /// Returns true if two rectangles overlap
-        bool bulletHitMap(Rectangle &bulletColRect, const int map[HEIGHT][WIDTH]); /// Help function for detecting collision between moving bullet and map.
-        void renderScore(); /// Draws the current score in upper right corner
-        void respawnPlayer(); /// Respawns player at start position
+        void init();    // Sets some initial values
+        void spawnEnemy();  // Spawns a new enemy
+        void moveEnemies(); // Movement and AI for all enemies
+        void moveWithCollisionTest(Entity* entity, const int map[HEIGHT][WIDTH]); // Moves entity in map. If collision occurs, entity can not move further.
+        bool hitTestRect(Rectangle r1, Rectangle r2); // Returns true if two rectangles overlap @see https://developer.mozilla.org/en-US/docs/Games/Techniques/2D_collision_detection#Axis-Aligned_Bounding_Box
+        bool bulletHitMap(Rectangle &bulletColRect, const int map[HEIGHT][WIDTH]); // Help function for detecting collision between moving bullet and map.
+        void renderScore(); // Draws the current score in upper right corner
+        void respawnPlayer(); // Respawns player at start position
         
-        int livesLeft; /// The number of lives left
-        bool paused;        // True if the game is paused
+        int livesLeft; // The number of lives left
+        bool paused;   // True if the game is paused
         
-        int spawnRate;  /// Probability of enemy spawning in a single frame/update.
-        static const int spawnPoints[3][2]; /// Positions where enemies can spawn.
+        int spawnRate;  // Probability of enemy spawning in a single frame/update.
+        static const int spawnPoints[3][2]; // Positions where enemies can spawn.
         
-        Entity player;  /// Player object
-        std::vector<Point*> bullets;    /// Container for bullets
-        std::vector<Enemy*> enemies;    /// Container for enemies
+        Entity player;  // Player object
+        std::vector<Point*> bullets;    // Container for bullets
+        std::vector<Enemy*> enemies;    // Container for enemies
         
-        bool releasedBtnB;  /// True if button B has been released after being pressed down
-        bool releasedBtnC;  /// True if button C has been released after being pressed down
+        bool releasedBtnB;  // True if button B has been released after being pressed down
+        bool releasedBtnC;  // True if button C has been released after being pressed down
 };
 
 #endif
--- a/GameOver.cpp	Mon May 11 03:52:18 2015 +0000
+++ b/GameOver.cpp	Mon May 11 04:40:23 2015 +0000
@@ -45,8 +45,22 @@
 
 void GameOver::render()
 {
+    // Draw border
+    for (int i = 0; i < HEIGHT/3; ++i)
+    {
+        drawImage(Image::Pattern3, 0, 3*i);           // left border
+        drawImage(Image::Pattern3, (WIDTH-3), 3*i);   // right border
+    }
+    
+    for (int j = 0; j < WIDTH/3; ++j)
+    {
+        drawImage(Image::Pattern3, 3 * j, (HEIGHT-3));    // bottom border
+        drawImage(Image::Pattern3, 3 * j, 0);             // top border
+    }
+    
+    // Draw text
     int xMargin = 8;
-    lcd->printString("Game Over", xMargin, 1);
+    lcd->printString("Game Over!", xMargin, 1);
     
     switch (currentState)
     {        
--- a/GameOver.h	Mon May 11 03:52:18 2015 +0000
+++ b/GameOver.h	Mon May 11 04:40:23 2015 +0000
@@ -13,21 +13,32 @@
 class GameOver : public State
 {
     public:
-    
+        /// Create a new GameOver object.
         GameOver(StateManager* fsm, N5110 *lcd, InputManager* input, Sound* sound)
                 : State(fsm, lcd, input, sound) {init();}
                 
-        virtual void update(float dt);  /// Update logic
-        virtual void render();          /// Draw to lcd
+        /// Update logic
+        virtual void update(float dt);
+        
+        /// Draw to lcd
+        virtual void render();          
     
     private:
+        /// Setup initial configuration.
         void init();
-        static void btnAPress(); /// Interrupt callback function when button A is pressed
-        static void btnCPress(); /// Interrupt callback function when button C is pressed
+        
+         /// Interrupt callback function when button A is pressed.
+        static void btnAPress();
+        
+        /// Interrupt callback function when button C is pressed.
+        static void btnCPress();
         
     private:
-        enum GameOverState {SELECT_PLAY, SELECT_MAIN_MENU, LOAD_GAME, LOAD_MAIN_MENU}; // Internal states
-        static int currentState;    /// Current internal state
+        /// Internal states
+        enum GameOverState {SELECT_PLAY, SELECT_MAIN_MENU, LOAD_GAME, LOAD_MAIN_MENU};
+        
+        /// Current internal state
+        static int currentState;    
 };
 
 #endif
\ No newline at end of file
--- a/Global.h	Mon May 11 03:52:18 2015 +0000
+++ b/Global.h	Mon May 11 04:40:23 2015 +0000
@@ -7,6 +7,7 @@
 /// @file Global.h
 /// @brief Contains global variables and functions
 
+/// A highscore element consists of initials and a score.
 struct Highscore
 {
     std::string initials;
@@ -15,9 +16,14 @@
 
 namespace Global
 {
-    extern int score;                      /// Score in last game
-    extern Highscore highscores[3];        /// List showing the three all time highest score achieved. Sorted from highest to lowest.
-    void clearHighscoreList();                /// Clears the highscore list
+    /// Score in last game
+    extern int score;
+    
+    /// List showing the three all time highest score achieved. Sorted from highest to lowest.
+    extern Highscore highscores[3];        
+    
+    /// Clears the highscore list
+    void clearHighscoreList();
 }
 
 #endif
\ No newline at end of file
--- a/InputManager.h	Mon May 11 03:52:18 2015 +0000
+++ b/InputManager.h	Mon May 11 04:40:23 2015 +0000
@@ -56,8 +56,7 @@
         * @param button The requested button.
         * @return Pointer to the button.
         */
-        PinDetect* getBtnPtr(Input::Button button);
-        
+        PinDetect* getBtnPtr(Input::Button button);      
 };
 
 #endif
--- a/MainMenu.cpp	Mon May 11 03:52:18 2015 +0000
+++ b/MainMenu.cpp	Mon May 11 04:40:23 2015 +0000
@@ -56,9 +56,6 @@
     {
         drawImage(Image::Pattern3, 3 * j, (HEIGHT-3));    // bottom border
         drawImage(Image::Pattern3, 3 * j, 0);             // top border
-        
-//        if (currentState != HIGHSCORES && currentState != CONTROLS)
-            
     }
     
     // Check sate
--- a/MainMenu.h	Mon May 11 03:52:18 2015 +0000
+++ b/MainMenu.h	Mon May 11 04:40:23 2015 +0000
@@ -7,7 +7,6 @@
 */
 
 #include "State.h"
-#include "Resources.h"
 #include "Global.h"
 #include <string>
 #include <sstream>
@@ -20,20 +19,25 @@
         MainMenu(StateManager* fsm, N5110 *lcd, InputManager* input, Sound *sound)
                 : State(fsm, lcd, input, sound) {init();}
                 
-        virtual void update(float dt); /// Update logic
-        virtual void render();         /// Draw MainMenu to screen
+        /// Update logic
+        virtual void update(float dt);
+        
+        /// Draw MainMenu to screen
+        virtual void render();
     
     private:
         void init();
-        static void btnAPress(); /// Interrupt callback function when button A is pressed
-        static void btnBPress(); /// Interrupt callback function when button B is pressed
-        static void btnCPress(); /// Interrupt callback function when button C is pressed
+        static void btnAPress(); // Interrupt callback function when button A is pressed
+        static void btnBPress(); // Interrupt callback function when button B is pressed
+        static void btnCPress(); // Interrupt callback function when button C is pressed
         
     // Variables
     private:
         /// States for the main menu's internal finite state machine
         enum MenuState {SELECT_PLAY, SELECT_HIGHSCORES, SELECT_CONTROLS, LOAD_GAME, HIGHSCORES, CONTROLS};
-        static int currentState; /// Current state of the internal finite state machine
+        
+        /// Current state of the internal finite state machine
+        static int currentState;
         
         /** Transition table for internal fsm
         * Each row corresponds to a state
--- a/Sound.h	Mon May 11 03:52:18 2015 +0000
+++ b/Sound.h	Mon May 11 04:40:23 2015 +0000
@@ -4,6 +4,7 @@
 /// @file Sound.h
 
 #include "mbed.h"
+#include "Resources.h"
 
 /// A note is given by its frequency and beat.
 struct Note
@@ -22,9 +23,11 @@
         */
         Sound(PinName buzzerPin);
         
-        ~Sound(); /// Destructor
+        /// Destructor
+        ~Sound(); 
         
-        void playNote(Note &note); /// Plays the given note. @param note The note to be played.
+        /// Plays the given note. @param note The note to be played.
+        void playNote(Note &note);
         
     private:
         void start() {*buzzer = 0.5;}   // Set duty cycle to 50%
@@ -32,8 +35,8 @@
     
     // Variables
     private:
-        PwmOut *buzzer; /// Piezo buzzer.
-        Timeout ticker; /// Used for stopping sound after the given beat.
+        PwmOut *buzzer; // Piezo buzzer.
+        Timeout ticker; // Used for stopping sound after the given beat.
 };
 
 /// Sound effects. Commonly used notes.
--- a/SubmitHighscore.cpp	Mon May 11 03:52:18 2015 +0000
+++ b/SubmitHighscore.cpp	Mon May 11 04:40:23 2015 +0000
@@ -165,10 +165,6 @@
             case SEL_SUBMIT:
                 lcd->printString(">",25-6 , 4);
             break;
-            
-            
         }
-    }
-    
-        
+    }   
 }
\ No newline at end of file
--- a/SubmitHighscore.h	Mon May 11 03:52:18 2015 +0000
+++ b/SubmitHighscore.h	Mon May 11 04:40:23 2015 +0000
@@ -18,8 +18,10 @@
                 : State(fsm, lcd, input, sound) {init();}
         
         void init();
-        virtual void update(float dt);  /// Update logic
-        virtual void render();          /// Draw to screen
+        /// Update logic
+        virtual void update(float dt);
+        /// Draw to screen
+        virtual void render();
     
     private:
         static void btnAPress(); /// Interrupt callback function when button A is pressed
@@ -29,12 +31,14 @@
         
     private:
         enum SubmitHighscoreState{LETTER1, LETTER2, LETTER3, SEL_SUBMIT, WRITE_TO_FILE, LOAD_GAME_OVER};
+        
         static int currentState;
         /** Changes the current selected letter.
           * @param index The index of the letter to be changed (0, 1 or 2)
           * @param next Goes to the next letter in the alphabet if true. Goes to the previous letter if false.
         */
         static void changeLetter(int index, bool next);
+        
         static int letters[3];
 };
 
--- a/TitleScreen.h	Mon May 11 03:52:18 2015 +0000
+++ b/TitleScreen.h	Mon May 11 04:40:23 2015 +0000
@@ -11,7 +11,7 @@
     public:
         TitleScreen(StateManager* fsm, N5110 *lcd, InputManager* input, Sound* sound)
                 : State(fsm, lcd, input, sound) {init();}
-                
+        
         virtual void update(float dt);
         virtual void render();
         
--- a/main.cpp	Mon May 11 03:52:18 2015 +0000
+++ b/main.cpp	Mon May 11 04:40:23 2015 +0000
@@ -32,27 +32,38 @@
 #define BUTTON_C p29
 
 // Input and Output
-N5110 *lcd; /// Display
-Sound *sound; // Sound: Piezo buzzer
+/// Display
+N5110 *lcd;
 
-InputManager *input; /// Responsible for managing user input
+/// Sound: Piezo buzzer
+Sound *sound; 
 
-// Brightness potentiometer
-AnalogIn ledPot(LED_POT);
+/// Responsible for managing user input
+InputManager *input;
 
-void init(); /// Set up initial variables
-void cleanUp(); /// Frees remaining allocated memory
-
-StateManager* fsm;  /// Finite state machine
+/// Brightness potentiometer
+AnalogIn ledPot(LED_POT);
 
 AnalogIn randomPin(p18); // Unconnected pin => noise, used for generating a random seed.
 AnalogIn randomPin2(p19); // Unconnected pin
 
-LocalFileSystem local("local"); /// Local file system
+/// Set up initial variables
+void init();
+
+/// Frees remaining allocated memory
+void cleanUp();
+
+/// Finite state machine
+StateManager* fsm;
+
+/// Local file system
+LocalFileSystem local("local");
 
 /// Function for generating a random seed by using a unconnected analog input pin
 // Problem: Not trully random as pin is fairly stable. It is unfortunately connected to the ground layer (often returning 0). Would be better to connect it to a "loose wire"/bad connection-
 unsigned int getRandSeed();
+
+/// Read and load the high score file.
 void readHighscoreFile();
 
 int main()
@@ -146,7 +157,9 @@
     delete sound;
 }
 
-/// Get a random seed based on unconnected analog input pins
+/** Get a random seed based on unconnected analog input pins
+  * @see https://developer.mbed.org/questions/2886/Why-is-the-rand-function-not-the-least-b/
+ */
 unsigned int getRandSeed()
 {
     unsigned int randNum = 0;