Design of the Objects and General Program Flow

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 

The 'Lazer' header file is the entire collection of variables and methods that the laser beam needs to operate. This header file is self-contained so that it can be added to any other game, called, and used, just like anyone would call upon the use of a library.

All the variables are stored privately, in order to help prevent errors such as accidentally changing the position variables or it's kill count.

 

class lazer //lazer class
{
private: // private types
int x,y,z; // positions
int ID; // unique ID
int kill; // number of enemies killed
public: // public types
lazer *next; // next pointer
void initLight(); // initiate the lighting

lazer(int x, int y, int z, int ID)
:next(NULL), x(x), y(y), z(0), ID(ID), kill(0)
{ /* do nothing */ } // end constructor

void redraw(); // draw the lazer
int getID(); // get the ID
void setX(int); int getX(); // set & get for x
void setY(int); int getY(); // set & get for y
void setZ(int); int getZ(); // set & get for z
void addKill(); int getKill(); // add a kill for the lazer
}; // end lazer class

/* set positions */
void lazer::setX(int n) { x = n; } // set a new x position
void lazer::setY(int n) { y = n; } // set a new y position
void lazer::setZ(int n) { z = n; } // set a new z position
void lazer::addKill() { kill += 1; } // increase kill count

/* get positions */
int lazer::getX() { return x; } // return the x position
int lazer::getY() { return y; } // return the y position
int lazer::getZ() { return z; } // return the z position
int lazer::getID() { return ID; } // return the ID
int lazer::getKill(){ return kill; } // return the kill count

/* draws the both lazer beams */
void lazer::redraw()
{ initLight(); // turn on the light for this object
glLineWidth(5); // make the lines thick
glBegin(GL_LINES); // lazer drawing the lines
glVertex3f( x,y,z ); // draw the bottom
glVertex3f( x,y+30,z ); // draw the top
glEnd(); // stop drawing lines
glLineWidth(1); // reset back to normal
} // end drawLazer

 
Here we have the lighting that the laser beam uses. It has everything defined here, and uses the x,y,z positioning of the laser beam to position the light source directly where the laser is located. This number's can be altered freely to achieve different lighting effects and colours
 

/* initLight ****************************************************** *\
This method init's the lighting of the world
\* **************************************************************** */
void lazer::initLight()
{ GLfloat ambient[] = { 0.0, 0.0, 5.0, 0.5 };
GLfloat diffuse[] = { 1.0, 1.0, 1.0, 0.5 };
GLfloat specular[] = { 1.0, 1.0, 1.0, 1.0 };
GLfloat position[] = { x, y, z, 1.0 };
GLfloat lmodel_ambient[] = { 0.5, 0.5, 0.5, 0.5 };
GLfloat local_view[] = { 6.0 };

glLightfv( GL_LIGHT0, GL_AMBIENT, ambient );
glLightfv( GL_LIGHT0, GL_DIFFUSE, diffuse );
glLightfv( GL_LIGHT0, GL_SPECULAR, specular );
glLightfv( GL_LIGHT0, GL_POSITION, position );

glLightModelfv( GL_LIGHT_MODEL_AMBIENT, lmodel_ambient );
glLightModelfv( GL_LIGHT_MODEL_LOCAL_VIEWER, local_view );
} // end initLight

 
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The other objects such as the enemy header file, and the photon header file are kept in the same fashion as the laser header file.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
The following is a generic header file that stores some most-common functions that I've found myself using over the past year. This header file is something I attach to any of my programs that need random numbers. This header file is constantly growing as I find more everyday-use functions.
 
/* ************************************ *\
Author: Corrado Coia
Email: year7C0@gmail.com
Date: December 19, 2005
Title: Generic Method Storage
Type: Header File
\* ************************************ */
 

/* random ********************************************************* *\
This method accepts two int's, and returns a random interger
from x to y
This method is overloaded
\* **************************************************************** */
int random(int x, int y)
{ return (int)y*rand()/RAND_MAX + x; }


/* random ********************************************************* *\
This method returns a random interger
\* **************************************************************** */
int random()
{ return rand(); }


