Chapter 5. The Game Loop and Graphics

Computer games come in many genres, from abstract puzzle games like Tetris to turn-based strategy games like Civilization to fast-paced first-person shooters like Half-Life. All these games, and all computer games, are programmed in a similar way.

How Do Games Work?

The most important way a game communicates with the player is via the TV screen or computer monitor. It’s quite common to hear about frame-rate in games. A good frame-rate is from 30 frames per second to 60 frames per second. But what does frame-rate actually mean when programming a game?

A single frame is the time between screen updates. The computer program is responsible for updating the screen with new information at least 30 times a second. Computers are very fast; it’s no problem for a computer to update the screen this quickly.

How much time does the computer have to update each frame? If the minimum requirement is 30 frames per second, that is 33 milliseconds per frame. The computer has 33 milliseconds to “think” about what should happen in the next frame. A computer can do a vast amount of calculation in 33 milliseconds; more than most humans could do in a week.

All game code has a central loop called the game loop. While the game is running, the central loop is called repeatedly and as often as possible. The game loop has three main stages: it first gets the state of any input (such as gamepad or the keyboard), updates the state of the game world, and finally updates all the pixels on the screen.

A Closer Look at the Game Loop

Game code is not just responsible for updating the graphics on the screen; it has two other very important tasks to do. It must take input from the user such as “If the user is pressing button B, then make the character jump,” and it must update the game world, “If the player has just killed the end boss, then go to the credits.”

All game loops follow a similar pattern.

while (true)
{
  // Find out what state keyboard and joypads are in
  UpdateInput();
  // Handle input, update the game world, move characters etc
  Process();
  // Draw the current state of the game to the screen
  Render();
}

These are the three main stages of the loop: update the player input, update the game world, and then tell the graphics card what to render.

Implementing a Fast Game Loop in C#

Open Visual Studio. Visual Studio assumes by default you will be making a software application that’s event-driven rather than one that’s executing code continuously, such as a game. Event-driven programs execute code in response to events coming from the operating system or from user input. It’s easier to write games with a main loop that can be used to continually update the state of the game world. The default code needs to change a little to get a fast game loop implemented. It’s important that the loop runs as often as possible, so this C# code uses some C functions to ensure it’s the fastest possible game loop.

Create a new Windows Form Application and call the project GameLoop. A Program.cs file will automatically be generated with the following code:

namespace GameLoop
{
  static class Program

  {
    ///<summary>
    /// The main entry point for the application.
    ///</summary>
    [STAThread]
    static void Main()
    {
     Application.EnableVisualStyles();
     Application.SetCompatibleTextRenderingDefault(false);
     Application.Run(new Form1());
    }
  }
}

If this code is run, it will just show a standard Windows form. At the moment, this is an event-driven application. If it was executing continuously then it might look like this.

namespace GameLoop
{
  static class Program
  {
    ///<summary>
    /// The main entry point for the application.
    ///</summary>
    [STAThread]
    static void Main()
    {
     Application.EnableVisualStyles();
     Application.SetCompatibleTextRenderingDefault(false);
     Application.Run(new Form1());
    }
    static void GameLoop()
    {
     // GameCode goes here
     // GetInput
     // Process
     // Render
    }
  }
}

The GameLoop function should be called each frame. To do this, a new class needs to be created. Right-click the GameLoop project in the solution explorer tab and choose Add > Class. This will prompt you for a class name; name the class FastLoop. The FastLoop class is going to be used by the Program class. It will force the function GameLoop to be called each frame. Before GameLoop is written, let’s consider how it might be used.

static class Program
{
  static FastLoop _fastLoop = new FastLoop(GameLoop);
  ///<summary>
  /// The main entry point for the application.
  ///</summary>
  [STAThread]
  static void Main()
  {
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);
    Application.Run(new Form1());
  }
  static void GameLoop()
  {
    // GameCode goes here
    // GetInput
    // Process
    // Render
  }
}

