Time for action – creating the Maze classes
- Add a new class file called
Maze.cs
to the Cube Chaser project. - Add the following
using
directives to the top of theMaze.cs
class file:using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics;
- Add the following fields to the
Maze
class:#region Fields public const int mazeWidth = 20; public const int mazeHeight = 20; GraphicsDevice device; VertexBuffer floorBuffer; Color[] floorColors = new Color[2] { Color.White, Color.Gray }; #endregion
- Add a constructor for the
Maze
class:#region Constructor public Maze(GraphicsDevice device) { this.device = device; BuildFloorBuffer(); }#endregion
- Add the following region and helper methods to the
Maze
class:#region The Floor private void BuildFloorBuffer() { List<VertexPositionColor> vertexList = new List<VertexPositionColor>(); int counter = 0; for (int x = 0; x < mazeWidth; x++) { counter++; for (int z = 0; z < mazeHeight; z++) { counter++; foreach (VertexPositionColor vertex in FloorTile(x, z, floorColors[counter % 2])) { vertexList.Add(vertex); } } } floorBuffer = new VertexBuffer( device, VertexPositionColor.VertexDeclaration, vertexList.Count, BufferUsage.WriteOnly); floorBuffer.SetData<VertexPositionColor>(vertexList.ToArray()); } private List<VertexPositionColor> FloorTile( int xOffset, int zOffset, Color tileColor) { List<VertexPositionColor> vList = new List<VertexPositionColor>(); vList.Add(new VertexPositionColor( new Vector3(0 + xOffset, 0, 0 + zOffset), tileColor)); vList.Add(new VertexPositionColor( new Vector3(1 + xOffset, 0, 0 + zOffset), tileColor)); vList.Add(new VertexPositionColor( new Vector3(0 + xOffset, 0, 1 + zOffset), tileColor)); vList.Add(new VertexPositionColor( new Vector3(1 + xOffset, 0, 0 + zOffset), tileColor)); vList.Add(new VertexPositionColor( new Vector3(1 + xOffset, 0, 1 + zOffset), tileColor)); vList.Add(new VertexPositionColor( new Vector3(0 + xOffset, 0, 1 + zOffset), tileColor)); return vList; } #endregion
What just happened?
So far, we have not really defined anything about the actual maze associated with the Maze
class other than the width and height of the maze we will be generating. The goal at this point is to build the floor of the maze and then bring our Maze
and Camera
classes together to allow us to display something to the game screen.
After we have generated all of the triangles necessary for our floor, they will be stored in the floorBuffer
field. This field is a VertexBuffer
, which holds a list of 3D vertices that can be sent to the graphics card in a single push.
Tip
Drawing with triangles
While we want to draw square floor tiles, the graphics card really only works with triangles. Even the most complex 3D models are made up of thousands or millions of small triangles. Fortunately, a square can be easily created with two equally-sized right triangles placed next to each other. Even when we load and display complex 3D models they are actually composed of lots of small triangles positioned to make up the surface of the object we are displaying.
In order to fill out this VertexBuffer
, we need to generate the points that make up the triangles for the floor. This is the job of the BuildFloorBuffer()
method, and its helper, FloorTile()
. BuildFloorBuffer()
begins by defining a List
of VertexPositionColor
objects. The built-in vertex declarations in XNA allow for different combinations of color, texture, and normal vectors to be associated with the position of the vertex (no matter what else a vertex has, it will always have a position). As the name implies, a VertexPositionColor
defines a vertex with a position in 3D space and an associated color.
We will determine the color of the vertices (and thus the triangles and the squares) on the floor of the maze by alternating between white and gray, picking the colors from the floorColors
list as the vertices are built.
The vertices for each square are built by calling the FloorTile()
method, which returns a list of VertexPositionColor
objects. Because we need two triangles to make up a square, we need to return six VertexPositionColor
elements. We will use a similar technique when we build the maze walls later in this chapter.
The FloorTile()
method accepts the X and Z offsets for this tile (if we were looking down at the maze from above, the number of squares across and down the maze we are building this square for) and the color for this particular tile. It then builds a new set of VertexPositionColor
objects by adding six new vertices, three for each triangle, to a List
object. The order that we define the vertices, called the winding order, is important. The vertices of each triangle need to be specified in a clockwise direction based on the angle from which the triangle will be viewed. The graphics device considers triangles to be single-sided entities. If we were to swing our camera underneath the maze, the floor would completely disappear.
In the previous image, we can see that we need four vertices to define the two triangles which compose a single floor square. We build the first triangle from the upper-left, upper-right, and lower-left vertices, and the second triangle from the upper-right, lower-right, and lower-left vertices, in those orders.
In the FloorTile()
method mentioned previously, we explicitly add these six points to the list of vertices that will be returned from the function, offsetting the X and Z values of each vertex by the values passed into the function. This has the effect of translating the triangles to their appropriate positions in the 3D world; otherwise they would be stacked all on top of each other near the world origin.