Using Managed DirectX to Write a Game

Adding Obstacles

Congratulations. This is the first interactive 3D graphics application you've created. The effect of simulating car movement has been achieved. While you know that it's really the road that is moving below the car, it appears as if the car is speeding down the road. You've got half of the gameplay options implemented already. What you need now are the obstacles that you will be trying to avoid. Much like you added the car class, add a new class called "Obstacle". Make sure you add the using clauses for the Managed DirectX references for this code file as well.

Rather than using the same mesh for every obstacle, it would be nice to have a little variety in the obstacle list: in both type of obstacle as well as color. You can use the stock objects that can be created from the mesh object to vary the types of mesh, and you can use the materials to vary the colors. You should add the constants and variables needed for the obstacle class from Listing 6.10.

Listing 6.10 Obstacle Constants

// Object constants
private const int NumberMeshTypes = 5;
private const float ObjectLength = 3.0f;
private const float ObjectRadius = ObjectLength / 2.0f;
private const int ObjectStacksSlices = 18;
// obstacle colors
private static readonly Color[] ObstacleColors = {
    Color.Red, Color.Blue, Color.Green,
    Color.Bisque, Color.Cyan, Color.DarkKhaki,
    Color.OldLace, Color.PowderBlue, Color.DarkTurquoise,
    Color.Azure, Color.Violet, Color.Tomato,
    Color.Yellow, Color.Purple, Color.AliceBlue,
    Color.Honeydew, Color.Crimson, Color.Firebrick };
// Mesh information
private Mesh obstacleMesh = null;
private Material obstacleMaterial;
private Vector3 position;
private bool isTeapot;

As you can see from the first constant, there will be five different mesh types (sphere, cube, torus, cylinder, and teapot). The majority of these mesh types will have either a length parameter or a radius parameter. Since you will want the obstacles to be similarly sized, you should use constants for these parameters. Many of the mesh types also have extra parameters that control the number of triangles included in the mesh (stacks, slices, rings, and so forth). The last constant will be used for these parameters. Increase this value for more triangles (and a higher level of detail), decrease it for less.

The list of colors that is next is where you will pick your color for the mesh. I've randomly selected these colors, but you can feel free to add, remove, or change any of the colors in this list. You'll notice that you aren't storing an array of materials, nor are you storing textures for our meshes in this class. You know the default mesh types only contain one subset with no textures or materials, so this extra information isn't necessary.

Since the obstacles should be "lying" on the road as the player drives by, and you know in reality that it's the road that is moving (not the player), you will also need to ensure that the obstacles move with the world. You therefore maintain a position for the obstacle that will be updated every frame as the road moves. Finally, since you can't control the size of a teapot during creation, you need to know if we're using a teapot obstacle so that you can scale it to the correct size.

Replace the class constructor of the Obstacle class with the following new one:

public Obstacle(Device device, float x, float y, float z)
{
  // Store our position
  position = new Vector3(x, y, z);
  // It's not a teapot
  isTeapot = false;
  // Create a new obstacle
  switch (Utility.Rnd.Next(NumberMeshTypes))
  {
    case 0:
      obstacleMesh = Mesh.Sphere(device, ObjectRadius, ObjectStacksSlices,
        ObjectStacksSlices);
      break;
    case 1:
      obstacleMesh = Mesh.Box(device, ObjectLength, ObjectLength, ObjectLength);
      break;
    case 2:
      obstacleMesh = Mesh.Teapot(device);
      isTeapot = true;
      break;
    case 3:
      obstacleMesh = Mesh.Cylinder(device, ObjectRadius, ObjectRadius, ObjectLength,
        ObjectStacksSlices, ObjectStacksSlices);
      break;
    case 4:
      obstacleMesh = Mesh.Torus(device, ObjectRadius / 3.0f, ObjectRadius / 2.0f,
        ObjectStacksSlices, ObjectStacksSlices);
      break;
  }
  // Set the obstacle color
  obstacleMaterial = new Material();
  Color objColor = ObstacleColors[Utility.Rnd.Next(ObstacleColors.Length)];
  obstacleMaterial.Ambient = objColor;
  obstacleMaterial.Diffuse = objColor;
}

You'll notice the use of the "Rnd" property on a utility class here. The source for this function is included on the CD, and is unimportant to the implementation of this function. Its goal is to simply return random numbers. The constructor for our obstacle stores the default position of the obstacle, and defaults to a non-teapot mesh (since there is only one case where it will be a teapot). It then randomly selects one of the mesh types and creates that mesh. Finally, it selects a random color from the list and uses that as the material color for the obstacles.

Before you're ready to get the obstacles into your game engine, though, there are a couple other things you will need to do. First, you need to have a function to update your objects' position to match the road. Add the following method:

