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;     /// <summary>     /// Create a new instance of the block class     /// </summary>     /// <param name="dev">D3D Device to use as the render object</param>     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]);     }     /// <summary>     /// Initialize the static data     /// </summary>     /// <param name="dev">D3D Device to use as the render object</param>     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];         }     }     /// <summary>     /// Clean up any resources used for rendering the blocks     /// </summary>     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:

 /// <summary> /// Current Box Position /// </summary> 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.

Listing 7.9. Controlling Blocks
 /// <summary> /// Retrieve the block color /// </summary> public BlockColor BlockColor {     get { return colors[colorIndex]; } } /// <summary> /// Sets whether or not the block should pulse /// </summary> public void SetBlockPulse(bool pulse) {     shouldPulse = pulse; } /// <summary> /// Sets the current total time on a block /// </summary> public void SetBlockTime(float totalTime) {     time = totalTime; } /// <summary> /// Sets the block velocity after game over /// </summary> public void SetBlockVelocity(Vector3 vel) {     velocity = vel; } public void UpdateBlock(float elapsed) {     // Update the block position based on velocity     Vector3 velocityFrame = velocity;     velocityFrame.Normalize();     velocityFrame.Scale(velocity.Length() * elapsed);     // Move the block     pos += velocityFrame; } 

You obviously need a way to get the color of any given block (which is used to check whether the level was completed), and you also want a way to set whether the block should "pulse." The blocks that are the incorrect color pulse slightly to give the player a visual cue about what blocks still need to be switched. You use the time variable to control the effect of the pulse.

When the game is over, you want all the blocks to "explode" away, to really let the user know he or she failed. You create this explosion by assigning a random velocity to the block. The UpdateBlock method uses a similar method to what you used when you moved the player around. The velocity is normalized and then scaled to the correct velocity based on the elapsed time. The position of the block is then updated with this new velocity. This method is called only after the game is over and only when the level was lost.

These methods all use some variables that haven't been declared yet, so declare them now:

 private float time = 0.0f; private bool shouldPulse = false; private Vector3 velocity; 

With all of this done, you still need a way to render the blocks. You will find the render method in Listing 7.10.

Listing 7.10. Rendering Blocks
 /// <summary> /// Render the current box using instance settings /// </summary> public void Draw(Device dev) {     // Set the device's material to the color of this box     dev.Material = material;     // Move the box into position     if (shouldPulse)     {         float scaling = (float)Math.Abs(Math.Sin(time * MaxBoxScaleSpeed));         scaling *= MaxBoxScale;         float scaleFactor = 1.0f + scaling;         dev.Transform.World = Matrix.Translation(pos) * Matrix.Scaling(             scaleFactor, 1.0f, scaleFactor);     }     else     {         // Move the box into position         dev.Transform.World = Matrix.Translation(pos);     }     // Turn off any texture     dev.SetTexture(0, null);     // Draw the box     boxMesh.DrawSubset(0); } 

The rendering of the block is pretty easy to see. To ensure that the block is the correct color, you first set the material. Then you determine whether the block should pulse; if it shouldn'tit's easyyou simply translate the box into position. If the block is pulsing, though, there is a slight difference because you also scale the box. You achieve the pulse effect by quickly scaling the box between normal size and slightly larger using a standard sine wave. After the transformation is complete, you simply use the DrawSubset call to render the box.

The Block class is almost done; you have only one more thing left to do. You need a way to make the block move to the next color when it is stepped on. Include the method from Listing 7.11 in your class.

Listing 7.11. Selecting Next Block Color
 public void SelectNextColor() {     // Are we at the end, and can wrap?  If so, reset the color index     if ((colorIndex == (colors.Length - 1)) && (canWrap))     {         // Reset color index         colorIndex = 0;     }     // Otherwise, if we're not at the end, increment the color index     else if (colorIndex != (colors.Length - 1))     {         // Increment color index since we haven't gotten to the last one yet         colorIndex++;     }     // Update material     SetBlockColor(colors[colorIndex]); } 

Here you need to first check whether the color can wrap. If it can, and your index is already on the last color in your list, simply reset the index back to the first. Otherwise, if you haven't made it to the last color in your list, simply increment your index to select the next color. If you are on the last color in your list and you can't wrap, you won't do anything. Next, call the SetBlockColor method to update the material for your block.



Beginning 3D Game Programming
Beginning 3D Game Programming
ISBN: 0672326612
EAN: 2147483647
Year: 2003
Pages: 191
Authors: Tom Miller

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net