The FastLoop class will take only one argument. That argument will be the function GameLoop. That’s all that will be needed to convert a project so that it continually executes.

Open FastLoop.cs. In the Program class, the FastLoop constructor call takes in a reference to the function GameLoop. Therefore the FastLoop class must define its constructor to have a function passed into it as a parameter.

public class FastLoop
{
  public delegate void LoopCallback();
  public FastLoop(LoopCallback callback)
  {
  }
}

In C#, functions can be stored as variables by using a delegate that defines the function signature—what the function returns and what parameters it takes in. In this case, a delegate is used to define a function type with no return value or parameters; it’s named LoopCallback. FastLoop has one constructor that takes in the callback. The callback will be called every frame to allow the game to update itself.

All C# form programs have a static class called Application. This class represents the program and its settings. It can also be used to modify a program so that it can be used in real-time.

A program can have a large number of events to handle; the user might be maximizing the form or Windows may be shutting down. The Application is the part of the code that handles all these events. When it has no events to handle, it calls a callback Application.Idle. This means it’s about to enter idle time. This idle time, when the application is not busy, is when the game logic needs to be updated. To use the Application code, using System.Windows. Forms needs to be added near the top of the file.

using System.Windows.Forms;
namespace GameLoop
{
  public class FastLoop
  {
    public delegate void LoopCallback();
    LoopCallback _callback;

    public FastLoop(LoopCallback callback)
    {
      _callback = callback;
      Application.Idle += new EventHandler(OnApplicationEnterIdle);
    }
    void OnApplicationEnterIdle(object sender, EventArgs e)
    {
    }
  }
}

Here the game loop callback has been stored in a member variable, _callback, for later use. The Application.Idle event has been given a handler, OnApplicationEnterIdle. This will be called when the application begins to idle. The next part is a bit trickier. The code needs to know if the application is still in the idle state, and if it is then it needs to continually call the loop callback. Read this code to see how the callback will be called.

void OnApplicationEnterIdle(object sender, EventArgs e)
{
  while (IsAppStillIdle())
  {
    _callback();
  }
}
private bool IsAppStillIdle()
{
  Message msg;
  return !PeekMessage(out msg,IntPtr.Zero, 0, 0, 0);
}

The code calls the callback continuously unless IsAppStillIdle returns false. IsAppStillIdle uses a new PeekMessage function to check if the application has any important events that need to be dealt with. If there are important messages for the application then these need to be handled before returning to the game loop.

To check if the application is idle, we need to have a look at the Windows message queue. Windows has a big queue of events. If a window is moved, an event is added to the event queue; if it’s minimized, an event is added to the queue; if a user presses a key on the keyboard, an event is added—many different actions generate events. All forms need to handle their own events. If our application has some events in its queue, then it needs to stop idling and deal with them.

The easiest way to check the event queue is by using some Windows C functions. In C#, this is called InterOp, short for InterOperation. Using some C functions in a C# program is actually very easy to do. To see what’s in the event queue, we’re going to use a C function called PeekMessage. We just want to have a peek at the queue and see if anything’s there. This is the C definition.

BOOL PeekMessage(
  LPMSG lpMsg,
  HWND hWnd,
  UINT wMsgFilterMin,
  UINT wMsgFilterMax,
  UINT wRemoveMsg
);

According to the documentation

  • If a message is available, the return value is nonzero.

  • If no messages are available, the return value is zero.

This is the perfect function to decide if there are events waiting for our particular application.

It’s not very important to understand all the details of this C function. The important thing is how the function is called from C#. To call it from C#, the function arguments need changing from C types to the equivalent C# types.

The first argument, lpMsg is a message type. This type isn’t available in C#; it needs to be imported. The second type is a Windows handle; this is just a reference to our form. The last three arguments are all unsigned integers, which are a standard C# type. Here’s the C message structure, which is the first argument.

typedef struct {
  HWND hwnd;
  UINT message;
  WPARAM wParam;
  LPARAM lParam;
  DWORD time;
  POINT pt;
} MSG, *PMSG;

