Designing the Blocks
With the code for the player now implemented, you are probably anxious to get it into the game engine and make your player move around the level and play the game. Don't get too far ahead of yourself yet, though: you haven't even designed the level object or the requirements for the level object, namely the blocks that compose the levels. You should design these blocks now. First, add a new code file to your project called block.cs and then add the initial class implementation in Listing 7.8.
Listing 7.8. The Initial Block Implementation
public class Block
{
private const float BoxSize = 3.0f; // World size of box
// List of colors available for boxes
private static readonly Color[] BoxColors = {Color.Red , Color.Blue,
Color.Green, Color.Yellow, Color.SandyBrown, Color.WhiteSmoke,
Color.DarkGoldenrod, Color.CornflowerBlue, Color.Pink};
// List of each possible material object for the box
private static Material[] boxMaterials;
// The static mesh of the box
private static Mesh boxMesh = null;
private Vector3 pos; // Current position of the box in the level
private Material material; // Current material color of the box
// Possible colors of the blocks
private BlockColor[] colors;
private int colorIndex;
private bool canWrap;
///
/// Create a new instance of the block class
///
/// D3D Device to use as the render object
public Block(Device dev, BlockColor[] list, int index, bool wrap)
{
if ((list == null) || (list.Length == 0))
throw new ArgumentNullException("list",
"You must pass in a valid list");
// Initialize the static data if needed
if (boxMesh == null)
{
InitializeStaticData(dev);
}
// Set the default position
pos = new Vector3(0,-1.0f, 0);
// Store the information
colors = list;
colorIndex = index;
canWrap = wrap;
// Set the default material
SetBlockColor(colors[colorIndex]);
}
///
/// Initialize the static data
///
/// D3D Device to use as the render object
private static void InitializeStaticData(Device dev)
{
// Create the mesh for each box that will be drawn
boxMesh = Mesh.Box(dev, BoxSize, BoxSize, BoxSize);
// Create the material list based on the colors of the boxes
boxMaterials = new Material[BoxColors.Length];
for (int i = 0; i < boxMaterials.Length; i++)
{
boxMaterials[i].Diffuse = boxMaterials[i].Ambient = BoxColors[i];
}
}
///
/// Clean up any resources used for rendering the blocks
///
public static void Cleanup()
{
if (boxMesh != null)
{
boxMesh.Dispose();
}
boxMesh = null;
boxMaterials = null;
}
}
One of the first things you'll notice is that the various block colors are all stored in a static read-only array. It is entirely possible for you to change these colors and add or remove others. Note that later when you create the level maker, doing so requires changes in there as well, but you haven't gotten there yet. One of the more interesting aspects of block mesh is that you'll notice that it's marked static. Regardless of how many blocks you have displayed onscreen, you only need a single mesh loaded. There's no reason to store many copies of the same data.
Because the blocks are all colored, the colors are controlled through the material that is applied to the rendering device before the block is rendered. Given that the light in the scene is white, if the material is say, blue, only the blue portion of the white light reflects on the block, making it appear blue. There is one material created for each of the block colors, and this array is static much like the colors array because you need only a single instance of this array. Finally, you declare the actual mesh, and again, it is static because you need only one for all blocks.
The actual instance variables for this class are similar to other classes you've already written. Obviously, because there will be many blocks in different places, you want to store the position of this block. You also store the current material of the block so it is colored correctly while rendering.
The last set of variables declared here control how the block reacts in the level. Each level has a minimum of two different block colors that any block can be at any time. You want to store the possible block colors in the order they will be used in this array. You also want to know the index of the color this block currently is. For the more difficult levels, these colors actually "wrap" back to the first index after they make it to the last color. To avoid passing the actual color structure around to specify the colors, you create an enumeration where each color is the index into the color array:
public enum BlockColor : byte
{
Red = 0,
Blue = 1,
Green = 2,
Yellow = 3,
SandyBrown = 4,
WhiteSmoke = 5,
DarkGoldenRod = 6,
CornflowerBlue = 7,
Pink = 8
}
The constructor for the block takes the list of colors for this level (which is simply stored), the color index for this block (which is also stored), whether or not the colors will "wrap" (again, stored), and finally the rendering device. Assuming the static boxMesh variable hasn't been allocated yet, all the static data is initialized now. Because the boxMesh variable is set in that method, it is called only once.
The InitializeStaticData method uses the Mesh.Box method to create a cube with the width, height, and depth sizes, all using the constant declared earlier. You then set up the possible materials using the array of colors declared earlier. With all the static data now created, you're free to continue creating your individual blocks. You store a default position for the blocks and set the current block color to the correct color based on the list of colors passed in and the index of the color. You haven't actually implemented this method yet, but you can find it in here:
private void SetBlockColor(BlockColor color)
{
material = boxMaterials[(byte)color];
}
As you see, it does nothing more than set the material of the block to the correct material based on the current block color. Because the BlockColor enumeration is just an index into the material and colors array, this method is easy to implement. Finally, you need a way to clean up the mesh the blocks use. Because it is a static method, you don't want to implement IDisposable on the Block class, which would cause the block's mesh to be disposed too soon and too often. You need to call the static method once before the application is closed. As a matter of fact, you should go ahead and add the call to your Dispose implementation in the game engine class now, just so you don't forget later. Directly before the device dispose call, add the cleanup for the blocks:
// Make sure to clean up the blocks
Block.Cleanup();
Because you need to update the position of the blocks, you want to ensure that you have a property to access it, so add this to the Block class:
///
/// Current Box Position
///
public Vector3 Position
{
get { return pos; }
set { pos = value; }
}
There are still a few methods to add to the Block class, which you will find in Listing 7.9.
|