/* randomSign ***************************************************** *\
This method accepts an interger, and returns that interger with
either a positive, or a negitive sign
\* **************************************************************** */
int randomSign(int n)
{
float x = (float)rand()/RAND_MAX;
if (x < 0.5) return n;
else return -n;
} // end randomSign


/* normalize ****************************************************** *\
This method accepts a vector of length 3, and normalizes it,
returning the normalized vector
\* **************************************************************** */
float* normalize(float *vector)
{
float *vectorPrime = vector; // define a new vector from the old one

float v = sqrt(vector[0]*vector[0] + vector[1]*vector[1] + vector[2]*vector[2]);
v = 1/v; // calculate the denominator

for( int x=0; x <3; x++ ) // divide the orginal by the denominator
vectorPrime[x] = vectorPrime[x] * v;

return vectorPrime; // return the new vector
} // end normalize

 
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
The following is the main cpp file of the project. First off, all the header files I've composed were added, along with some standard libraries, such as gl.h and glut.h.
 
#if !defined(Linux) // if anything but Linux
#include <windows.h> // include the windows libraries
#else // else
#include <stdlib.h> // include this other library
#endif // end include if
 

/* libraries */
#include "gl.h" // include the gl libraries
#include "glut.h" // include the glut libraries
#include <ctime> // include the time functions (for random)
#include <math.h> // include the math libraries
#include <mmsystem.h> // include the sound libraries
#include <iostream> // include the input/output stream
using namespace std; // declare the namespace
 

/* project header files */
#include "methods.h" // include general methods
#include "stars.h" // include the stars in the sky
#include "lazer.h" // include the lazer beams
#include "crosshair.h" // include the crosshair
#include "enemy.h" // include enemies
#include "photon.h" // include the photon

#pragma comment(lib, "winmm.lib") // add sound parameter
#define worldSize 600 // define the square world size
 

 

Following the includes and defines, all the variables that help define the aspects of the game, such as life, the stars, flags used, and text used, are all defined within structures. All structures are initialized with the beginning numbers in the game (current life = 100, energy level = 2, etc)

 
/* global structure to hold star variables */
struct globalStar
{ star *list; // global list to hold stars
int next; // declare time 'til next star appears
int start; // declare the start time of the counter
int speed; // declare the speed of the stars
int maxCount; // maximum amount of stars in the screen
}; // end global struct


/* global structure to hold enemy variables */
struct globalEnemy
{ enemy *list; // global list to hold enemies
int next; // iterations 'til next enemy
int start; // global counter
int speed; // speed of the enemy
int interval; // interval of arrival
}; // end global struct


/* global structure to hold lazer variables */
struct globalLazer
{ lazer *list; // global list to hold lazer beam
int maxEnergy; // max number of lazer's at once
int curEnergy; // current energy level
int increment; // energy drain/gain
int speed; // speed of the lazer beam
}; // end global struct


/* global structure to hold photon */
struct globalPhoton
{ photon *list; // global list
float speed; // speed of the photon
int interval; // interval of photons
int current; // current kills
}; // end global struct


/* global structure to hold text */
struct globalText
{ char life[5]; // "Life"
char energy[7]; // "Energy"
char shots[7]; // "Shots:"
char hits[6]; // "Hits:"
char score[7]; // "Score:"
char paused[12]; // "-Paused-"
char gameOver[14]; // "-Game Over-"
char energyUp[13]; // "Energy Up!"
char title[19]; // "~Super Space Wars~"
char damage[14]; // "Damage Taken"
char photon[14]; // "Photon Ready!"
}; // end global struct


/* global structure to hold player stats */
struct globalStats
{ int maxLife; // maximum life
int curLife; // current life
int increment; // life drain
int totalShots; // total shots fired
int totalHits; // total hits scored
int counter; // global counter
int score; // the players score
}; // end global struct


/* global flags */
struct globalFlags
{ bool pause; // pauses the game
bool gameOver; // game over flag
bool lightswitch; // lightswitch
bool energy1; // energy level 1
bool energy2; // energy level 2
bool energy3; // energy level 3
bool photon; // photon death
}; // end global struct


