Using Managed DirectX to Write a Game

Writing the Game (contd.)

Nothing new here, unless you consider the background color being black new or exciting. Now you can get ready to put in the first game play object, the road. The source code on the CD includes a .X file that will hold the road mesh data, so naturally we will need to declare the variables for the road mesh:

// Game board mesh information
private Mesh roadMesh = null;
private Material[] roadMaterials = null;
private Texture[] roadTextures = null;

You will also use a variation of the load mesh function you wrote in the previous chapter. The major differences here are that it will be static, since it will need to be called from more than one class, and you will pass in all the material and texture information rather than relying on the class level variables that were used before. Add the method from Listing 6.4 to your code.

Listing 6.4 Generic Mesh Loading Routine

public static Mesh LoadMesh(Device device, string file, ref Material[] meshMaterials,
  ref Texture[] meshTextures)
{
  ExtendedMaterial[] mtrl;
  // Load our mesh
  Mesh tempMesh = Mesh.FromFile(file, MeshFlags.Managed, device, out mtrl);
  // If we have any materials, store them
  if ((mtrl != null) && (mtrl.Length > 0))
  {
    meshMaterials = new Material[mtrl.Length];
    meshTextures = new Texture[mtrl.Length];
    // Store each material and texture
    for (int i = 0; i < mtrl.Length; i++)
    {
      meshMaterials[i] = mtrl[i].Material3D;
      if ((mtrl[i].TextureFilename != null) && (mtrl[i].TextureFilename !=
        string.Empty))
      {
        // We have a texture, try to load it
        meshTextures[i] = TextureLoader.FromFile(device, @"..\..\" +
          mtrl[i].TextureFilename);
      }
    }
  }
  return tempMesh;
}

This function has already been discussed in depth before, so there's no need to go into it once more. You will use this function to load the road's mesh, and you should do this in the device reset event function. At the end of that function, add the following code:

// Create our road mesh
roadMesh = LoadMesh(device, @"..\..\road.x", ref roadMaterials, ref roadTextures);

Make sure you have copied the road mesh and texture file to your source code location. This code will load our road mesh, including the textures, and store the textures, materials, and mesh. Now, you will want to render this road mesh more than once per frame; you should create a function to do the rendering. Add the following function to your code:

private void DrawRoad(float x, float y, float z)
{
  device.Transform.World = Matrix.Translation(x, y, z);
  for (int i = 0; i < roadMaterials.Length; i++)
  {
    device.Material = roadMaterials[i];
    device.SetTexture(0, roadTextures[i]);
    roadMesh.DrawSubset(i);
  }
}

You should recognize this function, since it's remarkably similar to the one used to render the mesh before. You translate the mesh into the correct position and render each subset. The plan for rendering the road is to render two sections at a time: the section the car is currently driving on and the section immediately after that. In all actuality, the car will not be moving at all; we will instead move the road.

