Before you can create your DirectDraw device, a new project will need to be created. Since this will still be an application where items will need to be rendered, windows forms should still be used. Use these steps to get started:
You'll notice that most of these steps are similar to what was done for the 3D applications that were built in earlier chapters, with the major difference being the references that were added. In DirectDraw you don't have textures that are used to represent your 2D images; instead you have "surfaces." A surface is essentially a data store for a rectangular image. When rendering a single sprite with a full-screen device, you will need a total of three surfaces. Add these to your application now: private Surface primary = null; private Surface backBuffer = null; private Surface sprite = null; Obviously the sprite surface will be where the actual image being rendered is stored. The first two parameters are something new, though. Since the screen itself is essentially a rectangular surface that you will be rendering to, you will use a surface to access this as well. Rendering directly to the video screen is never a good idea, because then you can have problems with what is called "tearing," which is when part of two (or more) different rendered scenes end up on the screen at the same time. To ensure this behavior doesn't happen, all of the rendering should happen in a stored back buffer, and then be copied to the primary buffer in one chunk. This is handled for you automatically in Direct3D (see Chapter 1, "Introducing Direct3D"), but you must handle it yourself in DirectDraw. Before you go on, you should once again ensure that you handle the Escape key to quit. Since the application will be running in full screen mode, you want an easy way to quit. Add the following code to ensure your application exits on escape: protected override void OnKeyUp(KeyEventArgs e) { if (e.KeyCode == Keys.Escape) // Quit this.Close(); base.OnKeyUp (e); } Now that an easy method to quit has been established, you can create your initialization method. Add the method found in Listing 17.1 to your application. Listing 17.1 Initialize a DirectDraw Devicepublic void InitializeGraphics() { SurfaceDescription description = new SurfaceDescription(); device = new Device(); // Set the cooperative level. device.SetCooperativeLevel(this, CooperativeLevelFlags.FullscreenExclusive); // Set the display mode width and height, and 16 bit color depth. device.SetDisplayMode(ScreenWidth, ScreenHeight, 16, 0, false); // Make this a complex flippable primary surface with one backbuffer description.SurfaceCaps.PrimarySurface = description.SurfaceCaps.Flip = description.SurfaceCaps.Complex = true; description.BackBufferCount = 1; // Create the primary surface primary = new Surface(description, device); SurfaceCaps caps = new SurfaceCaps(); caps.BackBuffer = true; // Get the backbuffer from the primary surface backBuffer = primary.GetAttachedSurface(caps); // Create the sprite bitmap surface. sprite = new Surface(@"..\..\logo.bmp", new SurfaceDescription(), device); // Set the colorkey to the bitmap surface. // which is what the ColorKey struct is initialized to. ColorKey ck = new ColorKey(); sprite.SetColorKey(ColorKeyFlags.SourceDraw, ck); } As you can see, this method is slightly more complicated than the Direct3D version. After your DirectDraw device has been created, you will need to call the SetCooperativeLevel method, which notifies DirectDraw how you plan on cooperating with other applications wishing to share the graphics card resources. Since this application will be full screen, there's no need to share the resources. Using the option FullScreenExclusive tells DirectDraw this. After the device has been created and the cooperative level has been set, you can now change the display mode. The first two parameters are the width and height of the updated display mode, respectively. The third parameter is the color depth you want the display mode to be in. 16-bit color is the most commonly used format. 32-bit color gives you a larger array of colors to use, but uses double the amount of resources. The fourth parameter is the refresh rate the monitor should use; for most applications simply using zero for the monitor default should be sufficient. The last parameter indicates whether this mode is a standard VGA mode. It's a rare case when this would be used. With the device in a good state now, it's time to create our surfaces. First, the primary surface should be created. Each surface that is created must be created with a surface description describing that surface. In this case, the primary surface description will include the options of PrimarySurface (naturally), Flip, and Complex. These options inform DirectDraw that not only will this be the primary surface, it will be flippable and complex. Complex surfaces have other surfaces attached, and in this case, the attached surface will be the back buffer surface. This is the reason for the back buffer count of one for this description. With this surface description, you can now create the primary surface. From that surface, the back buffer surface can be retrieved by calling the GetAttachedSurface method on the primary surface. With the first two surfaces created, the last item that needs to be created is the actual sprite surface. The code included on the CD uses the dx5_logo.bmp that ships as part of the DirectX SDK as the sprite to render. Since the surface will be created from the file, no extra information is needed for creating this surface, thus the "empty" surface description.
The initialization method uses a few constants that haven't been declared yet. Go ahead and add those now: public const int ScreenWidth = 800; public const int ScreenHeight = 600; private static readonly Rectangle SpriteSize = new Rectangle(0,0,256,256); The screen width and height constants have been used before. Since the image file that will be used as the sprite is of known size, storing this size is easier than determining the size later. If you did want to calculate the size, you could simply look at the Width and Height members of the surface description structure after the surface has been created. You will need to call the initialize method at some point, and the main method is the ideal place. Update this method much like the Direct3D applications were used: static void Main() { using (Form1 frm = new Form1()) { // Show our form and initialize our graphics engine frm.Show(); frm.InitializeGraphics(); Application.Run(frm); } } Now, once again you will need a wrapper class that will be used to maintain your sprites' information. Add the following class to your application: public class GraphicsSprite { // static data for our sprites private static readonly Random rnd = new Random(); // Instance data for our sprites private int xPosition = 0; private int yPosition = 0; private float xUpdate = 1.4f; private float yUpdate = 1.4f; /// <summary> /// Constructor for our sprite /// </summary> /// <param name="posx">Initial x position</param> /// <param name="posy">Initial y position</param> public GraphicsSprite(int posx, int posy) { xPosition = posx; yPosition = posy; xUpdate += (float)rnd.NextDouble(); yUpdate += (float)rnd.NextDouble(); } } This class will store the current position of the sprite's upper-left corner. You will notice that in this case, two separate integer values are stored, rather than the Vector3 that was used in the Direct3D example. Since DirectDraw only deals with screen coordinates, the position will be integer values rather than the floats used in Direct3D. Since there is no depth in a DirectDraw application, the third member isn't needed at all. The class will still store the velocity for each direction separately, in order to have the sprites bounce around the screen. The constructor for the class will take the initial position of the sprite, as well as randomly incrementing the velocity in each direction, so all sprites don't follow the same pattern. Now, you should update your sprite class to handle the movement and drawing. Add the two methods found in Listing 17.2 to your sprite class. Listing 17.2 Updating and Drawing Your Spritespublic void Draw(Surface backBuffer, Surface spriteSurface, Rectangle spriteSize) { backBuffer.DrawFast(xPosition, yPosition, spriteSurface, spriteSize, DrawFastFlags.DoNotWait | DrawFastFlags.SourceColorKey); } public void Update(Rectangle spriteSize) { // Update the current position xPosition += (int)xUpdate; yPosition += (int)yUpdate; // See if we've gone beyond the screen if (xPosition > (Form1.ScreenWidth - spriteSize.Width)) { xUpdate *= -1; } if (yPosition > (Form1.ScreenHeight - spriteSize.Height)) { yUpdate *= -1; } // See if we're too high or too the left if (xPosition < 0) { xUpdate *= -1; } if (yPosition < 0) { yUpdate *= -1; } } The drawing call takes in the back buffer surface that you will render to (remember, you don't want to render directly to the screen), as well as the sprite's surface and its size. The DrawFast call from the back buffer simply draws the sprite at the location specified, using the full sprite as the source material (the size). The flags tell DirectDraw that it shouldn't wait if the draw cannot be completed immediately, and that the source color key should be used for transparency (which was set up when the sprite was created).
The update method is virtually identical to the Direct3D version. The position of the sprite is updated by the velocity, and the direction is reversed if the sprite has come to a border. With the sprite class now complete, you will need to maintain a list of sprites for your application. The same method that was used for the Direct3D example will be used here, so add the following variable to your main class: System.Collections.ArrayList ar = new System.Collections.ArrayList(); You'll still need to add some sprites to your collection now, and you can use the same code that was used before. Add the following to the end of your initialization method: // Add a few sprites ar.Add(new GraphicsSprite(0,0)); ar.Add(new GraphicsSprite(64,128)); ar.Add(new GraphicsSprite(128,64)); ar.Add(new GraphicsSprite(128,128)); ar.Add(new GraphicsSprite(192,128)); ar.Add(new GraphicsSprite(128,192)); ar.Add(new GraphicsSprite(256,256)); The only thing left is to add the rendering to our main class. This application will use the same mechanism for the rendering, so add the following override into your windows form class: protected override void OnPaint(System.Windows.Forms.PaintEventArgs e) { Microsoft.DirectX.DirectXException.IgnoreExceptions(); foreach(GraphicsSprite gs in ar) gs.Update(SpriteSize); backBuffer.ColorFill(0); foreach(GraphicsSprite gs in ar) gs.Draw(backBuffer, sprite, SpriteSize); primary.Flip(backBuffer, FlipFlags.DoNotWait); this.Invalidate(); } The very first thing this method does is to turn off all Managed DirectX exception throwing. This will be explained briefly, but you should use this method with extreme caution, as it will disable all exception throwing from Managed DirectX. Now each sprite is updated based on the stored size, much like the 3D version of this application. Next, in place of the Clear method the Direct3D application used, the back buffer will simply be filled with a solid color. In this case, black will be used. Next, each sprite in your collection will be rendered to the back buffer. With everything now rendered into the back buffer, you are ready to update the screen. Simply call the Flip method on the primary buffer, passing in the back buffer where everything has been rendered. This will update the screen with the contents of the back buffer. Finally, invalidate the window's contents, so that the OnPaint override will be called again immediately, and there is a constant state of rendering going on.
Running the application now should show similar results to the Direct3D version of this application. |