/* global structure to hold crosshair variables */
struct pointer
{ int x, y, z; // global positions
}; // end pointer struct


// initialize the structures
struct globalStar gs = { NULL, 0, 0, 1, 50 };
struct globalEnemy ge = { NULL, 500, 0, 3, 400 };
struct globalLazer gl = { NULL, 20, 20, 10, 8 };
struct globalPhoton gp = { NULL, 0, 50 };
struct globalText gt = { "Life", "Energy", "Shots:", "Hits:",
    "Score:", "-PAUSED-", "-GAME OVER-", "Energy Up!",
   "-Super Space Wars-", "Damage Taken!", "Photon Ready!" };
struct globalStats gst = { 100, 100, 10, 0, 0, 0, 0 };
struct globalFlags gf = { false, false, false, false, false, false, false };
struct pointer aim = { 0, -worldSize+100, 0 };
 
 
Most importantly, the game uses the glutIdleFunc() to control the game. Everything that happens automatically (which is everything expect moving the cursor and firing the weapons) happens in the idle method. The idle method calls every appropriate method such as drawing the world, drawing and moving the stars, enemies, laser beams, calculating and updating scores, life etc etc.
 
/* idle *********************************************************** *\
This method calls all the methods that need to be run at each
frame
\* **************************************************************** */
void idle()
{
maintainGame(); // maintain the game stats

if (gf.pause) // if paused
{ glutSwapBuffers(); // swap buffers for smooth movement
glFlush(); // flush to refresh the swapped buffer
return; // if paused, do nothing
} // end if

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glMatrixMode(GL_MODELVIEW); // define the matrix mode to handle models

drawWorld(); // draw the world
drawStars(); // move and draw the stars
drawEnemy(); // move and draw the enemies
drawLazer(); // move and draw the lazer beams
drawPhoton(); // move and draw the photon

deleteStars(); // checks stars and deletes them
deleteEnemy(); // checks enemies and deletes them
deleteLazer(); // checks for off screen lazers and deletes them

crosshair(aim.x, aim.y, aim.z); // draw the crosshair at the mouse position

maintainStars(); // maintain the stars
maintainEnemy(); // maintain the enemies
 

Every pass though the idle function is one complete iteration in the game. Also, at the end of the idle function, the swapping of the back buffer to the front and the clearing of the new back buffer occurs. This is the only time this happens, as each of the above the methods all write to the back buffer, which has been flushed at the end of the prior iteration.

 
glutSwapBuffers(); // swap buffers for smooth movement
glFlush(); // flush to refresh the swapped buffer
} // end idle
 

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

 

Lastly, we have the main function which initiates the program and starts the glut functions. Also note that this is running for the 2D version. To run it with the 3D version, the glOrtho should be commented out, and the gluPerspective and gluLookAt should be uncommented.

 
int main(int argc, char **argv)
{
srand( (unsigned)time( NULL ) ); // generate number seed based on system clock
glutInit(&argc, argv); // use the agruments
glutInitWindowSize(worldSize,worldSize);

glutInitDisplayMode( GLUT_RGB | GLUT_SINGLE | GLUT_DOUBLE );
glutCreateWindow("Super Space Wars");

glMatrixMode(GL_PROJECTION); // adjust the camera
glLoadIdentity(); // reset the camera
glEnable(GL_DEPTH_TEST); // test the depth (enable it)
glOrtho(-worldSize,worldSize,-worldSize, worldSize,-worldSize,worldSize);
// gluPerspective(60, 1, 0.1, 100000); // define the distance to see
// gluLookAt(0,0,worldSize*2,0,0,0,0,1,0);

glMatrixMode(GL_MODELVIEW); // switch to adjusting models
glClearColor(0.0, 0.0, 0.0, 1.0); // set the background to black
glPushMatrix(); // add this matrix to the stack

glutDisplayFunc(drawWorld); // define the initial draw
glutIdleFunc(idle); // call the idle function
glutKeyboardFunc(keyboard); // define the keyboard function
glutSpecialFunc(special); // define special keyboard function
glutMouseFunc(mouse); // define the mouse state function
glutPassiveMotionFunc(mouse); // define the mouse movement function
glutMainLoop(); // start the main loop

return 0; // return 0 errors
} // end main
 

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

 
The following code is the keyboard command for the 'special keys' (non-character keys), as well as the function for the character keys. Note that pressing L (turning on and off the light), is all handled using flags. Those flags are read each step of the idle function.
 
