For this project you will write a two-dimensional animation program using OpenGL and GLUT.
Once upon a time, in a galaxy far, far away...
...there was a game called Space Invaders.
For this project you will write a program in C++ or C using OpenGL that plays a simplified version of this game or some other arcade-style video game. If you want to use another game as the basis for your project you speak with the professor.
But first, some discussion about animation.
For our purposes animation refers to the repeated generation of frames in a smooth sequence. In between each frame the animation state variables are updated so that when the next frame is drawn it will correctly show the next step in the animation sequence.
A first stab at an animation routine might look like
while ( not done ) do update position information render scene end while
It is often the case that the time taken for each pass through the loop is constant, and in some cases a delay may be added to the loop so that iterations occur at a known, fixed interval.
There is a major problem with this type of loop; it does not lend itself well to an event-driven system. GLUT, in providing an event-loop that is invoked with glutMainLoop(), implements a fairly coarse level of event processing. For example, once a callback function has been called to handle some event, no other events are processed until it returns. Thus, the main event loop is something like:
while ( true ) do wait for event if ( event has a callback ) then call associated callback function endif end while
This works well if each callback function runs in a fairly small amount of time. However, if the display callback used the animation loop above, then no other events (key presses, mouse clicks, etc) would be processed until the entire animation sequence was completed. (Note: Events that GLUT processes are distinct from events that X processes. It's just that GLUT chooses to ignore all possible events until the currently running callback function returns.)
One way to do animation with GLUT is to have the display callback draw a single frame of the animation, and to repeatedly generate display events, thus repeatedly activating the display callback. The way to properly generate display events is with the idle callback. When a function is registered with glutIdleFunc(), it will be called whenever there is no other event waiting to be processed.
Ideally, the display callback should be idempotent, meaning that if the animation state variables are unchanged then it will draw the same scene no matter how many times it is called. Thus, we have a logical division of work:
Idle Callback | Display Callback |
---|---|
Registered with glutIdleFunc() | Registered with glutDisplayFunc() |
Responsible for updating animation state variables and generating a display event | Responsible for using current settings of animation state variables to generate a frame in the animation sequence. |
The animation callback function usually has the form
void cbAnimate(void) { // This comment should be replaced with code to update // the animation state variables. // Done updating, cause next frame to be displayed. glutPostRedisplay(); }
and is registered with the call glutIdleFunc(cbAnimate).
Because other events can be processed while the animation is proceeding, it is possible to stop and restart the animation in response to other events such as key presses or mouse clicks. The animation is stopped by calling glutIdleFunc(NULL) and restarted with glutIdleFunc(cbAnimate).
The program cube.cc provides an example of how animation can be carried out using the idle callback. It also shows how to use the window visibility information to toggle the animation so that no animation is preformed if the window is not visible.
One problem with the idle callback is that it draws one animation frame after another as fast as possible. When the cube program was first written it ran at a presentable speed; on our current workstations the rotation is merely a blur.
A better approach is to use a timer function that will cause the display to be redrawing at a constant rate that is independent of the processor and graphics display speed. GLUT handles this with the timer callback.
The main difference from the user perspective between the idle callback and the timer callback is the the timer callback only triggers once. As a result, the animation callback must not only update the state variables as described above, but reinitiate another timer callback. The animate callback can also now take a parameter, usually used to indicate if the animation should continue or not.
void cbAnimate( int animate ) { if ( animate ) { glutTimerFunc( TIME_BETWEEN_FRAMES, cbAnimate, doAnimation ); // This comment should be replaced with code to update // the animation state variables. // Done updating, cause next frame to be displayed. glutPostRedisplay(); } }
Note that while this function appears to be recursive since glutTimerFunc() references cbAnimate(); it is not; cbAnimate() will not be called again until TIME_BETWEEN_FRAMES milliseconds have elapsed.
The global variable doAnimation is used to determine whether or not the animation is active; see the program cube_timer.cc for an example of how animation can be carried out using the timer callback and the window visibility information to toggle the animation on and off. Note that the first timer callback is registered the first time the cbVisibile() function is called.
Do not attempt to use three-dimensional graphics for this project; you'll get a chance to do this with the next project.
Your program should, at a minimum, behave as follows:
Alien ships are arranged in rows and move left-right on the screen. When they reach an edge they move down one step and begin moving in the opposite direction.
Your ship moves left-right along the bottom of the screen. The mouse controls the motion of the ship. When the mouse moves the x-coordinate of the mouse is read. It is scaled to the range from -1 to 1 (where 0 is at the center of the screen) and multiplied times a constant to determine the velocity of the ship.
Pressing the left mouse button fires a missile from the current ship location up at the approaching enemy ships. These missiles move at a constant velocity.
Pressing the middle button should toggle the game between a paused state and the active state.
Pressing the right mouse button will pause the game, if it's not already paused; advance the game state by one step, and write to standard output the current game state (location and velocity of ships, location of missiles, etc.) This feature is for debugging and the format of the dumped information is up to you.
The 'q' key (either upper or lowercase) should terminate the game.
Note: It is not necessary for you to maintain any scoring information.
You will notice that much of the project is open-ended and so much of how the game looks and operates is left up to you.
The bottom most alien ship in each column will periodically drop a bomb that will travel straight down from the point at which it was released. If the bomb contacts your ship, your ship explodes.
Place three shields that your ship can hide behind between your ship and the enemy ships. These shields should be damaged (and reduced in size) each time a missile (yours or one from the enemy) hits them, and they should be obliterated (i.e. erased) when an enemy ship comes into contact with them.
Leave a copy of your program (source and executable) in your ~/cs373/p3 directory and turn in a hardcopy of the source.