PlaySound("data/shot.wav", NULL, SND_ASYNC); // Play Gun Shot Sound
Now we set up a viewport. viewport[] will hold the current x, y, length and width of the current viewport (OpenGL Window).
glGetIntegerv(GL_VIEWPORT, viewport) gets the current viewport boundries and stores them in viewport[]. Initially, the boundries are equal the the OpenGL window dimensions. glSelectBuffer(512, buffer) tells OpenGL to use buffer for it's selection buffer.
// The Size Of The Viewport. [0] Is <x>, [1] Is <y>, [2] Is <length>, [3] Is <width>
GLint viewport[4];
// This Sets The Array <viewport> To The Size And Location Of The Screen Relative To The Window
glGetIntegerv(GL_VIEWPORT, viewport);
glSelectBuffer(512, buffer); // Tell OpenGL To Use Our Array For Selection
All of the code below is very important. The first line puts OpenGL in selection mode. In selection mode, nothing is drawn to the screen. Instead, information about objects rendered while in selection mode will be stored in the selection buffer.
Next we initialize the name stack by calling glInitNames() and glPushName(0). It's important to note that if the program is not in selection mode, a call to glPushName() will be ignored. Of course we are in selection mode, but it's something to keep in mind.
// Puts OpenGL In Selection Mode. Nothing Will Be Drawn. Object ID's and Extents Are Stored In The Buffer.
(void) glRenderMode(GL_SELECT);
glInitNames(); // Initializes The Name Stack
glPushName(0); // Push 0 (At Least One Entry) Onto The Stack
After preparing the name stack, we have to to restrict drawing to the area just under our crosshair. In order to do this we have to select the projection matrix. After selecting the projection matrix we push it onto the stack. We then reset the projection matrix using glLoadIdentity().
We restrict drawing using gluPickMatrix(). The first parameter is our current mouse position on the x-axis, the second parameter is the current mouse position on the y-axis, then the width and height of the picking region. Finally the current viewport[]. The viewport[] indicates the current viewport boundaries. mouse_x and mouse_y will be the center of the picking region.
glMatrixMode(GL_PROJECTION); // Selects The Projection Matrix
glPushMatrix(); // Push The Projection Matrix
glLoadIdentity(); // Resets The Matrix
// This Creates A Matrix That Will Zoom Up To A Small Portion Of The Screen, Where The Mouse Is.
gluPickMatrix((GLdouble) mouse_x, (GLdouble) (viewport[3]-mouse_y), 1.0f, 1.0f, viewport);
Calling gluPerspective() multiplies the perspective matrix by the pick matrix which restricts the drawing to the area requested by gluPickMatrix().
We then switch to the modelview matrix and draw our targets by calling DrawTargets(). We draw the targets in DrawTargets() and not in Draw() because we only want selection to check for hits with objects (targets) and not the sky, ground or crosshair.
After drawing our targets, we switch back to the projection matrix and pop the stored matrix off the stack. We then switch back to the modelview matrix.
The last line of code below switches back to render mode so that objects we draw actually appear on the screen. hits will hold the number of objects that were rendered in the viewing area requested by gluPickMatrix().
// Apply The Perspective Matrix
gluPerspective(45.0f, (GLfloat) (viewport[2]-viewport[0])/(GLfloat) (viewport[3]-viewport[1]), 0.1f, 100.0f);
glMatrixMode(GL_MODELVIEW); // Select The Modelview Matrix
DrawTargets(); // Render The Targets To The Selection Buffer
glMatrixMode(GL_PROJECTION); // Select The Projection Matrix
glPopMatrix(); // Pop The Projection Matrix
glMatrixMode(GL_MODELVIEW); // Select The Modelview Matrix
hits=glRenderMode(GL_RENDER); // Switch To Render Mode, Find Out How Many
Now we check to see if there were more than 0 hits recorded. if so, we set choose to equal the name of the first object drawn into the picking area. depth holds how deep into the screen, the object is.
Each hit takes 4 items in the buffer. The first item is the number of names on the name stack when the hit occured. The second item is the minimum z value of all the verticies that intersected the viewing area at the time of the hit. The third item is the maximum z value of all the vertices that intersected the viewing area at the time of the hit and the last item is the content of the name stack at the time of the hit (name of the object). We are only interested in the minimum z value and the object name in this tutorial.
if (hits > 0) // If There Were More Than 0 Hits
{
int choose = buffer[3]; // Make Our Selection The First Object
int depth = buffer[1]; // Store How Far Away It Is
We then loop through all of the hits to make sure none of the objects are closer than the first object hit. If we didn't do this, and two objects were overlapping, the first object hit might behind another object, and clicking the mouse would take away the first object, even though it was behind another object. When you shoot at something, the closest object should be the object that gets hit.
So, we check through all of the hits. Remember that each object takes 4 items in the buffer, so to search through each hit we have to multiply the current loop value by 4. We add 1 to get the depth of each object hit. If the depth is less than the the current selected objects depth, we store the name of the closer object in choose and we store the depth of the closer object in depth. After we have looped through all of our hits, choose will hold the name of the closest object hit, and depth will hold the depth of the closest object hit.
for (int loop = 1; loop < hits; loop++) // Loop Through All The Detected Hits
{
// If This Object Is Closer To Us Than The One We Have Selected
if (buffer[loop*4+1] < GLuint(depth)) {
choose = buffer[loop*4+3]; // Select The Closer Object
depth = buffer[loop*4+1]; // Store How Far Away It Is
}
}
All we have to do is mark the object as being hit. We check to make sure the object has not already been hit. If it has not been hit, we mark it as being hit by setting hit to TRUE. We increase the players score by 1 point, and we increase the kills counter by 1.
if (!object[choose].hit) // If The Object Hasn't Already Been Hit
{
object[choose].hit=TRUE; // Mark The Object As Being Hit
score+=1; // Increase Score
kills+=1; // Increase Level Kills
I use kills to keep track of how many objects have been destroyed on each level. I wanted each level to have more objects (making it harder to get through the level). So I check to see if the players kills is greater than the current level multiplied by 5. On level 1, the player only has to kill 5 objects (1*5). On level 2 the player has to kill 10 objects (2*5), progressively getting harder each level.
So, the first line of code checks to see if kills is higher than the level multiplied by 5. If so, we set miss to 0. This sets the player morale back to 10 out of 10 (the morale is 10-miss). We then set kills to 0 (which starts the counting process over again).
Finally, we increase the value of level by 1 and check to see if we've hit the last level. I have set the maximum level to 30 for the following two reasons… Level 30 is insanely difficult. I am pretty sure no one will ever have that good of a game. The second reason… At the top of the code, we only set up 30 objects. If you want more objects, you have to increase the value accordingly.