Bournemouth University Software Programming: Assignment 2
Cloud Breaker was my first ever game I created for my University Assignments at Bournemouth, it taught me the fundamentals of how to draw images to buffers and screens and all the fundamentals for future games. Cloud Breaker was created using the open source Allegro API and C++;
Since Cloud Breaker my skills have been transferred into XNA where my concepts of OOP have greatly improved and become much more integrated into my entire programming experience.
About:
Cloud Breaker is a fairly simple game yet hugely addictive. With quirky graphics and funky sounds, in Cloud Breaker you must destroy / break the clouds in the sky to allow the sun to shine!
The aim of the game is to destroy the clouds by launching your bouncing ball at them which you must keep in the sky without touching the ground, otherwise it will damage your precious plants! When a cloud is destroyed it drops rain which can be used to bring plants back.
How the idea for the game developed:
When I first started learning the Allegro game programming library I had no idea what kind of game I wanted to create so I just stuck with the tutorials in the book (All in one game programming: Third Edition) which I used mainly to learn the library.
Through the learning process I started to get some idea of the kind of game I wanted to create, it wasn’t until the later tutorials came along which featured a ball simply bouncing around the corners of the screen when I had the head for a ball breaker kind of game. So I decided to create a game in which you destroy boxes in the sky and stop the ball from falling by deflecting it with a platform.
I wanted to get away from the grey and space themed types of this game I had seen before so I decided to go for a colourful green and plant themed game. I eventually decided that clouds would be a great thing to be placed in the air since they occur naturally in the air in the real world but I wanted to give it all a twist.
So I gave the clouds some character by adding faces which change every time they are hit, increasing in unhappiness and eventually shock up until the point when they burst and they release a rain drop which is happy and falls to the floor.
In an interesting way to represent the player’s lives, rather than text I decided to have plants along the bottom.
Game Screenshots:
How requirements of the Assignment have been met:
- The game has to be in graphics mode: The game has been created using a wide variety of Bitmap images and animation.
- The game must contain a user controllable object: The user controls the leaf which bounces the ball around the screen.
- The game must contain at least one additional non-user controller object: The clouds, rain drop, flowers and ball are all objects which the user does not directly control.
- The game must allow user input / control by both keyboard and mouse: Both forms of input are supported. You can control the base leaf with the two arrow keys or by using the mouse and clicking where you want it to go.
- The game must provide the user with feedback: Noises are made when the ball hits an object including the leaf you control.
- The game may contain sound: Game music and bouncing sounds are featured.
- The game must provide the current score: Score is kept and presented at the end of the level if you win. It is also displayed at the top at all times.
- The game should be single player only: Yes.
- The game must provide the user with simple instructions: Prompts are given for the user to launch the ball and the rules can be viewed from the level select menu.
- The game must begin and end in a controlled manner: Menus and dedicated closing and starting buttons are used (ESCAPE and ENTER).
- The game must implement at least one C++ class: The ball which the game features is implemented using a class, which stores its features and functions directly related to it.
The code:
The code is constructed with 4 main files C++ files. These are:
- Cloudbreaker.h
- Levels.cpp
- BallClass.cpp
- Main.cpp
“Cloudbreaker.h”: The cloudbreaker.h file is included by all the .cpp files. The header file includes all the definitions in the file, such as #define BLACK makecol(0,0,0) and #define WHITE makecol(255,255,255) and #define WIDTH 480. Along with definitions which are used throughout the program, the cloudsremaining array is declared in here as it is used within the level.cpp and main.cpp file: extern int cloudsremaining[15][4];.
Functions used within the Levels.cpp files are declared in the header file, so that they can be used in the main program. The class for the ball is also declared in the header.
“Levels.cpp”: The levels.cpp file consist of 5 functions which relates directly to the 5 levels which the user can play in the game.
Each function returns the number of clouds in the level, which is used throughout the main program and also uses the cloudsremaining variable to set which clouds are where and initialize them.
“BallClass.cpp”: The ballclass.cpp file creates the class for the ball and all the functions that are associated with it.
“Main.cpp”: The main.cpp file is where the main bulk of the game will occur, it contains the large loops which the game and make use of all the other C++ files mentioned above.
Code Listings will be found on the next pages:
cloudbreaker.h
#ifndef CLOUDBREAKER_H // Makes sure the code in the header is only run once
#define CLOUDBREAKER_H
#include “allegro.h”
#include <stdlib.h>
#include <stdio.h>
// Definitions
#define BLACK makecol(0,0,0)
#define WHITE makecol(255,255,255)
#define WIDTH 480
#define HEIGHT 800
#define GAMEPLAYDISTANCEFROMSCREENWIDTH 18
#define GAMEPLAYDISTANCEFROMSCREENTOP 208
#define GAMEPLAYDISTANCEFROMSCREENBOTTOM 44
#define MODE GFX_AUTODETECT_WINDOWED
#define BASEYPOSITION 680
// Definitons
extern int cloudsremaining[15][4]; // CLOUD NUMBER, CLOUD FACE, CLOUD X POSITION, CLOUD Y POSITION
//function prototypes
int loadpresetslevel_1();
int loadpresetslevel_2();
int loadpresetslevel_3();
int loadpresetslevel_4();
int loadpresetslevel_5();
class ballclass;
//function prototypes
//class
class ballclass
{
public:
int x,y;
int width,height;
int xspeed,yspeed;
int xdelay,ydelay;
int xcount,ycount;
int curframe,maxframe,animdir;
int framecount,framedelay;
bool balllaunched;
BITMAP *ballimage;
//Constructor and Descructor
ballclass(BITMAP *theballimg);
~ballclass();
void reset();
void updateposition();
bool ballmissed(int basexpos,int baselength, bool isballbelow);
};
//class
#endif
levels.cpp
#include “cloudbreaker.h”
int loadpresetslevel_1()
{
//for level 1
cloudsremaining[0][0] = 1;
cloudsremaining[1][0] = 1;
cloudsremaining[2][0] = 1;
cloudsremaining[0][1] = 0;
cloudsremaining[1][1] = 0;
cloudsremaining[2][1] = 0;
cloudsremaining[0][2] = 72 + GAMEPLAYDISTANCEFROMSCREENWIDTH;
cloudsremaining[0][3]= 90 + GAMEPLAYDISTANCEFROMSCREENTOP;
cloudsremaining[1][2] = 172 + GAMEPLAYDISTANCEFROMSCREENWIDTH;
cloudsremaining[1][3]= 90 + GAMEPLAYDISTANCEFROMSCREENTOP;
cloudsremaining[2][2] = 272 + GAMEPLAYDISTANCEFROMSCREENWIDTH;
cloudsremaining[2][3]= 90 + GAMEPLAYDISTANCEFROMSCREENTOP;
//for level 1
return 3; //Return number of clouds in the level
}
int loadpresetslevel_2(){
//for level 2
//Set ALIVE and reset faces
cloudsremaining[0][0] = 1;
cloudsremaining[1][0] = 1;
cloudsremaining[2][0] = 1;
cloudsremaining[3][0] = 1;
cloudsremaining[4][0] = 1;
cloudsremaining[0][1] = 0;
cloudsremaining[1][1] = 0;
cloudsremaining[2][1] = 0;
cloudsremaining[3][1] = 0;
cloudsremaining[4][1] = 0;
//Set ALIVE and reset faces
//Positions
cloudsremaining[0][2] = GAMEPLAYDISTANCEFROMSCREENWIDTH;
cloudsremaining[0][3]= 90 + GAMEPLAYDISTANCEFROMSCREENTOP;
cloudsremaining[1][2] = 172 + GAMEPLAYDISTANCEFROMSCREENWIDTH;
cloudsremaining[1][3]= 90 + GAMEPLAYDISTANCEFROMSCREENTOP;
cloudsremaining[2][2] = (480 – GAMEPLAYDISTANCEFROMSCREENWIDTH) – 100;
cloudsremaining[2][3]= 90 + GAMEPLAYDISTANCEFROMSCREENTOP;
cloudsremaining[3][2] = 172 + GAMEPLAYDISTANCEFROMSCREENWIDTH;
cloudsremaining[3][3]= 90 + GAMEPLAYDISTANCEFROMSCREENTOP – 50;
cloudsremaining[4][2] = 172 + GAMEPLAYDISTANCEFROMSCREENWIDTH;
cloudsremaining[4][3]= 90 + GAMEPLAYDISTANCEFROMSCREENTOP + 50;
//Positions
//for level 2
return 5;//Return number of clouds in the level
}
int loadpresetslevel_3(){
//for level 3
//Set ALIVE and Face
cloudsremaining[0][0] = 1;
cloudsremaining[1][0] = 1;
cloudsremaining[2][0] = 1;
cloudsremaining[3][0] = 1;
cloudsremaining[4][0] = 1;
cloudsremaining[5][0] = 1;
cloudsremaining[0][1] = 0;
cloudsremaining[1][1] = 0;
cloudsremaining[2][1] = 0;
cloudsremaining[3][1] = 0;
cloudsremaining[4][1] = 0;
cloudsremaining[5][1] = 0;
//Positions
cloudsremaining[0][2] = 19;
cloudsremaining[0][3]= 208;
cloudsremaining[1][2] = 363;
cloudsremaining[1][3]= 208;
cloudsremaining[2][2] = 19;
cloudsremaining[2][3]= 310;
cloudsremaining[3][2] = 363;
cloudsremaining[3][3]= 310;
cloudsremaining[4][2] = 19;
cloudsremaining[4][3]= 408;
cloudsremaining[5][2] = 363;
cloudsremaining[5][3]= 408;
//Positions
//for level 3
//Return number of clouds in the level
return 6;
}
int loadpresetslevel_4(){
//for level 3
//Set ALIVE and Face
cloudsremaining[0][0] = 1;
cloudsremaining[1][0] = 1;
cloudsremaining[2][0] = 1;
cloudsremaining[3][0] = 1;
cloudsremaining[4][0] = 1;
cloudsremaining[5][0] = 1;
cloudsremaining[6][0] = 1;
cloudsremaining[7][0] = 1;
cloudsremaining[8][0] = 1;
cloudsremaining[0][1] = 0;
cloudsremaining[1][1] = 0;
cloudsremaining[2][1] = 0;
cloudsremaining[3][1] = 0;
cloudsremaining[4][1] = 0;
cloudsremaining[5][1] = 0;
cloudsremaining[6][1] = 0;
cloudsremaining[7][1] = 0;
cloudsremaining[8][1] = 0;
//187
//Positions
cloudsremaining[0][2] = 19;
cloudsremaining[0][3]= 208;
cloudsremaining[1][2] = 363;
cloudsremaining[1][3]= 208;
cloudsremaining[2][2] = 19;
cloudsremaining[2][3]= 310;
cloudsremaining[3][2] = 363;
cloudsremaining[3][3]= 310;
cloudsremaining[4][2] = 19;
cloudsremaining[4][3]= 408;
cloudsremaining[5][2] = 363;
cloudsremaining[5][3]= 408;
cloudsremaining[6][2] = 187;
cloudsremaining[6][3]= 208;
cloudsremaining[7][2] = 187;
cloudsremaining[7][3]= 310;
cloudsremaining[8][2] = 187;
cloudsremaining[8][3]= 408;
//for level 3
//Return number of clouds in the level
return 9;
}
int loadpresetslevel_5(){
//for level 3
//Set ALIVE and Face
cloudsremaining[0][0] = 1;
cloudsremaining[1][0] = 1;
cloudsremaining[2][0] = 1;
cloudsremaining[3][0] = 1;
cloudsremaining[4][0] = 1;
cloudsremaining[5][0] = 1;
cloudsremaining[6][0] = 1;
cloudsremaining[7][0] = 1;
cloudsremaining[8][0] = 1;
cloudsremaining[9][0] = 1;
cloudsremaining[10][0] = 1;
cloudsremaining[11][0] = 1;
cloudsremaining[12][0] = 1;
cloudsremaining[13][0] = 1;
cloudsremaining[14][0] = 1;
cloudsremaining[8][0] = 1;
cloudsremaining[0][1] = 0;
cloudsremaining[1][1] = 0;
cloudsremaining[2][1] = 0;
cloudsremaining[3][1] = 0;
cloudsremaining[4][1] = 0;
cloudsremaining[5][1] = 0;
cloudsremaining[6][1] = 0;
cloudsremaining[7][1] = 0;
cloudsremaining[8][1] = 0;
cloudsremaining[9][1] = 0;
cloudsremaining[10][1] = 0;
cloudsremaining[11][1] = 0;
cloudsremaining[12][1] = 0;
cloudsremaining[13][1] = 0;
cloudsremaining[14][1] = 0;
//left
cloudsremaining[0][2] = 19;
cloudsremaining[0][3]= 208;
cloudsremaining[1][2] = 19;
cloudsremaining[1][3]= 258;
cloudsremaining[2][2] = 19;
cloudsremaining[2][3]= 308;
cloudsremaining[3][2] = 19;
cloudsremaining[3][3]= 358;
cloudsremaining[4][2] = 19;
cloudsremaining[4][3]= 408;
cloudsremaining[5][2] = 119;
cloudsremaining[5][3]= 408;
//left
//right
cloudsremaining[6][2] = 363;
cloudsremaining[6][3]= 208;
cloudsremaining[7][2] = 363;
cloudsremaining[7][3]= 258;
cloudsremaining[8][2] = 363;
cloudsremaining[8][3]= 308;
cloudsremaining[9][2] = 363;
cloudsremaining[9][3]= 358;
cloudsremaining[10][2] = 363;
cloudsremaining[10][3]= 408;
cloudsremaining[11][2] = 263;
cloudsremaining[11][3]= 408;
//right
//middle clouds
cloudsremaining[12][2] = 190;
cloudsremaining[12][3]= 208;
cloudsremaining[13][2] = 190;
cloudsremaining[13][3]= 258;
cloudsremaining[14][2] = 190;
cloudsremaining[14][3]= 308;
//middle clouds
//Return number of clouds in the level
return 15;
}
BallClass.cpp
#include “cloudbreaker.h”
//ballcalss
ballclass::ballclass(BITMAP *theballimg)
{
ballimage = theballimg;
x = SCREEN_W / 2 + ballimage->w;
y = BASEYPOSITION – ballimage->h;
width = ballimage->w;
height = ballimage->h;
xdelay = 1;
ydelay = 1;
xcount = 0;
ycount = 0;
if (rand() % 2 == 0) // chooses a random x speed for the ball, whether it goes left or right
{
xspeed = rand() % 2 + 1;
}
else
{
xspeed = rand() % 1 -2;
}
yspeed = rand() % 2 – 3;
curframe = rand() % 64;
maxframe = 63;
framecount = 0;
framedelay = rand() % 5 + 1;
balllaunched = false;
}
ballclass::~ballclass()
{
//Destructor
}
void ballclass::reset() // resets all variables in the class to their standard values
{
x = SCREEN_W / 2 + ballimage->w;
y = BASEYPOSITION – ballimage->h;
width = ballimage->w;
height = ballimage->h;
xdelay = 1;
ydelay = 1;
xcount = 0;
ycount = 0;
if (rand() % 2 == 0)
{
xspeed = rand() % 2 + 1;
}
else
{
xspeed = rand() % 1 -2;
}
yspeed = rand() % 2 – 3;
curframe = rand() % 64;
maxframe = 63;
framecount = 0;
framedelay = rand() % 5 + 1;
balllaunched = false;
}
void ballclass::updateposition()
{
//update x position
if (++xcount > xdelay)
{
xcount = 0;
x += xspeed;
}
//update y position
if (++ycount > ydelay)
{
ycount = 0;
y += yspeed;
}
}
bool ballclass::ballmissed(int basexpos,int baselength,bool isballbelow)
{
if (y > BASEYPOSITION) // IF YOU HAVE MISSED THE BALL
{
isballbelow = true;
if(y + height > (SCREEN_H – GAMEPLAYDISTANCEFROMSCREENBOTTOM))
{
isballbelow = true;
x = (basexpos + (baselength / 2) – (width / 2));
y = BASEYPOSITION – height;
xspeed = 3;
yspeed = -2;
return true;
}
}
return false;
}
//ballclass
Main.cpp
As the Main.cpp file is the largest one in the game I have only included the functions that are included within the main program. To view all code in Main.cpp check the DVD.
//function prototypes
int main(void);
void init();
void erasesprite(BITMAP *dest);
void bouncesprite(ballclass &spr);
int collidedcheck(ballclass &ball, int cloudsleft[][4],int numberofclouds);
void drawcloudslevel(BITMAP *dest, int cloudsleft[][4],int noofclouds);
void resetcloudarray(int cloudsleft[][4]);
void restingcallback(void);
void mousecontrols (bool currentgameover, int &basexpos, BITMAP *base);
void updatebasepos(int &basexpos,bool currentgameover, BITMAP *base);
void basehitcheck(int basexpos, ballclass &ball, int baselength);
bool ballmissed(ballclass &ball,int basexpos,int baselength);
void drawflowers(BITMAP *dest);
void Drawmainscreen(BITMAP *dest, ballclass &ball, int basexpos, BITMAP *ballimage, int currentlevelselection);
void gameovercheckanddrawrain(bool ¤tgameover, BITMAP *dest);
void levelselectionloop(BITMAP *buffer1, BITMAP *arrow,BITMAP *levelbackground,BITMAP *rules);
void drawsunrotate(BITMAP *sunface, BITMAP *sunflare, BITMAP *dest, float &angle);
void updateframerate();
void waitforusertolaunch(ballclass &theball,int &basexpos,BITMAP *buffer1, BITMAP *theballimg,bool ¤tgameover,int &temploop);
void WINorLOSE(bool ¤tgameover, BITMAP *buffer1,ballclass &theball, int basexpos, BITMAP *theballimg,BITMAP *sunface, BITMAP *sunflare, float angle);
//function prototypes
Testing & problems:
One of the biggest problems I faced while creating the game was the collision detection of the ball with the clouds. I came up with the algorithm to detect whether the cloud had been hit fairly quickly as it was simple seeing if the x and y positions are inside the clouds.
The big problems raised when I had to detect which side the cloud was hit and what the ball should do afterwards since. I drew out lots of ideas on paper in order to try and get the ball to collide and then bounce off in the opposite direction but there was always something that went wrong, usually the ball colliding and the direction of the ball not changing properly so the cloud would collide with the ball over 5 times and would be destroyed instantly with just one hit.
After much thought and design I eventually came up with a design which decides where the ball collides by using a different corner of the ball to decide which edge has been hit because there is always one corner that cannot be above one edge of the cloud if another is elsewhere so drawing that out I managed to work out the algorithm.
The image below shows my notes for my final design of the ball collision and the code can be found in the function: int collidedcheck(ballclass &ball, int cloudsleft[][4],int numberofclouds);
Other tests once the game was complete and expected results:
Test | Expected Result | Actual Result |
Pressing Enter at the welcome screen | Game progresses to level select screen | Game progresses to level select screen |
Pressing up and down arrow keys moves on the level selection | Pressing down button moves arrow down 1 level until the bottom and the same for the up button. | Pressing down button moves arrow down 1 level until the bottom and the same for the up button. |
Press “R” to view rules | Rule screen appears | Rule screen appears |
When playing the game, lose all lives | Game over screen displayed and game ends | Game over screen displayed and game ends |
Win a game on any level | Game win screen and score displayed | Game win screen and score displayed |
When ball hits the darkest grey cloud | Cloud destroyed and rain starts falling | Cloud destroyed and rain starts falling |
When the main game background music ends | Main game background music restarts | Main game background music restarts |