The reasoning behind doing it this way is two-fold. First, if the car was moved every frame, you would also need to move the camera every frame to keep up with it. That's just extra calculations you don't need. Another reason is precision: If you let the car move forward, and the player was good, eventually the car would be so far into the "world" that you could lose floating point precision, or worse overflow the variable. Since the "world" will not have any boundaries (there won't be any way to "win" the game), you keep the car in the same relative position, and move the road below it.

Naturally, you will need to add some variables for controlling the road. Add the following class level variables and constants:

// Constant values for the locations
public const float RoadLocationLeft = 2.5f;
public const float RoadLocationRight = -2.5f;
private const float RoadSize = 100.0f;
private const float MaximumRoadSpeed = 250.0f;
private const float RoadSpeedIncrement = 0.5f;
// Depth locations of the two 'road' meshes we will draw
private float RoadDepth0 = 0.0f;
private float RoadDepth1 = -100.0f;
private float RoadSpeed = 30.0f;

The mesh being used for the road is a known commodity. It is exactly 100 units long and 10 units wide. The size constant reflects the actual length of the road, while the two location constants mark the center of the lanes on either side of the road. The last two constants are designed to control the actual gameplay (once it is implemented). The maximum road speed you want to allow the game to get to is 250 units per second, and every time you increment the speed, you will want to increment it one half of a unit.

Finally, you need to maintain the depth location of the two road sections. You should initialize the first section at zero, with the second section initialized directly at the end of the first (notice that this is the same as the road size variable). With the basic variables and constants needed to draw (and move) the road, let's add the calls to do the actual drawing. You will want to draw the road first, so immediately after your BeginScene call in your rendering function, add the two DrawRoad calls:

// Draw the two cycling roads
DrawRoad(0.0f, 0.0f, RoadDepth0);
DrawRoad(0.0f, 0.0f, RoadDepth1);

Running the application now, you can see that the road is definitely being drawn; however, the "asphalt" of the road looks extremely pixilated. The cause of this pixilation is the way Direct3D determines the color of a pixel in the rendered scene. When one texel happens to cover more than one pixel on the screen, the pixels are run through a magnify filter to compensate. When there can be multiple texels covering a single pixel, they are run through a minifying filter. The default filter for both minification and magnification is a Point filter, which simply uses the nearest texel as the color for that pixel. This is the cause of our pixilation.

Now, there are multiple different ways to filter our textures; however, the device may or may not support them. What you really want is a filter that can interpolate between the texels to give a more smooth look to the road texture. In the OnDeviceReset function, add the code found in Listing 6.5.

Listing 6.5 Implementing Texture Filtering

// Try to set up a texture minify filter, pick anisotropic first
if (device.DeviceCaps.TextureFilterCaps.SupportsMinifyAnisotropic)
{
  device.SamplerState[0].MinFilter = TextureFilter.Anisotropic;
}
else if (device.DeviceCaps.TextureFilterCaps.SupportsMinifyLinear)
{
  device.SamplerState[0].MinFilter = TextureFilter.Linear;
}
// Do the same thing for magnify filter
if (device.DeviceCaps.TextureFilterCaps.SupportsMagnifyAnisotropic)
{
  device.SamplerState[0].MagFilter = TextureFilter.Anisotropic;
}
else if (device.DeviceCaps.TextureFilterCaps.SupportsMagnifyLinear)
{
  device.SamplerState[0].MagFilter = TextureFilter.Linear;
}

As you can see here, you first detect whether your device can support anisotropic filtering for minification or magnification. If it can, you use that filter for both filters. If it cannot, you next see whether the device supports a linear filter for both, and use that if it's available. If neither of these is available, you should do nothing and live with the pixilation of the road. Assuming your card supports one of these filtering methods, running the application now will show a much less-pixilated road.

Now the road is in the middle of the screen, but it's not yet moving. You will need a new method that will be used to update the game state, and do nifty things like moving the road and detecting when the car has collided with an obstacle. You should call this function as the first thing you do in your OnPaint method (before the Clear method):

// Before this render, we should update any state
OnFrameUpdate();

You will also need to add this method to your application. Add the method from Listing 6.6 to your code.

Listing 6.6 Per Frame Updates

private void OnFrameUpdate()
{
  // First, get the elapsed time
  elapsedTime = Utility.Timer(DirectXTimer.GetElapsedTime);
  RoadDepth0 += (RoadSpeed * elapsedTime);
  RoadDepth1 += (RoadSpeed * elapsedTime);
  // Check to see if we need to cycle the road
  if (RoadDepth0 > 75.0f)
  {
    RoadDepth0 = RoadDepth1 - 100.0f;
  }
 
  if (RoadDepth1 > 75.0f)
  {
    RoadDepth1 = RoadDepth0 - 100.0f;
  }
}

This function will get much larger before the game is finished, but for now, all you really want it to do is move the road. Ignoring the elapsed time (which I'll get to briefly), the only thing this function currently does is move the road and then remove road sections you've already passed, and place them at the end of the current road section. When determining the amount of movement the road should have, we use the current road speed (measured in units per second), and multiply that by the amount of elapsed time (measured in seconds), to get the "fraction" of movement this frame should have. You will also need to include the reference to the elapsedTime variable in your declaration section:

private float elapsedTime = 0.0f;

You might also like...

Comments

Contribute

Why not write for us? Or you could submit an event or a user group in your area. Alternatively just tell us what you think!

Our tools

We've got automatic conversion tools to convert C# to VB.NET, VB.NET to C#. Also you can compress javascript and compress css and generate sql connection strings.

“C++ : Where friends have access to your private members.” - Gavin Russell Baker