CIS Logo SVC Logo

   Computing & Information Systems
   Department

 

Schoology Facebook        Search CIS Site      Tutorials

Software Design Using C++



Beginner-Level GDK Capstone Programs



Overview


Thus far in our tutorials utilizing Dark GDK, we have attempted to present programs which have direct relevance to the lessons with which they are associated. Now, as we approach the end of our beginning tutorials it seems appropriate that we should have at least a few unrelated programs, just as we looked at some introductory material at the beginning of the lessons. We will consider two simple programs which combine a variety of what we have done this semester without exhausting it, however. As we continue through the intermediary material and the advanced material, our creations will be of far greater complexity than these simple programs.

Nevertheless, let us go off the beaten path to look at two final edifying example programs.



Simple Asteroid Avoiding Game

What would be 2D graphics programming without something like a moving sprite on the screen, controlled by the user? Better yet, what would it be without having to avoid asteroids? Well, we're not going to look at the case of the plural if for no reason than to keep the complexity of the program down for these tutorials. However, we will now look at a little game which draws a spaceship on the screen as well as an asteroid which begins moving in a random direction. The code is available at MovingSprite.cpp.

Most of our initialization protocol is the same. There is one new function which is called at the beginning after everything has been initialized, namely dbRotateSprite. Since this function will play a role below, we will consider it now. Simply put this function rotates a given sprite, identified by the first parameter by a given amount of degrees in the clock-wise direction. Every sprite is rendered with an initial "direction" which is rotated in addition to the image. Initially, the direction is upward which is defined by the normalized 2-dimensional vector <0,-1>, having no x component and a negative y component. As we rotate the sprite, this direction is rotated too. When we call the dbMoveSprite function, we move the sprite which is specified by the first parameter by the distance indicated by the second parameter, in the current forward direction of the sprite. The reason that we rotate the asteroid by a random amount is to give it a random direction to fly through the space setting.

The only really substantial section in this code is the main event loop:


while(LoopGDK() && !dbSpriteHit (2,3)) {
   // Check for forward / backward thrust
   if(dbUpKey()) {
	  dbMoveSprite(2,spriteVelocity);
   } else if (dbDownKey()) {
	  dbMoveSprite(2,-spriteVelocity);
   }

   // Check for rotation
   if(dbLeftKey()) {
	  spriteAngle -= spriteRotationAmount;
	  dbRotateSprite(2,spriteAngle);
   } else if(dbRightKey()) {
	  spriteAngle += spriteRotationAmount;
	  dbRotateSprite(2,spriteAngle);
   }
   dbMoveSprite(3,asteroidVelocity);
   // See if the sprites went off the screen and act appropriately
   spriteWrapAround(2);
   spriteWrapAround(3);
   dbSync();
}


