新书推介:《语义网技术体系》
作者:瞿裕忠,胡伟,程龚
   XML论坛     W3CHINA.ORG讨论区     计算机科学论坛     SOAChina论坛     Blog     开放翻译计划     新浪微博  
 
  • 首页
  • 登录
  • 注册
  • 软件下载
  • 资料下载
  • 核心成员
  • 帮助
  •   Add to Google

    >> 本版讨论高级C/C++编程、代码重构(Refactoring)、极限编程(XP)、泛型编程等话题
    [返回] 计算机科学论坛计算机技术与应用『 C/C++编程思想 』 → [推荐]NeHe OpenGL教程(中英文版附带VC++源码)Lesson 21-lesson 22 查看新帖用户列表

      发表一个新主题  发表一个新投票  回复主题  (订阅本版) 您是本帖的第 38279 个阅读者浏览上一篇主题  刷新本主题   平板显示贴子 浏览下一篇主题
     * 贴子主题: [推荐]NeHe OpenGL教程(中英文版附带VC++源码)Lesson 21-lesson 22 举报  打印  推荐  IE收藏夹 
       本主题类别:     
     一分之千 帅哥哟,离线,有人找我吗?射手座1984-11-30
      
      
      威望:1
      等级:研一(随老板参加了WWW大会还和Tim Berners-Lee合了影^_^)
      文章:632
      积分:4379
      门派:XML.ORG.CN
      注册:2006/12/31

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给一分之千发送一个短消息 把一分之千加入好友 查看一分之千的个人资料 搜索一分之千在『 C/C++编程思想 』的所有贴子 引用回复这个贴子 回复这个贴子 查看一分之千的博客楼主
    发贴心情 

    Lesson 21
       
    Welcome to my 21st OpenGL Tutorial! Coming up with a topic for this tutorial was extremely difficult. I know alot of you are tired of learning the basics. Everyone is dying to learn about 3D objects, Multitexturing and all that other good stuff. For those people, I'm sorry, but I want to keep the learning curve gradual. Once I've gone a step ahead it's not as easy to take a step back without people losing interest. So I'd prefer to keep pushing forward at a steady pace.

    In case I've lost a few of you :) I'll tell you a bit about this tutorial. Until now all of my tutorials have used polygons, quads and triangles. So I decided it would be nice to write a tutorial on lines. A few hours after starting the line tutorial, I decided to call it quits. The tutorial was coming along fine, but it was BORING! Lines are great, but there's only so much you can do to make lines exciting. I read through my email, browsed through the message board, and wrote down a few of your tutorial requests. Out of all the requests there were a few questions that came up more than others. So... I decided to write a multi-tutorial :)

    In this tutorial you will learn about: Lines, Anti-Aliasing, Orthographic Projection, Timing, Basic Sound Effects, and Simple Game Logic. Hopefully there's enough in this tutorial to keep everyone happy :) I spent 2 days coding this tutorial, and It's taken almost 2 weeks to write this HTML file. I hope you enjoy my efforts!

    At the end of this tutorial you will have made a simple 'amidar' type game. Your mission is to fill in the grid without being caught by the bad guys. The game has levels, stages, lives, sound, and a secret item to help you progress through the levels when things get tough. Although this game will run fine on a Pentium 166 with a Voodoo 2, a faster processor is recommended if you want smoother animation.

    I used the code from lesson 1 as a starting point while writing this tutorial. We start off by adding the required header files. stdio.h is used for file operations, and we include stdarg.h so that we can display variables on the screen, such as the score and current stage.   
       

    // This Code Was Created By Jeff Molofee 2000
    // If You've Found This Code Useful, Please Let Me Know.

    #include <windows.h>       // Header File For Windows
    #include <stdio.h>       // Standard Input / Output
    #include <stdarg.h>       // Header File For Variable Argument Routines
    #include <gl\gl.h>       // Header File For The OpenGL32 Library
    #include <gl\glu.h>       // Header File For The GLu32 Library
    #include <gl\glaux.h>       // Header File For The Glaux Library

    HDC  hDC=NULL;       // Private GDI Device Context
    HGLRC  hRC=NULL;       // Permanent Rendering Context
    HWND  hWnd=NULL;       // Holds Our Window Handle
    HINSTANCE hInstance;       // Holds The Instance Of The Application

       
    Now we set up our boolean variables. vline keeps track of the 121 vertical lines that make up our game grid. 11 lines across and 11 up and down. hline keeps track of the 121 horizontal lines that make up the game grid. We use ap to keep track of whether or not the 'A' key is being pressed.

    filled is FALSE while the grid isn't filled and TRUE when it's been filled in. gameover is pretty obvious. If gameover is TRUE, that's it, the game is over, otherwise you're still playing. anti keeps track of antialiasing. If anti is TRUE, object antialiasing is ON. Otherwise it's off. active and fullscreen keep track of whether or not the program has been minimized or not, and whether you're running in fullscreen mode or windowed mode.   
       

    bool  keys[256];       // Array Used For The Keyboard Routine
    bool  vline[11][10];       // Keeps Track Of Verticle Lines
    bool  hline[10][11];       // Keeps Track Of Horizontal Lines
    bool  ap;        // 'A' Key Pressed?
    bool  filled;        // Done Filling In The Grid?
    bool  gameover;       // Is The Game Over?
    bool  anti=TRUE;       // Antialiasing?
    bool  active=TRUE;       // Window Active Flag Set To TRUE By Default
    bool  fullscreen=TRUE;      // Fullscreen Flag Set To Fullscreen Mode By Default

       
    Now we set up our integer variables. loop1 and loop2 will be used to check points on our grid, see if an enemy has hit us and to give objects random locations on the grid. You'll see loop1 / loop2 in action later in the program. delay is a counter variable that I use to slow down the bad guys. If delay is greater than a certain value, the enemies are moved and delay is set back to zero.

    The variable adjust is a very special variable! Even though this program has a timer, the timer only checks to see if your computer is too fast. If it is, a delay is created to slow the computer down. On my GeForce card, the program runs insanely smooth, and very very fast. After testing this program on my PIII/450 with a Voodoo 3500TV, I noticed that the program was running extremely slow. The problem is that my timing code only slows down the gameplay. It wont speed it up. So I made a new variable called adjust. adjust can be any value from 0 to 5. The objects in the game move at different speeds depending on the value of adjust. The lower the value the smoother they move, the higher the value, the faster they move (choppy at values higher than 3). This was the only real easy way to make the game playable on slow systems. One thing to note, no matter how fast the objects are moving the game speed will never run faster than I intended it to run. So setting the adjust value to 3 is safe for fast and slow systems.

    The variable lives is set to 5 so that you start the game with 5 lives. level is an internal variable. The game uses it to keep track of the level of difficulty. This is not the level that you will see on the screen. The variable level2 starts off with the same value as level but can increase forever depending on your skill. If you manage to get past level 3 the level variable will stop increasing at 3. The level variable is an internal variable used for game difficulty. The stage variable keeps track of the current game stage.   
       

    int  loop1;        // Generic Loop1
    int  loop2;        // Generic Loop2
    int  delay;        // Enemy Delay
    int  adjust=3;       // Speed Adjustment For Really Slow Video Cards
    int  lives=5;       // Player Lives
    int  level=1;       // Internal Game Level
    int  level2=level;       // Displayed Game Level
    int  stage=1;       // Game Stage

       
    Now we create a structure to keep track of the objects in our game. We have a fine X position (fx) and a fine Y position (fy). These variables will move the player and enemies around the grid a few pixels at a time. Creating a smooth moving object.

    Then we have x and y. These variables will keep track of what intersection our player is at. There are 11 points left and right and 11 points up and down. So x and y can be any value from 0 to 10. That is why we need the fine values. If we could only move one of 11 spots left and right and one of 11 spots up and down our player would jump around the screen in a quick (non smooth) motion.

    The last variable spin will be used to spin the objects on their z-axis.   
       

    struct  object        // Create A Structure For Our Player
    {
     int fx, fy;        // Fine Movement Position
     int x, y;        // Current Player Position
     float spin;        // Spin Direction
    };

       
    Now that we have created a structure that can be used for our player, enemies and even a special item we can create new structures that take on the characteristics of the structure we just made.

    The first line below creates a structure for our player. Basically we're giving our player structure fx, fy, x, y and spin values. By adding this line, we can access the player x position by checking player.x. We can change the player spin by adding a number to player.spin.

    The second line is a bit different. Because we can have up to 15 enemies on the screen at a time, we need to create the above variables for each enemy. We do this by making an array of 15 enemies. the x position of the first enemy will be enemy[0].x. The second enemy will be enemy[1].x, etc.

    The last line creates a structure for our special item. The special item is an hourglass that will appear on the screen from time to time. We need to keep track of the x and y values for the hourglass, but because the hourglass doesn't move, we don't need to keep track of the fine positions. Instead we will use the fine variables (fx and fy) for other things later in the program.   
       

    struct object player;        // Player Information
    struct object enemy[9];       // Enemy Information
    struct object hourglass;       // Hourglass Information

       
    Now we create a timer structure. We create a structure so that it's easier to keep track of timer variables and so that it's easier to tell that the variable is a timer variable.

    The first thing we do is create a 64 bit integer called frequency. This variable will hold the frequency of the timer. When I first wrote this program, I forgot to include this variable. I didn't realize that the frequency on one machine may not match the frequency on another. Big mistake on my part! The code ran fine on the 3 systems in my house, but when I tested it on a friends machine the game ran WAY to fast. Frequency is basically how fast the clock is updated. Good thing to keep track of :)

    The resolution variable keeps track of the steps it takes before we get 1 millisecond of time.

    mm_timer_start and mm_timer_elapsed hold the value that the timer started at, and the amount of time that has elapsed since the the timer was started. These two variables are only used if the computer doesn't have a performance counter. In that case we end up using the less accurate multimedia timer, which is still not to bad for a non-time critical game like this.

    The variable performance_timer can be either TRUE of FALSE. If the program detects a performance counter, the variable performance_timer variable is set to TRUE, and all timing is done using the performance counter (alot more accurate than the multimedia timer). If a performance counter is not found, performance_timer is set to FALSE and the multimedia timer is used for timing.

    The last 2 variables are 64 bit integer variables that hold the start time of the performance counter and the amount of time that has elapsed since the performance counter was started.

    The name of this structure is "timer" as you can see at the bottom of the structure. If we want to know the timer frequency we can now check timer.frequency. Nice!   
       

    struct           // Create A Structure For The Timer Information
    {
      __int64       frequency;       // Timer Frequency
      float         resolution;       // Timer Resolution
      unsigned long mm_timer_start;       // Multimedia Timer Start Value
      unsigned long mm_timer_elapsed;      // Multimedia Timer Elapsed Time
      bool  performance_timer;      // Using The Performance Timer?
      __int64       performance_timer_start;     // Performance Timer Start Value
      __int64       performance_timer_elapsed;     // Performance Timer Elapsed Time
    } timer;         // Structure Is Named timer

       
    The next line of code is our speed table. The objects in the game will move at a different rate depending on the value of adjust. If adjust is 0 the objects will move one pixel at a time. If the value of adjust is 5, the objects will move 20 pixels at a time. So by increasing the value of adjust the speed of the objects will increase, making the game run faster on slow computers. The higher adjust is however, the choppier the game will play.

    Basically steps[ ] is just a look-up table. If adjust was 3, we would look at the number stored at location 3 in steps[ ]. Location 0 holds the value 1, location 1 holds the value 2, location 2 holds the value 4, and location 3 hold the value 5. If adjust was 3, our objects would move 5 pixels at a time. Make sense?   
       

    int  steps[6]={ 1, 2, 4, 5, 10, 20 };    // Stepping Values For Slow Video Adjustment

       
    Next we make room for two textures. We'll load a background scene, and a bitmap font texture. Then we set up a base variable so we can keep track of our font display list just like we did in the other font tutorials. Finally we declare WndProc().   
       

    GLuint  texture[2];       // Font Texture Storage Space
    GLuint  base;        // Base Display List For The Font

    LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);    // Declaration For WndProc

       
    Now for the fun stuff :) The next section of code initializes our timer. It will check the computer to see if a performance counter is available (very accurate counter). If we don't have a performance counter the computer will use the multimedia timer. This code should be portable from what I'm told.

    We start off by clearing all the timer variables to zero. This will set all the variables in our timer structure to zero. After that, we check to see if there is NOT a performance counter. The ! means NOT. If there is, the frequency will be stored in timer.frequency.

    If there was no performance counter, the code in between the { }'s is run. The first line sets the variable timer.performance_timer to FALSE. This tells our program that there is no performance counter. The second line gets our starting multimedia timer value from timeGetTime(). We set the timer.resolution to 0.001f, and the timer.frequency to 1000. Because no time has elapsed yet, we make the elapsed time equal the start time.   
       

    void TimerInit(void)        // Initialize Our Timer (Get It Ready)
    {
     memset(&timer, 0, sizeof(timer));     // Clear Our Timer Structure

     // Check To See If A Performance Counter Is Available
     // If One Is Available The Timer Frequency Will Be Updated
     if (!QueryPerformanceFrequency((LARGE_INTEGER *) &timer.frequency))
     {
      // No Performace Counter Available
      timer.performance_timer = FALSE;    // Set Performance Timer To FALSE
      timer.mm_timer_start = timeGetTime();   // Use timeGetTime() To Get Current Time
      timer.resolution = 1.0f/1000.0f;    // Set Our Timer Resolution To .001f
      timer.frequency  = 1000;     // Set Our Timer Frequency To 1000
      timer.mm_timer_elapsed = timer.mm_timer_start;   // Set The Elapsed Time To The Current Time
     }

       
    If there is a performance counter, the following code is run instead. The first line grabs the current starting value of the performance counter, and stores it in timer.performance_timer_start. Then we set timer.performance_timer to TRUE so that our program knows there is a performance counter available. After that we calculate the timer resolution by using the frequency that we got when we checked for a performance counter in the code above. We divide 1 by the frequency to get the resolution. The last thing we do is make the elapsed time the same as the starting time.

    Notice instead of sharing variables for the performance and multimedia timer start and elapsed variables, I've decided to make seperate variables. Either way it will work fine.   
       

     else
     {
      // Performance Counter Is Available, Use It Instead Of The Multimedia Timer
      // Get The Current Time And Store It In performance_timer_start
      QueryPerformanceCounter((LARGE_INTEGER *) &timer.performance_timer_start);
      timer.performance_timer  = TRUE;    // Set Performance Timer To TRUE
      // Calculate The Timer Resolution Using The Timer Frequency
      timer.resolution  = (float) (((double)1.0f)/((double)timer.frequency));
      // Set The Elapsed Time To The Current Time
      timer.performance_timer_elapsed = timer.performance_timer_start;
     }
    }

       
    The section of code above sets up the timer. The code below reads the timer and returns the amount of time that has passed in milliseconds.

    The first thing we do is set up a 64 bit variable called time. We will use this variable to grab the current counter value. The next line checks to see if we have a performance counter. If we do, timer.performance_timer will be TRUE and the code right after will run.

    The first line of code inside the { }'s grabs the counter value and stores it in the variable we created called time. The second line takes the time we just grabbed (time and subtracts the start time that we got when we initialized the timer. This way our timer should start out pretty close to zero. We then multiply the results by the resolution to find out how many seconds have passed. The last thing we do is multiply the result by 1000 to figure out how many milliseconds have passed. After the calculation is done, our results are sent back to the section of code that called this procedure. The results will be in floating point format for greater accuracy.

    If we are not using the peformance counter, the code after the else statement will be run. It does pretty much the same thing. We grab the current time with timeGetTime() and subtract our starting counter value. We multiply it by our resolution and then multiply the result by 1000 to convert from seconds into milliseconds.   
       

    float TimerGetTime()        // Get Time In Milliseconds
    {
     __int64 time;        // time Will Hold A 64 Bit Integer

     if (timer.performance_timer)      // Are We Using The Performance Timer?
     {
      QueryPerformanceCounter((LARGE_INTEGER *) &time);  // Grab The Current Performance Time
      // Return The Current Time Minus The Start Time Multiplied By The Resolution And 1000 (To Get MS)
      return ( (float) ( time - timer.performance_timer_start) * timer.resolution)*1000.0f;
     }
     else
     {
      // Return The Current Time Minus The Start Time Multiplied By The Resolution And 1000 (To Get MS)
      return( (float) ( timeGetTime() - timer.mm_timer_start) * timer.resolution)*1000.0f;
     }
    }

       
    The following section of code resets the player to the top left corner of the screen, and gives the enemies a random starting point.

    The top left of the screen is 0 on the x-axis and 0 on the y-axis. So by setting the player.x value to 0 we move the player to the far left side of the screen. By setting the player.y value to 0 we move our player to the top of the screen.

    The fine positions have to be equal to the current player position, otherwise our player would move from whatever value it's at on the fine position to the top left of the screen. We don't want to player to move there, we want it to appear there, so we set the fine positions to 0 as well.   
       

    void ResetObjects(void)        // Reset Player And Enemies
    {
     player.x=0;        // Reset Player X Position To Far Left Of The Screen
     player.y=0;        // Reset Player Y Position To The Top Of The Screen
     player.fx=0;        // Set Fine X Position To Match
     player.fy=0;        // Set Fine Y Position To Match

       
    Next we give the enemies a random starting location. The number of enemies displayed on the screen will be equal to the current (internal) level value multiplied by the current stage. Remember, the maximum value that level can equal is 3 and the maximum number of stages per level is 3. So we can have a total of 9 enemies.

    To make sure we give all the viewable enemies a new position, we loop through all the visible enemies (stage times level). We set each enemies x position to 5 plus a random value from 0 to 5. (the maximum value rand can be is always the number you specify minus 1). So the enemy can appear on the grid, anywhere from 5 to 10. We then give the enemy a random value on the y axis from 0 to 10.

    We don't want the enemy to move from it's old position to the new random position so we make sure the fine x (fx) and y (fy) values are equal to the actual x and y values multiplied by width and height of each tile on the screen. Each tile has a width of 60 and a height of 40.   
       

     for (loop1=0; loop1<(stage*level); loop1++)    // Loop Through All The Enemies
     {
      enemy[loop1].x=5+rand()%6;     // Select A Random X Position
      enemy[loop1].y=rand()%11;     // Select A Random Y Position
      enemy[loop1].fx=enemy[loop1].x*60;    // Set Fine X To Match
      enemy[loop1].fy=enemy[loop1].y*40;    // Set Fine Y To Match
     }
    }

       
    The AUX_RGBImageRec code hasn't changed so I'm skipping over it. In LoadGLTextures() we will load in our two textures. First the font bitmap (Font.bmp) and then the background image (Image.bmp). We'll convert both the images into textures that we can use in our game. After we have built the textures we clean up by deleting the bitmap information. Nothing really new. If you've read the other tutorials you should have no problems understanding the code.   
       

    int LoadGLTextures()        // Load Bitmaps And Convert To Textures
    {
     int Status=FALSE;       // Status Indicator
     AUX_RGBImageRec *TextureImage[2];     // Create Storage Space For The Textures
     memset(TextureImage,0,sizeof(void *)*2);    // Set The Pointer To NULL

     if  ((TextureImage[0]=LoadBMP("Data/Font.bmp")) &&   // Load The Font
       (TextureImage[1]=LoadBMP("Data/Image.bmp")))   // Load Background Image
     {
      Status=TRUE;       // Set The Status To TRUE

      glGenTextures(2, &texture[0]);     // Create The Texture

      for (loop1=0; loop1<2; loop1++)     // Loop Through 2 Textures
      {
       glBindTexture(GL_TEXTURE_2D, texture[loop1]);
       glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[loop1]->sizeX, TextureImage[loop1]->sizeY,
        0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[loop1]->data);
       glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
       glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
      }

      for (loop1=0; loop1<2; loop1++)     // Loop Through 2 Textures
      {
       if (TextureImage[loop1])    // If Texture Exists
       {
        if (TextureImage[loop1]->data)   // If Texture Image Exists
        {
         free(TextureImage[loop1]->data); // Free The Texture Image Memory
        }
        free(TextureImage[loop1]);   // Free The Image Structure
       }
      }
     }
     return Status;        // Return The Status
    }

       
    The code below builds our font display list. I've already done a tutorial on bitmap texture fonts. All the code does is divides the Font.bmp image into 16 x 16 cells (256 characters). Each 16x16 cell will become a character. Because I've set the y-axis up so that positive goes down instead of up, it's necessary to subtract our y-axis values from 1.0f. Otherwise the letters will all be upside down :) If you don't understand what's going on, go back and read the bitmap texture font tutorial.   
       

    GLvoid BuildFont(GLvoid)       // Build Our Font Display List
    {
     base=glGenLists(256);       // Creating 256 Display Lists
     glBindTexture(GL_TEXTURE_2D, texture[0]);    // Select Our Font Texture
     for (loop1=0; loop1<256; loop1++)     // Loop Through All 256 Lists
     {
      float cx=float(loop1%16)/16.0f;     // X Position Of Current Character
      float cy=float(loop1/16)/16.0f;     // Y Position Of Current Character

      glNewList(base+loop1,GL_COMPILE);    // Start Building A List
       glBegin(GL_QUADS);     // Use A Quad For Each Character
        glTexCoord2f(cx,1.0f-cy-0.0625f);  // Texture Coord (Bottom Left)
        glVertex2d(0,16);    // Vertex Coord (Bottom Left)
        glTexCoord2f(cx+0.0625f,1.0f-cy-0.0625f); // Texture Coord (Bottom Right)
        glVertex2i(16,16);    // Vertex Coord (Bottom Right)
        glTexCoord2f(cx+0.0625f,1.0f-cy);  // Texture Coord (Top Right)
        glVertex2i(16,0);    // Vertex Coord (Top Right)
        glTexCoord2f(cx,1.0f-cy);   // Texture Coord (Top Left)
        glVertex2i(0,0);    // Vertex Coord (Top Left)
       glEnd();      // Done Building Our Quad (Character)
       glTranslated(15,0,0);     // Move To The Right Of The Character
      glEndList();       // Done Building The Display List
     }         // Loop Until All 256 Are Built
    }

       
    It's a good idea to destroy the font display list when you're done with it, so I've added the following section of code. Again, nothing new.   
       

    GLvoid KillFont(GLvoid)        // Delete The Font From Memory
    {
     glDeleteLists(base,256);      // Delete All 256 Display Lists
    }

       
    The glPrint() code hasn't changed that much. The only difference from the tutorial on bitmap font textures is that I have added the ability to print the value of variables. The only reason I've written this section of code out is so that you can see the changes. The print statement will position the text at the x and y position that you specify. You can pick one of 2 character sets, and the value of variables will be written to the screen. This allows us to display the current level and stage on the screen.

    Notice that I enable texture mapping, reset the view and then translate to the proper x / y position. Also notice that if character set 0 is selected, the font is enlarged one and half times width wise, and double it's original size up and down. I did this so that I could write the title of the game in big letters. After the text has been drawn, I disable texture mapping.   
       

    GLvoid glPrint(GLint x, GLint y, int set, const char *fmt, ...)   // Where The Printing Happens
    {
     char  text[256];      // Holds Our String
     va_list  ap;       // Pointer To List Of Arguments

     if (fmt == NULL)       // If There's No Text
      return;        // Do Nothing

     va_start(ap, fmt);       // Parses The String For Variables
         vsprintf(text, fmt, ap);      // And Converts Symbols To Actual Numbers
     va_end(ap);        // Results Are Stored In Text

     if (set>1)        // Did User Choose An Invalid Character Set?
     {
      set=1;        // If So, Select Set 1 (Italic)
     }
     glEnable(GL_TEXTURE_2D);      // Enable Texture Mapping
     glLoadIdentity();       // Reset The Modelview Matrix
     glTranslated(x,y,0);       // Position The Text (0,0 - Bottom Left)
     glListBase(base-32+(128*set));      // Choose The Font Set (0 or 1)

     if (set==0)        // If Set 0 Is Being Used Enlarge Font
     {
      glScalef(1.5f,2.0f,1.0f);     // Enlarge Font Width And Height
     }

     glCallLists(strlen(text),GL_UNSIGNED_BYTE, text);   // Write The Text To The Screen
     glDisable(GL_TEXTURE_2D);      // Disable Texture Mapping
    }

       
    The resize code is NEW :) Instead of using a perspective view I'm using an ortho view for this tutorial. That means that objects don't get smaller as they move away from the viewer. The z-axis is pretty much useless in this tutorial.

    We start off by setting up the view port. We do this the same way we'd do it if we were setting up a perspective view. We make the viewport equal to the width of our window.

    Then we select the projection matrix (thing movie projector, it information on how to display our image). and reset it.

    Immediately after we reset the projection matrix, we set up our ortho view. I'll explain the command in detail:

    The first parameter (0.0f) is the value that we want for the far left side of the screen. You wanted to know how to use actual pixel values, so instead of using a negative number for far left, I've set the value to 0. The second parameter is the value for the far right side of the screen. If our window is 640x480, the value stored in width will be 640. So the far right side of the screen effectively becomes 640. Therefore our screen runs from 0 to 640 on the x-axis.

    The third parameter (height) would normally be our negative y-axis value (bottom of the screen). But because we want exact pixels, we wont have a negative value. Instead we will make the bottom of the screen equal the height of our window. If our window is 640x480, height will be equal to 480. So the bottom of our screen will be 480. The fourth parameter would normally be the positive value for the top of our screen. We want the top of the screen to be 0 (good old fashioned screen coordinates) so we just set the fourth parameter to 0. This gives us from 0 to 480 on the y-axis.

    The last two parameters are for the z-axis. We don't really care about the z-axis so we'll set the range from -1.0f to 1.0f. Just enough that we can see anything drawn at 0.0f on the z-axis.

    After we've set up the ortho view, we select the modelview matrix (object information... location, etc) and reset it.   
       

    GLvoid ReSizeGLScene(GLsizei width, GLsizei height)    // Resize And Initialize The GL Window
    {
     if (height==0)        // Prevent A Divide By Zero By
     {
      height=1;       // Making Height Equal One
     }

     glViewport(0,0,width,height);      // Reset The Current Viewport

     glMatrixMode(GL_PROJECTION);      // Select The Projection Matrix
     glLoadIdentity();       // Reset The Projection Matrix

     glOrtho(0.0f,width,height,0.0f,-1.0f,1.0f);    // Create Ortho 640x480 View (0,0 At Top Left)

     glMatrixMode(GL_MODELVIEW);      // Select The Modelview Matrix
     glLoadIdentity();       // Reset The Modelview Matrix
    }

       
    The init code has a few new commands. We start off by loading our textures. If they didn't load properly, the program will quit with an error message. After we have built the textures, we build our font set. I don't bother error checking but you can if you want.

    After the font has been built, we set things up. We enable smooth shading, set our clear color to black and set depth clearing to 1.0f. After that is a new line of code.

    glHint() tells OpenGL how to draw something. In this case we are telling OpenGL that we want line smoothing to be the best (nicest) that OpenGL can do. This is the command that enables anti-aliasing.

    The last thing we do is enable blending and select the blend mode that makes anti-aliased lines possible. Blending is required if you want the lines to blend nicely with the background image. Disable blending if you want to see how crappy things look without it.

    It's important to point out that antialiasing may not appear to be working. The objects in this game are quite small so you may not notice the antialaising right off the start. Look hard. Notice how the jaggie lines on the enemies smooth out when antialiasing is on. The player and hourglass should look better as well.   
       

    int InitGL(GLvoid)        // All Setup For OpenGL Goes Here
    {
     if (!LoadGLTextures())       // Jump To Texture Loading Routine
     {
      return FALSE;       // If Texture Didn't Load Return FALSE
     }

     BuildFont();        // Build The Font

     glShadeModel(GL_SMOOTH);      // Enable Smooth Shading
     glClearColor(0.0f, 0.0f, 0.0f, 0.5f);     // Black Background
     glClearDepth(1.0f);       // Depth Buffer Setup
     glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);     // Set Line Antialiasing
     glEnable(GL_BLEND);       // Enable Blending
     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);   // Type Of Blending To Use
     return TRUE;        // Initialization Went OK
    }

       
    Now for the drawing code. This is where the magic happens :)

    We clear the screen (to black) along with the depth buffer. Then we select the font texture (texture[0]). We want the words "GRID CRAZY" to be a purple color so we set red and blue to full intensity, and we turn the green up half way. After we've selected the color, we call glPrint(). We position the words "GRID CRAZY" at 207 on the x axis (center on the screen) and 24 on the y-axis (up and down). We use our large font by selecting font set 0.

    After we've drawn "GRID CRAZY" to the screen, we change the color to yellow (full red, full green). We write "Level:" and the variable level2 to the screen. Remember that level2 can be greater than 3. level2 holds the level value that the player sees on the screen. %2i means that we don't want any more than 2 digits on the screen to represent the level. The i means the number is an integer number.

    After we have written the level information to the screen, we write the stage information right under it using the same color.   
       

    int DrawGLScene(GLvoid)        // Here's Where We Do All The Drawing
    {
     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);   // Clear Screen And Depth Buffer
     glBindTexture(GL_TEXTURE_2D, texture[0]);    // Select Our Font Texture
     glColor3f(1.0f,0.5f,1.0f);      // Set Color To Purple
     glPrint(207,24,0,"GRID CRAZY");      // Write GRID CRAZY On The Screen
     glColor3f(1.0f,1.0f,0.0f);      // Set Color To Yellow
     glPrint(20,20,1,"Level:%2i",level2);     // Write Actual Level Stats
     glPrint(20,40,1,"Stage:%2i",stage);     // Write Stage Stats

       
    Now we check to see if the game is over. If the game is over, the variable gameover will be TRUE. If the game is over, we use glColor3ub(r,g,b) to select a random color. Notice we are using 3ub instead of 3f. By using 3ub we can use integer values from 0 to 255 to set our colors. Plus it's easier to get a random value from 0 to 255 than it is to get a random value from 0.0f to 1.0f.

    Once a random color has been selected, we write the words "GAME OVER" to the right of the game title. Right under "GAME OVER" we write "PRESS SPACE". This gives the player a visual message letting them know that they have died and to press the spacebar to restart the game.   
       

     if (gameover)        // Is The Game Over?
     {
      glColor3ub(rand()%255,rand()%255,rand()%255);   // Pick A Random Color
      glPrint(472,20,1,"GAME OVER");     // Write GAME OVER To The Screen
      glPrint(456,40,1,"PRESS SPACE");    // Write PRESS SPACE To The Screen
     }

       
    If the player still has lives left, we draw animated images of the players character to the right of the game title. To do this we create a loop that goes from 0 to the current number of lives the player has left minus one. I subtract one, because the current life is the image you control.

    Inside the loop, we reset the view. After the view has been reset, we translate to the 490 pixels to the right plus the value of loop1 times 40.0f. This draws each of the animated player lives 40 pixels apart from eachother. The first animated image will be drawn at 490+(0*40) (= 490), the second animated image will be drawn at 490+(1*40) (= 530), etc.

    After we have moved to the spot we want to draw the animated image, we rotate counterclockwise depending on the value stored in player.spin. This causes the animated life images to spin the opposite way that your active player is spinning.

    We then select green as our color, and start drawing the image. Drawing lines is alot like drawing a quad or a polygon. You start off with glBegin(GL_LINES), telling OpenGL we want to draw a line. Lines have 2 vertices. We use glVertex2d to set our first point. glVertex2d doesn't require a z value, which is nice considering we don't care about the z value. The first point is drawn 5 pixels to the left of the current x location and 5 pixels up from the current y location. Giving us a top left point. The second point of our first line is drawn 5 pixels to the right of our current x location, and 5 pixels down, giving us a bottom right point. This draws a line from the top left to the bottom right. Our second line is drawn from the top right to the bottom left. This draws a green X on the screen.

    After we have drawn the green X, we rotate counterclockwise (on the z axis) even more, but this time at half the speed. We then select a darker shade of green (0.75f) and draw another x, but we use 7 instead of 5 this time. This draws a bigger / darker x on top of the first green X. Because the darker X spins slower though, it will look as if the bright X has a spinning set of feelers (grin) on top of it.   
       

     for (loop1=0; loop1<lives-1; loop1++)     // Loop Through Lives Minus Current Life
     {
      glLoadIdentity();      // Reset The View
      glTranslatef(490+(loop1*40.0f),40.0f,0.0f);   // Move To The Right Of Our Title Text
      glRotatef(-player.spin,0.0f,0.0f,1.0f);    // Rotate Counter Clockwise
      glColor3f(0.0f,1.0f,0.0f);     // Set Player Color To Light Green
      glBegin(GL_LINES);      // Start Drawing Our Player Using Lines
       glVertex2d(-5,-5);     // Top Left Of Player
       glVertex2d( 5, 5);     // Bottom Right Of Player
       glVertex2d( 5,-5);     // Top Right Of Player
       glVertex2d(-5, 5);     // Bottom Left Of Player
      glEnd();       // Done Drawing The Player
      glRotatef(-player.spin*0.5f,0.0f,0.0f,1.0f);   // Rotate Counter Clockwise
      glColor3f(0.0f,0.75f,0.0f);     // Set Player Color To Dark Green
      glBegin(GL_LINES);      // Start Drawing Our Player Using Lines
       glVertex2d(-7, 0);     // Left Center Of Player
       glVertex2d( 7, 0);     // Right Center Of Player
       glVertex2d( 0,-7);     // Top Center Of Player
       glVertex2d( 0, 7);     // Bottom Center Of Player
      glEnd();       // Done Drawing The Player
     }

       
    Now we're going to draw the grid. We set the variable filled to TRUE. This tells our program that the grid has been completely filled in (you'll see why we do this in a second).

    Right after that we set the line width to 2.0f. This makes the lines thicker, making the grid look more defined.

    Then we disable anti-aliasing. The reason we disable anti-aliasing is because although it's a great feature, it eats CPU's for breakfast. Unless you have a killer graphics card, you'll notice a huge slow down if you leave anti-aliasing on. Go ahead and try if you want :)

    The view is reset, and we start two loops. loop1 will travel from left to right. loop2 will travel from top to bottom.

    We set the line color to blue, then we check to see if the horizontal line that we are about to draw has been traced over. If it has we set the color to white. The value of hline[loop1][loop2] will be TRUE if the line has been traced over, and FALSE if it hasn't.

    After we have set the color to blue or white, we draw the line. The first thing to do is make sure we haven't gone to far to the right. We don't want to draw any lines or check to see if the line has been filled in when loop1 is greater than 9.

    Once we are sure loop1 is in the valid range we check to see if the horizontal line hasn't been filled in. If it hasn't, filled is set to FALSE, letting our OpenGL program know that there is at least one line that hasn't been filled in.

    The line is then drawn. We draw our first horizontal (left to right) line starting at 20+(0*60) (= 20). This line is drawn all the way to 80+(0*60) (= 80). Notice the line is drawn to the right. That is why we don't want to draw 11 (0-10) lines. because the last line would start at the far right of the screen and end 80 pixels off the screen.   
       

     filled=TRUE;        // Set Filled To True Before Testing
     glLineWidth(2.0f);       // Set Line Width For Cells To 2.0f
     glDisable(GL_LINE_SMOOTH);      // Disable Antialiasing
     glLoadIdentity();       // Reset The Current Modelview Matrix
     for (loop1=0; loop1<11; loop1++)     // Loop From Left To Right
     {
      for (loop2=0; loop2<11; loop2++)    // Loop From Top To Bottom
      {
       glColor3f(0.0f,0.5f,1.0f);    // Set Line Color To Blue
       if (hline[loop1][loop2])    // Has The Horizontal Line Been Traced
       {
        glColor3f(1.0f,1.0f,1.0f);   // If So, Set Line Color To White
       }
       if (loop1<10)      // Dont Draw To Far Right
       {
        if (!hline[loop1][loop2])   // If A Horizontal Line Isn't Filled
        {
         filled=FALSE;    // filled Becomes False
        }
        glBegin(GL_LINES);    // Start Drawing Horizontal Cell Borders
         glVertex2d(20+(loop1*60),70+(loop2*40)); // Left Side Of Horizontal Line
         glVertex2d(80+(loop1*60),70+(loop2*40)); // Right Side Of Horizontal Line
        glEnd();     // Done Drawing Horizontal Cell Borders
       }

       
    The code below does the same thing, but it checks to make sure the line isn't being drawn too far down the screen instead of too far right. This code is responsible for drawing vertical lines.   
       

       glColor3f(0.0f,0.5f,1.0f);    // Set Line Color To Blue
       if (vline[loop1][loop2])    // Has The Horizontal Line Been Traced
       {
        glColor3f(1.0f,1.0f,1.0f);   // If So, Set Line Color To White
       }
       if (loop2<10)      // Dont Draw To Far Down
       {
        if (!vline[loop1][loop2])   // If A Verticle Line Isn't Filled
        {
         filled=FALSE;    // filled Becomes False
        }
        glBegin(GL_LINES);    // Start Drawing Verticle Cell Borders
         glVertex2d(20+(loop1*60),70+(loop2*40)); // Left Side Of Horizontal Line
         glVertex2d(20+(loop1*60),110+(loop2*40)); // Right Side Of Horizontal Line
        glEnd();     // Done Drawing Verticle Cell Borders
       }

       
    Now we check to see if 4 sides of a box are traced. Each box on the screen is 1/10th of a full screen picture. Because each box is piece of a larger texture, the first thing we need to do is enable texture mapping. We don't want the texture to be tinted red, green or blue so we set the color to bright white. After the color is set to white we select our grid texture (texture[1]).

    The next thing we do is check to see if we are checking a box that exists on the screen. Remember that our loop draws the 11 lines right and left and 11 lines up and down. But we dont have 11 boxes. We have 10 boxes. So we have to make sure we don't check the 11th position. We do this by making sure both loop1 and loop2 is less than 10. That's 10 boxes from 0 - 9.

    After we have made sure that we are in bounds we can start checking the borders. hline[loop1][loop2] is the top of a box. hline[loop1][loop2+1] is the bottom of a box. vline[loop1][loop2] is the left side of a box and vline[loop1+1][loop2] is the right side of a box. Hopefully I can clear things up with a diagram:


    All horizontal lines are assumed to run from loop1 to loop1+1. As you can see, the first horizontal line runs along loop2. The second horizontal line runs along loop2+1. Vertical lines are assumed to run from loop2 to loop2+1. The first vertical line runs along loop1 and the second vertical line runs along loop1+1

    When loop1 is increased, the right side of our old box becomes the left side of the new box. When loop2 is increased, the bottom of the old box becomes the top of the new box.

    If all 4 borders are TRUE (meaning we've passed over them all) we can texture map the box. We do this the same way we broke the font texture into seperate letters. We divide both loop1 and loop2 by 10 because we want to map the texture across 10 boxes from left to right and 10 boxes up and down. Texture coordinates run from 0.0f to 1.0f and 1/10th of 1.0f is 0.1f.

    So to get the top right side of our box we divide the loop values by 10 and add 0.1f to the x texture coordinate. To get the top left side of the box we divide our loop values by 10. To get the bottom left side of the box we divide our loop values by 10 and add 0.1f to the y texture coordinate. Finally to get the bottom right texture coordinate we divide the loop values by 10 and add 0.1f to both the x and y texture coordinates.

    Quick examples: loop1=0 and loop2=0


    Right X Texture Coordinate = loop1/10+0.1f = 0/10+0.1f = 0+0.1f = 0.1f
    Left X Texture Coordinate = loop1/10 = 0/10 = 0.0f
    Top Y Texture Coordinate = loop2/10 = 0/10 = 0.0f;
    Bottom Y Texture Coordinate = loop2/10+0.1f = 0/10+0.1f = 0+0.1f = 0.1f;

    loop1=1 and loop2=1


    Right X Texture Coordinate = loop1/10+0.1f = 1/10+0.1f = 0.1f+0.1f = 0.2f
    Left X Texture Coordinate = loop1/10 = 1/10 = 0.1f
    Top Y Texture Coordinate = loop2/10 = 1/10 = 0.1f;
    Bottom Y Texture Coordinate = loop2/10+0.1f = 1/10+0.1f = 0.1f+0.1f = 0.2f;

    Hopefully that all makes sense. If loop1 and loop2 were equal to 9 we would end up with the values 0.9f and 1.0f. So as you can see our texture coordinates mapped across the 10 boxes run from 0.0f at the lowest and 1.0f at the highest. Mapping the entire texture to the screen. After we've mapped a section of the texture to the screen, we disable texture mapping. Once we've drawn all the lines and filled in all the boxes, we set the line width to 1.0f.
      
       

       glEnable(GL_TEXTURE_2D);    // Enable Texture Mapping
       glColor3f(1.0f,1.0f,1.0f);    // Bright White Color
       glBindTexture(GL_TEXTURE_2D, texture[1]);  // Select The Tile Image
       if ((loop1<10) && (loop2<10))    // If In Bounds, Fill In Traced Boxes
       {
        // Are All Sides Of The Box Traced?
        if (hline[loop1][loop2] && hline[loop1][loop2+1] && vline[loop1][loop2] && vline[loop1+1][loop2])
        {
         glBegin(GL_QUADS);   // Draw A Textured Quad
          glTexCoord2f(float(loop1/10.0f)+0.1f,1.0f-(float(loop2/10.0f)));
          glVertex2d(20+(loop1*60)+59,(70+loop2*40+1)); // Top Right
          glTexCoord2f(float(loop1/10.0f),1.0f-(float(loop2/10.0f)));
          glVertex2d(20+(loop1*60)+1,(70+loop2*40+1)); // Top Left
          glTexCoord2f(float(loop1/10.0f),1.0f-(float(loop2/10.0f)+0.1f));
          glVertex2d(20+(loop1*60)+1,(70+loop2*40)+39); // Bottom Left
          glTexCoord2f(float(loop1/10.0f)+0.1f,1.0f-(float(loop2/10.0f)+0.1f));
          glVertex2d(20+(loop1*60)+59,(70+loop2*40)+39); // Bottom Right
         glEnd();    // Done Texturing The Box
        }
       }
       glDisable(GL_TEXTURE_2D);    // Disable Texture Mapping
      }
     }
     glLineWidth(1.0f);       // Set The Line Width To 1.0f

       
    The code below checks to see if anti is TRUE. If it is, we enable line smoothing (anti-aliasing).   
       

     if (anti)        // Is Anti TRUE?
     {
      glEnable(GL_LINE_SMOOTH);     // If So, Enable Antialiasing
     }

       
    To make the game a little easier I've added a special item. The item is an hourglass. When you touch the hourglass, the enemies are frozen for a specific amount of time. The following section of code is resposible for drawing the hourglass.

    For the hourglass we use x and y to position the timer, but unlike our player and enemies we don't use fx and fy for fine positioning. Instead we'll use fx to keep track of whether or not the timer is being displayed. fx will equal 0 if the timer is not visible. 1 if it is visible, and 2 if the player has touched the timer. fy will be used as a counter to keep track of how long the timer should be visible or invisible.

    ----------------------------------------------
    越学越无知

    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2007/10/19 12:53:00
     
     GoogleAdSense射手座1984-11-30
      
      
      等级:大一新生
      文章:1
      积分:50
      门派:无门无派
      院校:未填写
      注册:2007-01-01
    给Google AdSense发送一个短消息 把Google AdSense加入好友 查看Google AdSense的个人资料 搜索Google AdSense在『 C/C++编程思想 』的所有贴子 访问Google AdSense的主页 引用回复这个贴子 回复这个贴子 查看Google AdSense的博客广告
    2024/5/10 10:07:06

    本主题贴数6,分页: [1]

     *树形目录 (最近20个回帖) 顶端 
    主题:  [推荐]NeHe OpenGL教程(中英文版附带VC++源码)Lesson 2..(29777字) - 一分之千,2007年10月19日
        回复:  (2字) - ririyeye,2010年1月8日
        回复:  Lesson 22 This lesson was written by Jens S..(56335字) - 一分之千,2007年10月19日
        回复:  第二十二课[IMG]http://www.owlei.com/DancingWind/Pic..(33969字) - 一分之千,2007年10月19日
        回复:  lesson21 (continue)So we start off by checking ..(39678字) - 一分之千,2007年10月19日
        回复:  Lesson 21 Welcome to my 21st OpenGL Tutoria..(46137字) - 一分之千,2007年10月19日

    W3C Contributing Supporter! W 3 C h i n a ( since 2003 ) 旗 下 站 点
    苏ICP备05006046号《全国人大常委会关于维护互联网安全的决定》《计算机信息网络国际联网安全保护管理办法》
    484.375ms