public void Update(float elapsedTime, float speed)
{
  position.Z += (speed * elapsedTime);
}

Again, you pass in the elapsed time to ensure that we maintain the same behavior on machines of different speeds. You also pass in the current speed of the road, so the objects will be moving as if they are lying on the road. With the obstacles being updated, you also need a method to do the rendering. Much like the car's draw method, you will need one for your obstacle as well. Add the method found in Listing 6.11 to your obstacle class.

Listing 6.11 Drawing Obstacles

public void Draw(Device device)
{
  if (isTeapot)
  {
    device.Transform.World = Matrix.Scaling(ObjectRadius, ObjectRadius, ObjectRadius)
      * Matrix.Translation(position);
  }
  else
  {
    device.Transform.World = Matrix.Translation(position);
  }
  device.Material = obstacleMaterial;
  device.SetTexture(0, null);
  obstacleMesh.DrawSubset(0);
}

Since the teapot mesh isn't correctly scaled after creation, if you are rendering one of those, you should first scale the teapot, and then move it into position. You then set the material for your color, set the texture to null, and draw the mesh.

Obviously, you will want to have more than one obstacle on the road at a time. What you need is an easy way to add and remove obstacles from the game engine. Using an array is a possibility, but not the greatest since it makes resizing the array a bit difficult. You should just make a collection class for storing your obstacles. Add the class found in Listing 6.12 to the end of your obstacle code file.

Listing 6.12 Obstacles Collection Class

public class Obstacles : IEnumerable
{
  private ArrayList obstacleList = new ArrayList();
  /// <summary>
  /// Indexer for this class
  /// </summary>
  public Obstacle this[int index]
  {
    get
    {
      return (Obstacle)obstacleList[index];
    }
  }
  // Get the enumerator from our arraylist
  public IEnumerator GetEnumerator()
  {
    return obstacleList.GetEnumerator();
  }
  /// <summary>
  /// Add an obstacle to our list
  /// </summary>
  /// <param name="obstacle">The obstacle to add</param>
  public void Add(Obstacle obstacle)
  {
    obstacleList.Add(obstacle);
  }
  /// <summary>
  /// Remove an obstacle from our list
  /// </summary>
  /// <param name="obstacle">The obstacle to remove</param>
  public void Remove(Obstacle obstacle)
  {
    obstacleList.Remove(obstacle);
  }
  /// <summary>
  /// Clear the obstacle list
  /// </summary>
  public void Clear()
  {
    obstacleList.Clear();
  }
}

You will need to put a using clause for System.Collections at the top of your code file for this to compile correctly. This class contains an indexer for direct access to an obstacle, an enumerator so that the foreach construct works, and the three methods you will care about: add, remove, and clear. With this base functionality in your obstacles code file, you're ready to add obstacles to your engine.

First, you will need a variable that you can use to maintain the list of current obstacles in the scene. Add the following variable into the DodgerGame class:

// Obstacle information
private Obstacles obstacles;

Now you need a function you can use to fill up the next road section with new obstacles. Use the following code for this:

/// <summary>
/// Add a series of obstacles onto a road section
/// </summary>
/// <param name="minDepth">Minimum depth of the obstacles</param>
private void AddObstacles(float minDepth)
{
  // Add the right number of obstacles
  int numberToAdd = (int)((RoadSize / car.Diameter - 1) / 2.0f);
  // Get the minimum space between obstacles in this section
  float minSize = ((RoadSize / numberToAdd) - car.Diameter) / 2.0f;
  for (int i = 0; i < numberToAdd; i++)
  {
    // Get a random # in the min size range
    float depth = minDepth - ((float)Utility.Rnd.NextDouble() * minSize);
    // Make sure it's in the right range
    depth -= (i * (car.Diameter * 2));
    // Pick the left or right side of the road
    float location = (Utility.Rnd.Next(50) > 25)?RoadLocationLeft:RoadLocationRight;
    // Add this obstacle
    obstacles.Add(new Obstacle(device, location, ObstacleHeight, depth));
  }
}

This function will be the starting point for getting obstacles into the game. It first calculates the number of obstacles to add in this road section. It has to make sure that there will be enough room between obstacles for the car to fit; otherwise, the game wouldn't be very fair. After it calculates the number of obstacles needed for the road and the minimum space between obstacles, it adds them randomly to the road. It then adds them to the current list of obstacles. You'll notice the ObstacleHeight constant used when creating a new obstacle. The definition for this is

private const float ObstacleHeight = Car.Height * 0.85f;

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.

“Some people, when confronted with a problem, think "I know, I’ll use regular expressions." Now they have two problems.” - Jamie Zawinski