There are two checks, one for a thust motion and one for a rotational motion. If the up or down key is depressed, it will register with the respective dbUpKey / dbDownKey function. The same is true for left and right, which are used to control the rotation of the sprite (which as we note is not an ongoing amount but one which is continually incremented or decremented. In prudence, we should keep the rotation within -360 and 360 to prevent overflow, but that is not of critical import for us at this point. As you can see, the ship is moved if up / down is depressed and the asteroid is moved at every iteration. Finally, there is the function which checks to see if the sprite should wrap around once it goes off the boundary of the screen. In the code itself, the deficiencies of this function are noted. Once again, it is left as is for the sake of simplicity in this level of classes.

The final consideration is the dbSpriteHit function call. This Dark GDK function will return an integer 1 if the two sprites have had their bounding boxes collide. This by itself should expose the weakness of this method. This works relatively fine if you have sprites which take up almost all of their image area. However, if you have a lot of transparency around the edges, sprites will "collide" when in all actuality they are merely overlapping clear areas. Because of this, we have to be careful with our image creation or work on a checking method which verifies that non-transparent areas are hitting each other.



There are many ways by which you could extend this program, though I leave that in your hands, since we will revisit this in the intermediate section of the book to add various features based on the lesson contents. The whole program and its dependent files can be found at in MovingSprite.zip.



Mouse Input

Our final example is one which uses mouse input, MouseInput.cpp. Also, you may want to save drawing.txt to the executing directory for your program. You will see the role of this file as we discuss this example. This should remind you of the work we did in ShapeLoader.cpp and CreateShapeFile.cpp with a deal more interactivity! First, we should lay out the overall flow of the program logic:

  1. Program start; General initialization
  2. Load data file with last drawing state; draw last state
  3. Main Loop:
    1. Check for clear screen indicator; if so clear screen and file
    2. Check for mouse clicks; output appropriate shape to screen and file


Although this is our longest program, the code is, on the whole, self-explanatory. Therefore, our explication of it will not be cryptic at all. However, let us try to be explicit about those sections which have new code.


fstream OutFile;

...

// Perform necessary start files
if(!loadFileContents())
{
  dbPrint("Failed to load save-state file!\nPress any key to terminate program.");
  dbWaitKey();
  return;
}

OutFile.open(DRAWING_FILE_NAME,ios::app);

if(OutFile.fail()) {
  dbPrint("Failed to open save-state file!\nPress any key to terminate program.");
  dbWaitKey();
  return;
}


As said above, this is rather self-explanatory, though let us make a few comments to be explicit with our intentions. The function "loadFileContents" is almost exactly the same as what we did in ShapeLoader.cpp. This opens drawing.txt and draws the contents to the screen just as was done in the aforementioned example. (The file must exist at least as an empty file, though if we wanted to do so, we could check for the file's existence and not return an error in this case.) If that is successful, we then open the same file as an appended file so that we can continue to add drawing shapes based on the user's input.


Next, we have the main event loop:
// Main event loop while(LoopGDK()) { // Check to see if the space bar has been pressed (to indicate a screen clear) if(dbSpaceKey()) { dbCLS(); OutFile.close(); OutFile.open(DRAWING_FILE_NAME,ios::out); if(OutFile.fail()) { dbPrint("File Error! Aborting!\nPress any key to terminate program."); dbWaitKey(); return; } } // Get mouse information mouseButtonNum = dbMouseClick(); mouseX = dbMouseX(); mouseY = dbMouseY(); // Decide if the shape will be filled filled = dbControlKey(); // Check to see if any buttons are down if(mouseButtonNum == 1) { performRectangle(mouseX,mouseY,filled,OutFile); // A rather primitive way to wait for the left button to come up // without using threads while(dbMouseClick() == 1); } else if(mouseButtonNum == 2) { performCircle(mouseX,mouseY,filled,OutFile); // A rather primitive way to wait for the left button to come up // without using threads while(dbMouseClick() == 2); } // Wait a millisecond to keep from locking up with a fast-executing loop dbWait(1); }


This is a bit longer of a section but is by no means difficult to understand. The first block of code, handled in the conditional "if(dbSpaceKey())" is executed if the space bar is pressed by the user. In this case, the program clears the screen, closes the file, and reopens it as a "write" file. This will clear the file and set the file pointer to the beginning of it. On failure, the program alerts the user.

Although the next few commands are new, their function is rather obvious, retrieving the state of the mouse and the control key. Using this information, the next conditional block handles the rendering of a rectangle or circle if the appropriate button is clicked. The contents of those functions make any necessary computations and perform the output both to the screen and the status file. As you can see, we are using a rather primitive mechanism to register mouse-up events. Once again, this is in order to keep things simple for our examples here. The best way to do this would be to have a listening thread which manages input. Finally, we wait for a millisecond to keep the program from bogging up with what is effectively an infinite loop. The end of the application just manages the cleanup of the status file.



Conclusion

We have covered some ground in this first course, though there is much more that we can do in the coming two sets of sections. Learn these concepts thoroughly because we will pick up exactly where we have left off, plowing forward in the next section of this text. Perhaps you can think of some ways to modify these files. That would make for a great tool in deeply learning to use the toolkit.



Back to the main page for Software Design Using C++

Author: Br. David Carlson with contributions by Br. Isidore Minerd
Last updated: March 21, 2015
Disclaimer