/* special ******************************************************** *\
This function accepts keyboard commands and produces the
desired outcome via a simple swtich statement
\* **************************************************************** */
void special(int key, int x, int y)
{ if (gf.pause) return; // if paused, do nothing

switch(key)
{
case GLUT_KEY_LEFT: // move left
aim.x -= 20;
break;

case GLUT_KEY_RIGHT: // move right
aim.x += 20;
break;

case GLUT_KEY_UP: // fire lazer
makeLazer(x);
break;

case GLUT_KEY_DOWN: // fire photon
makePhoton(x);
break;

} // end switch
} // end special

 

/* keyboard ******************************************************* *\
This function accepts keyboard commands and produces the
desired outcome via a simple case statement
\* **************************************************************** */
void keyboard(unsigned char key, int x, int y)
{
switch (key){
case 'q': // quit
case 'Q':
exit(0);
break;

case 'p': // pause
case 'P':
if (gf.pause) gf.pause = false;
else gf.pause = true;
break;

case 'l': // lightswitch
case 'L':
if (gf.lightswitch) gf.lightswitch = false;
else gf.lightswitch = true;
if (gf.lightswitch) brighten();
else dim();
break;

default: // default catch
break;
} // end switch
} // end keyboard
 

 

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

 
And here we have the two mice functions. The passive mouse function which just updates the cursor position's each time the mouse is moved without any actions (such as clicking), then we have the regular mouse function which is action-based (left and right clicking). The two functions have the same name as they are overloaded. I love to overload similar functions.
 
/* mouse ********************************************************** *\
This function accepts mouse commands and produces the
desired outcome
\* **************************************************************** */
void mouse(int x, int y)
{ if (gf.pause) return; // if paused, do nothing
aim.x = x*2-worldSize; // else return coordinates
} // end mouse


/* mouse ********************************************************** *\
This function accepts mouse commands and produces the
desired outcome
\* **************************************************************** */
void mouse(int button, int state, int x, int y)
{ if (gf.pause) return; // if paused, do nothing

if (state == GLUT_DOWN) // mouse click
if (button==GLUT_LEFT_BUTTON) // fire lazer
makeLazer(x); // make the lazer
if (button==GLUT_RIGHT_BUTTON) // fire photon
makePhoton(x); // make the photon
} // end mouse
 

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

 
Lastly, here is an example of what exactly happens when a laser beam is made. Note the sound, and note how easy it is.
 
/* makeLazer ****************************************************** *\
This method adds a lazer to the list of current lazers
\* **************************************************************** */
void makeLazer(int x)
{
if (gl.curEnergy <= 0) return; // if no energy left, you can't fire
else gl.curEnergy -= gl.increment; // else take away energy from the shot

char* WAV = "laserfire3.wav"; // define what to play
sndPlaySound(WAV, SND_ASYNC); // then play it

gst.totalShots += 1; // increase total shots fired
lazer *l = gl.list; // assign pointer to the head of the list

if (l == NULL) // if the list is empty
{ lazer *newLazer = new lazer(aim.x, aim.y, aim.z, 1);
gl.list = newLazer; // point the list to a new lazer
} // end if
else // else the list has at least one lazer
{ while (l->next) // while i have a next
l = l->next; // advance the pointer

int ID = l->getID() + 1; // increase the ID value
lazer *newLazer = new lazer(aim.x, aim.y, aim.z, ID);

l->next = newLazer; // set the next (null) to the new lazer
} // end else
} // end makeLazer
 
Well, I hope that gives an insight into my code. If you have any future questions or comments or suggestions, feel free to email me.