A World Without a Sky Would Be Black


You implemented a sky box early in this book for Blockers; it was the inside of a dungeon room, and it was a way to encompass the level. Sky boxes are always implemented because without one, you would have an empty area where the objects of your world weren't rendered. A plain black sky isn't realistic unless, of course, your game resides inside a vacuum in space (or maybe a black hole). I don't think the tanks here would work too well in a black hole.

I hope you've been noticing a consistent theme in the implementation of Tankers. The code has been rearranged to form more logical units, and that practice continues for the sky box. Rather than make this code reside within the game engine, you create a new class. Go ahead and add a new code file to your project, call it skybox.cs or whatever you want, and look at the implementation from Listing 14.1.

Listing 14.1. The World Class (Sky Box)
 using System; using System.Drawing; using System.Windows.Forms; using Microsoft.DirectX; using Microsoft.DirectX.Direct3D; namespace Tankers {     public class Skybox : IDisposable     {         private const float ScaleSize = 1950.0f;         private const float Height = ScaleSize * 2.0f;         private static readonly Matrix ScaleMatrix = Matrix.Scaling(ScaleSize,             ScaleSize, ScaleSize) * Matrix.Translation(0,Height,0);         private const string SkyboxMeshName = "skybox.x";         private Mesh skyboxMesh = null;         private Texture[] skyboxTextures = null;         public Skybox(Device device)         {             // First create the mesh             ExtendedMaterial[] mtrls = null;             skyboxMesh = Mesh.FromFile(GameEngine.MediaPath + SkyboxMeshName,                   MeshFlags.Managed, device, out mtrls);             // With the mesh created, create each of the textures             // used for the sky box             if ((mtrls != null) && (mtrls.Length > 0))             {                 skyboxTextures = new Texture[mtrls.Length];                 for(int i = 0; i < mtrls.Length; i++)                 {                     skyboxTextures[i] = TexturePool.CreateTexture(device,                                         mtrls[i].TextureFilename);                 }             }         }         /// <summary>         /// Render the sky box         /// </summary>         public void Draw(GameEngine engine, Device device)         { #if (DEBUG)             engine.numberFaces += skyboxMesh.NumberFaces;             engine.numberVerts += skyboxMesh.NumberVertices; #endif             // When rendering the sky box you will want to turn off the zbuffer             device.RenderState.ZBufferEnable = false;             device.RenderState.ZBufferWriteEnable = false;             // Scale the world transform and turn off lighting             engine.SetWorldTransform(ScaleMatrix);             device.RenderState.Lighting = false;             // Render each subset of the mesh             for(int i = 0; i < skyboxTextures.Length; i++)             {                 device.SetTexture(0, skyboxTextures[i]);                 skyboxMesh.DrawSubset(i);             }             // Turn the zbuffer back on             device.RenderState.ZBufferEnable = true;             device.RenderState.ZBufferWriteEnable = true;             // Turn lighting back on             device.RenderState.Lighting = true;         }         /// <summary>         /// Will be used to clean up objects         /// </summary>         public void Dispose()         {             if (skyboxMesh != null)             {                 skyboxMesh.Dispose();                 skyboxMesh = null;             }         }     } } 

The constants are used to manipulate the transformation of the sky box (and define the name of the model that you will be using). The transformation matrix is defined by scaling the model to a much larger size, and it is translated up along the Y axis twice the scale size. This move forces the sky box to be large enough to encompass the entire world. (The tank model is large, and rather than scale that model down, you scale the sky up.)

The instance variables should be instantly recognizable by now, the mesh that is being used to render the sky box and the series of six textures that compose the sky that the sky box gets its name from. During the creation of the object, the mesh is loaded and each texture is loaded from the pool; then, you're ready to render the world. I hope you noticed that the materials weren't stored for the object now as they were during Blockers.

Construction Cue

You'll notice that even these textures are loaded from the texture pool you created a few chapters ago. You might be wondering why you need to use that even here, when it's obvious you won't be reusing these textures later. The fact is you could skip the texture pool and simply create it for use only here, but you should be consistent throughout the application. Besides, just because these textures aren't reused currently doesn't mean they won't be reused at any time in the future.


The rendering here includes the debug code for counting the number of vertices and faces being rendered. Because it is just a textured cube, the number of vertices and faces is small, but because you have the information, you might as well use it. After that, you turn off the depth buffer because there is no way that anything can be rendered behind the sky box; the extra calculation is unnecessary. You also turn off lighting because you want to use the texture color as the only source of color for the sky. Finally, before the actual rendering takes place, the world transform is set from the game engine. (Do you remember why we have a method for this?)

After the sky is rendered, you reset the render states that you were using to handle the rendering of the sky box; namely, you turn the depth buffer and lighting back on. The Dispose method shouldn't need any explanation by now.

Did you notice that you've stopped implementing a "finalizer" (destructor) to the objects on which you've implemented IDisposable? If you were designing a library that other users would use, this step would not be a good idea because you wouldn't be able to guarantee that the caller of your object would call Dispose(). Because the only caller of these objects is you, you can ensure that you are calling Dispose on these objects.

You're probably asking yourself, "Well, why do I care? Wouldn't I rather be safe than sorry and just leave them implemented? If they don't get used, well, that's great." Although that is an option, it wouldn't be a good idea, and it has everything to do with the garbage collector. Let's look at a scenario.

You've just created an object that has both a Dispose method and a finalizer. Your code only uses the object for a brief moment and then no longer needs the object, but it never calls the Dispose method. The garbage collector sees an unused short-lived object and decides, "I should probably get rid of that because it's obviously not needed anymore," and a generation 0 collection starts. As the collection notices that this object is no longer needed, it sees that the object also has a finalizer. An object cannot be collected until after the finalizer runs, and the garbage collector can't sit around and wait for the object to be finalized. So the object is marked that it's ready to be finalized and promoted to generation 1. In a few chapters, when you get to the performance chapter and I discuss the garbage collector in detail, you'll get a better understanding of why this is a bad thing.

Construction Cue

A good rule of thumb is to never have a finalizer unless you are sure you need it. For now, you do not.




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