The structure members aren’t that important; here’s how to import this C type into C#. First, include the using statement using System.Runtime.InteropServices; at the top of the FastLoop.cs with the other using statements. This library has useful functions for importing C types and structures.

using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace GameLoop
{
  [StructLayout(LayoutKind.Sequential)]
public struct Message
{
  public IntPtr hWnd;
  public Int32 msg;
  public IntPtr wParam;
  public IntPtr lParam;
  public uint time;
  public System.Drawing.Point p;
}

Adding this C struct is just a case of correctly matching the C types to the C# types. The attribute [StructLayout(LayoutKind.Sequential)] tells C# to lay out the structure in memory exactly the way it’s written. Without this attribute, C# might try to be clever and make the structure more memory-efficient by rearranging it. C expects the structure to be laid out in memory the exact way it’s written.

Now the message type is imported; all that remains is to import the PeekMessage function.

public class FastLoop
{
  [System.Security.SuppressUnmanagedCodeSecurity]
  [DllImport("User32.dll", CharSet = CharSet.Auto)]
  public static extern bool PeekMessage(
    out Message msg,
    IntPtr hWnd,
    uint messageFilterMin,
    uint messageFilterMax,
    uint flags);

The first attribute, [System.Security.SuppressUnmanagedCode Security], just says, “We are calling C, an unmanaged language, so don’t do any managed security checks.” The second attribute, [DllImport(“User32.dll”, CharSet = CharSet.Auto)], references the DLL file that the C function is to be imported from. User32.dll is one of the major files for interacting with the Windows operating system using C. The PeekMessage function fills out the Message structure, so it needs to be able to write to it. That’s why the out keyword is used on the first argument. The remaining arguments can all be ignored and no useful information will be passed into them.

With PeekMessage defined, it can now be used to determine if there are any messages waiting in the application’s event queue.

private bool IsAppStillIdle()
{
  Message msg;
  return !PeekMessage(out msg,IntPtr.Zero, 0, 0, 0);
}

That’s it. With IsAppStillIdle defined correctly, the program now has an extremely fast game loop. This FastLoop class can be reused for any game project you make in the future.

Now to test that the game loop really works, go to Project.cs and add the following line of code.

static void GameLoop()
{
  // GameCode goes here
  // GetInput
  // Process
  // Render
  System.Console.WriteLine("loop");
}

Every time the game loop is called, it will output the word “loop” to the console. To see the console, go to Debug > Windows > Output; this will bring up another window. Run the application and in the output window, the word “loop” will keep scrolling down the screen.

Adding High-Precision Timing

To animate things smoothly, it’s important to know how much time has elapsed between frames. This time can then be used to keep animation independent of the frame-rate. Games should run the same speed on all computers; a game character shouldn’t suddenly be able to run faster if the computer is faster!

Timing in computer games is very important. The time between frames must be accurate and of a high resolution or the game will appear to be jerky. To get the best timing functions, some C code needs to be used. This is less daunting as we’ve already used InterOp to peek at Windows messages. The time between frames is often called elapsedTime or the delta time, sometimes dt for short, and it’s measured in fractions of a second.

Create a new class called PreciseTimer. It’s not a big class, so here’s the code all in one go. Have a look through it and try to work out what it’s doing.

using System.Runtime.InteropServices;
namespace GameLoop
{
  public class PreciseTimer
  {
    [System.Security.SuppressUnmanagedCodeSecurity]
    [DllImport("kernel32")]
    private static extern bool QueryPerformanceFrequency(ref long
PerformanceFrequency);
    [System.Security.SuppressUnmanagedCodeSecurity]
    [DllImport("kernel32")]
    private static extern bool QueryPerformanceCounter(ref long
PerformanceCount);
    long _ticksPerSecond = 0;
    long _previousElapsedTime = 0;
    public PreciseTimer()
    {
     QueryPerformanceFrequency(ref _ticksPerSecond);
  GetElapsedTime(); // Get rid of first rubbish result
    }
    public double GetElapsedTime()
    {
     long time = 0;
     QueryPerformanceCounter(ref time);
     double elapsedTime = (double)(time - _previousElapsedTime) /
(double)_ticksPerSecond;
     _previousElapsedTime = time;
     return elapsedTime;
    }
  }
}

The QueryPerformanceFrequency function retrieves the frequency of the high-resolution performance counter. Most modern hardware has a high-resolution timer; this function is used to get the frequency at which the timer increments. The QueryPerformanceCounter function retrieves the current value of the high-resolution performance counter. These can be used together to time how long the last frame took.

GetElapsedTime should be called once per frame and this will keep track of the time. The elapsed time is so important that it should be incorporated into the game loop. Open the Program.cs file and change the game loop so it takes one argument.

static void GameLoop(double elapsedTime)
{
  // GameCode goes here
  // GetInput
  // Process
  // Render
  System.Console.WriteLine("loop");
}

FastLoop.cs needs to be changed as well; the delegate must take an elapsed time value and the PreciseTimer needs to be added as a member.

public class FastLoop
{
  PreciseTimer _timer = new PreciseTimer();
  public delegate void LoopCallback(double elapsedTime);

The timer is then called once per frame and elapsed time is passed on to FastLoop’s callback.

void OnApplicationEnterIdle(object sender, EventArgs e)
{
  while (IsAppStillIdle())
  {
    _callback(_timer.GetElapsedTime());
  }
}

The game loop can now be used to smoothly animate any game! By the end of this chapter, the game loop will be used to smoothly rotate a 3D triangle—the “Hello World” application of OpenGL programming.

Graphics

Now that the game loop is working, the next step is to get something to display on screen. You can either continue the current project with FastLoop.cs and GameLoop.cs or you can make a new project and modify it so it has a fast game loop.

To display graphics, the Tao libraries must be included in the project. If you’ve not installed the Tao framework yet, now is a good time to do it.

In Visual Studio, find the solution explorer window. The solution explorer will contain only one project. Expand the project and you will see an icon labeled References. Right-click this icon and choose Add Reference.

This will bring up the reference dialog box. Click the Browse tab and navigate to the Tao framework install directory. On Windows 7 and Vista this path will be C:Program Files (x86)TaoFrameworkin. For XP it will be C:Program FilesTaoFrameworkin. Once you navigate to the correct directory, you will see something similar to Figure 5.1. Choose Tao.OpenGL.dll and Tao.Platform. Windows.dll, then click OK. The Tao framework comes with a control, called SimpleOpenGLControl, that allows OpenGL to render in a Windows form. To enable the control, double-click the form in the solution explorer. This will bring up the form designer, as shown in Figure 5.2.

A list of references.

Figure 5.1. A list of references.

The form designer.

Figure 5.2. The form designer.

Right-click the toolbar pane and select Choose Items; this will bring up the dialog box shown in Figure 5.3.

Choose Toolbox Items dialog.

Figure 5.3. Choose Toolbox Items dialog.

One of the options in the dialog should be SimpleOpenGLControl. This was installed by the Tao installer. If the SimpleOpenGLControl isn’t there, then click the Browse button, navigate to the Tao framework binary directory (the same directory used for adding the references), and select the Tao.Platform.Windows.dll file. Check the box shown in Figure 5.3 and click OK. A new control, SimpleOpenGLControl, has now been added to the Toolbox. Drag this new control from the Toolbox on to the form in the form designer. Your form will look like Figure 5.4.

Adding SimpleOpenGLControl.

Figure 5.4. Adding SimpleOpenGLControl.

The little black window on the form is where all OpenGL rendering will take place. It’s a little small at the moment. To make it the same size as the form, right-click the control and click Properties. This will bring up the Properties window. Find the property called Dock and set it to Fill. This will make the OpenGLControl fill all the space on the control.

If you press the green play button now an error will appear that says “No device or rendering context available.” This is because the OpenGL control hasn’t been initialized yet.

Open the form in code view and add the following line.

public Form1()
{
  InitializeComponent();
  simpleOpenGlControl1.InitializeContexts();
}

Run the program and a form will be created with a black screen. That’s OpenGL working with C#—congratulations! The variable _openGLControl reads better than simpleOpenGlControl1; the refactor tools can be used to rename it.

Full Screen Mode

Most games allow the player to play the game full screen. This is an easy option to add. Here’s the code.

bool _fullscreen = true;
public Form1()
{
  InitializeComponent();
  _openGLControl.InitializeContexts();
  if (_fullscreen)
  {
    FormBorderStyle = FormBorderStyle.None;
    WindowState = FormWindowState.Maximized;
  }
}

The border style is the bits on the outside of the window, the menu bar, and the border. With these parts of the form removed, the size of the form will directly reflect the size of the OpenGL control. Forms have three possible Window-States: Normal, Minimized, and Maximized. Maximized gives the full-screen mode needed. Full-screen mode is good for playing games, but while developing a game, windowed mode is better. If the program is in a window, the debugger can be used while the game is running. For that reason, it’s probably best to set _fullscreen to false.

Rendering

To make games, we need to learn how to get OpenGL to start drawing to the screen. When learning a new program like OpenGL, it’s important to have fun, be curious, and play around with the API. Don’t be afraid to experiment or break things. All OpenGL functions are documented on the OpenGL website at http://www.opengl.org/sdk/docs/man/. This is a good site to start exploring the library.

Clearing the Background

Rendering graphics using OpenGL first requires a game loop. Previously, a game was created in the Program.cs class; this time the game loop will be created in Form.cs so that it has access to the openGLControl.

using Tao.OpenGl;
namespace StartingGraphics
{
  public partial class Form1 : Form
  {
    FastLoop _fastLoop;
    bool   _fullscreen = false;
    public Form1()
    {
     _fastLoop = new FastLoop(GameLoop);
     InitializeComponent();
     _openGLControl.InitializeContexts();
     if (_fullscreen)
     {
       FormBorderStyle = FormBorderStyle.None;
       WindowState = FormWindowState.Maximized;
     }
    }
    void GameLoop(double elapsedTime)
    {
     _openGLControl.Refresh();
    }
  }
}

This game loop code is very similar to the code written before. Remember to add the using Tao.OpenGl; statement or it won’t have access to the OpenGL libraries.

GameLoop is called every frame. At the moment, it just refreshes the OpenGL control. The refresh call tells the OpenGL control to update its graphics. Running the program will give a boring black screen, but with the game loop set up, that screen can now be changed.

void GameLoop(double elapsedTime)
{
  Gl.glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
  Gl.glClear(Gl.GL_COLOR_BUFFER_BIT);
  Gl.glFinish();
  _openGLControl.Refresh();
}

Three new lines have been added to the game loop. The first line tells OpenGL what color to use to clear the screen. OpenGL represents colors as four values: red, green, blue, and alpha. Alpha determines the transparency of the color: 1 is fully opaque, and 0 is fully transparent. Each value ranges from 0 to 1. In this case, red has been set to 1, green and blue to zero, and alpha to 1 as well. This will result in a bright red color. For the clear color, the alpha value is ignored. The clear color only needs to be set once, but to keep all the new code together, it’s currently being set every frame.

The second line issues the clear command. This function uses the clear color we set in the previous line to clear the screen. The final line, glFinish, tells OpenGL that we’ve finished for this frame and to make sure all commands are carried out.

Running the program will now give the window a new bright red background. It’s important to play with the library and get comfortable with it. Try switching up the commands or changing the color. For example, review this code and try to guess what will happen before running it.

Random r = new Random();
Gl.glClearColor((float)r.NextDouble(), (float)r.NextDouble(), (float)
r.NextDouble(), 1.0f);

Vertices

Vertices are the building blocks that make up virtual worlds. At their most basic they are position information; x and y for a two-dimensional world; x, y, and z for a three-dimensional world.

It’s easy to introduce vertices in OpenGL using immediate mode. Immediate mode is a way of telling the graphics card what to draw. These commands need to be sent every frame, even if nothing has changed. It’s not the fastest way to do OpenGL programming, but it is the easiest way to learn.

Let’s begin by drawing a point.

void GameLoop(double elapsedTime)
{
  Gl.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
  Gl.glClear(Gl.GL_COLOR_BUFFER_BIT);
  Gl.glBegin(Gl.GL_POINTS);
  {
    Gl.glVertex3d(0, 0, 0);
  }
  Gl.glEnd();
  Gl.glFinish();
  _openGLControl.Refresh();
}

There are three new OpenGL commands here. glBegin tells the graphics card that you will be drawing something. glBegin takes one argument; this describes what you will be drawing. The value passed in is GL_POINTS; this tells OpenGL to render any vertices as points (as opposed to triangles or quads).

glBegin must be followed by glEnd. Immediately after glBegin, there is an opening brace. This isn’t strictly necessary; it just provides indentation so that it’s clear where the glEnd should go. In between the parentheses is a glVertex command; the command ends in 3d. The 3 means it will use three dimensions—an x, y, and z—to make up the position. The d means the positions are expected to be doubles.

You may draw as many vertices as you want between the begin and end calls. The current vertex is being drawn at 0,0,0, which will be right in the center of the screen. The scene is currently set up according to OpenGL’s default settings. Figure 5.5 is a drawing of a cube that describes the default setup of the OpenGL scene. A point can be rendered anywhere in this cube, but if any of the coordinate numbers are lower than minus one or greater than one, then the point will be outside the scene and therefore won’t be visible. The camera viewing this scene can be considered to be at 0, 0, 1 facing –1 on the Z axis.

Default OpenGL scene.

Figure 5.5. Default OpenGL scene.

The position 0,0,0 is often referred to as the origin of the scene. After the vertex is drawn, the closing brace is written, followed by the end call. Run the program and you should see a white pixel in the middle of the screen. This is the point being drawn. It’s quite small, but the point drawing size is easy to adjust in OpenGL. Add Gl.glPointSize(5.0f); just before the glBegin statement. The point should be much easier to see now.

Triangles

OpenGL supports a number of primitive types that can be made from vertices. The majority of games use triangle strips to represent everything. The 3D characters in your favorite FPS or fighting game are just a list of vertices that can be joined up to make a list of triangles.

Gl.glBegin(Gl.GL_POINTS);
{
  Gl.glVertex3d(-0.5, 0, 0);
  Gl.glVertex3d(0.5, 0, 0);
  Gl.glVertex3d(0, 0.5, 0);
}
Gl.glEnd();

Run the above code snippet. It will draw three vertices as points in a triangle shape. To actually draw a full triangle, the argument passed into glBegin needs to change from GL_POINTS to GL_TRIANGLES. Run the program after making this change and you’ll see a white triangle like in Figure 5.6.

A rendered triangle.

Figure 5.6. A rendered triangle.

GL_TRIANGLES reads three vertices and then draws a triangle using those vertices. When loading meshes, it’s more common to use GL_TRIANGLE_STRIP. GL_TRIANGLE requires three vertices to draw a triangle. GL_TRIANGLE_STRIP is similar; the first triangle requires three vertices, but then each additional triangle only requires one more vertex. The difference is shown in Figure 5.7.

GL_TRIANGLES and GL_TRIANGLE_STRIP.

Figure 5.7. GL_TRIANGLES and GL_TRIANGLE_STRIP.

Coloring and Spinning the Triangle

The Hello World program of the 3D programming world is a spinning triangle with each vertex colored for red, green, and blue. This demonstrates all the basic graphics functionality is working, and an experienced graphics programmer can easily build from this.

Vertices can store lots of different information. At the moment, the three vertices here only have position data. Color data can be added in immediate mode by setting the color information before each vertex call.

Gl.glBegin(Gl.GL_TRIANGLE_STRIP);
{
  Gl.glColor3d(1.0, 0.0, 0.0);
  Gl.glVertex3d(-0.5, 0, 0);
  Gl.glColor3d(0.0, 1.0, 0.0);
  Gl.glVertex3d(0.5, 0, 0);

  Gl.glColor3d(0.0, 0.0, 1.0);
  Gl.glVertex3d(0, 0.5, 0);
}
Gl.glEnd();

Running this code will produce a triangle with a red, green, and blue corner. Along the surface of the triangle, the colors mix and fade into each other. By default, OpenGL will interpolate colors between vertices. In practice, this means if you give each vertex of a triangle a different color, then each pixel of the rendered triangle will be colored according to the distance from each of the vertices. Basic lighting systems get the lighting information for each vertex and then use this interpolation to decide how the surface should be shaded.

All that remains now is to spin the triangle. There are two ways to spin the triangle: move all the vertices or move the entire scene. It’s simpler to move the entire scene as OpenGL has a helpful function for rotation.

Gl.glRotated(10 * elapsedTime, 0, 1, 0);
Gl.glBegin(Gl.GL_TRIANGLE_STRIP);
{
  Gl.glColor4d(1.0, 0.0, 0.0, 0.5);
  Gl.glVertex3d(-0.5, 0, 0);
  Gl.glColor3d(0.0, 1.0, 0.0);
  Gl.glVertex3d(0.5, 0, 0);
  Gl.glColor3d(0.0, 0.0, 1.0);
  Gl.glVertex3d(0, 0.5, 0);
}
Gl.glEnd();

There are a few subtle points to touch on here. glRotate takes four arguments. The first argument is the angle to rotate in degrees and the last three are the axis to rotate around. The rotation is cumulative, which means if glRotated was called twice, those rotations will both be applied—rotating the object twice as much. In our case, the update call is being called as often as possible so the degree of rotation continually increases, which appears to make the triangle rotate. The rotate function automatically wraps at 360 so a rotation of 361 is equivalent to a rotation to 1.

The next three arguments describe the axis. These three arguments are actually a normalized vector. Vectors will be described later in the book. For now, just think of a vector as a line that is one unit of length long. The line described here is 1 on the Y axis, so it’s a straight line along the Y axis. Imagine this line pushed through the center of the triangle, passing from the point at the top through the middle of its wide base. Rotating the line then rotates the entire triangle. Try to visualize how the triangle would rotate with the arguments (1, 0, 0) and (0, 0, 1), then amend the program to check if you were correct.

The first argument, the angle in degrees, is being multiplied by the elapsed time. This is to ensure time-independent movement and should make the animation smoother. If the program is playing at 60 frames per second, that means each elapsed frame time will be around 0.1 seconds. This means to move the triangle 10 degrees will take 1 second. If someone else runs your program on a slower computer, and it can only go, say, 30 frames per second, then each frame will take about 0.2 seconds to complete. There are fewer frames per second but the triangle will take the same amount of time to rotate on both machines. If the degrees were not multiplied by the elapsed time, then the program would run twice as fast on the machine that runs at 60 frames per second.

Summary

Most games have a central loop that checks the player input, updates the game world, and then updates the screen. By default, C# does not have a central loop so some C functions need to be used to create a fast game loop useful for creating games. Additional C functions are used to get access to timing information so each frame can be timed. This time can be used to ensure the game runs smoothly, even on computers that run at different speeds.

The Tao framework library has a control called SimpleOpenGLControl. This control allows OpenGL to be used easily within a Windows Form. A spinning triangle is often used as the “Hello World” program for OpenGL. The triangle is made up of three vertices and each can be assigned a different color. If the vertices of a triangle are different colors, the triangle’s pixels will be colored according to the distance from each vertex. OpenGL has a function called glRotated, which takes care of the final task of rotating the triangle.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.116.63.231