Article Description
GDI+ is a feature rich graphics API that makes sophisticated graphical effects highly accessible to the C# developers. Unfortunately if youve tried to develop smooth detailed animation using GDI+ you have undoubtedly discovered that just how slow it can be. Consequently it is not particularly suited to games development, if you want to really take advantage of your 1 gig + processor and that fancy new graphics card youre going to have to get a little more low level and dirty, enter Microsofts DirectX API.
DirectX is a collection of COM dlls developed by Microsoft with games programming particularly in mind. DirectX communicates more directly with the computer hardware (ie Video Card, Sound Card etc..) then GDI+ yet still maintains a sufficient level of abstraction, freeing the developer from the particulars of the hardware running on the users machine.
Unfortunately .net and C# is not an officially supported platform for DirectX development and will not be until DirectX 9 is released at the end of the year. However we can use DirectX with C# in the meantime by using the Visual Basic type library's that come with version 7 and 8 of the API. This article uses the example of a relatively simple Breakout style game imaginatively called Space Breakout to demonstrate how to use the DirectX VB Type Lib in C#.

The Game

Figure I
Figure I is a UML diagram of the games design. There are eight classes in total, one class that represents the main windows form, one class which encapsulates the main directx objects, five classes which deal with the game sprites and a class that represents the group of block sprites. Each of these classes will be dealt with in detail below. To play the game use the left and right arrow keys to move the paddle and space to launch the game ball, to exit the game press the escape key.
DirectX and Direct Draw
DirectDraw was the main 2D interface to DirectX prior to version 8 (which is the latest version of DirectX). Since 2D graphics are no longer the mainstay of windows game programming Microsoft decided to merge the 2D and 3D elements of the DirectX API into a new interface known as DirectGraphics. There are advantages to this approach, however if you want to develop purely 2D games without any hardware accelerated 3D there are several disadvantages, the biggest being:-
Direct Graphics requires a 3D accelerator card for hardware 2D acceleration
Generating 2D graphics in DirectGraphics is more complex then using DirectDraw as it requires knowledge of 3D concepts.
Therefore when developing purely 2D games DirectDraw is probably the first place to start as it is still possible to create a DirectX7 object(which supports directDraw) under DirectX8.
Setting up a DirectX Application
Before we can begin a .net DirectX application we need to create a reference to the DirectX COM dll. In Visual Studio.net this is done by choosing Add Reference from the project menu. If DirectX 7 or 8 are installed on your machine then you should see a reference to the DirectX for Visual Basic Type Library under the COM tab of the Add reference Dialog. DirectX applications in .net must be based on a WinForm, so the next step is to create a standard borderless WinForm.
Initialising DirectX
In SpaceBreakout the main DirectX objects and their initialisation are encapsulated in the GameDirectDrawClass. There are two main display modes in DirectDraw, full-screen mode and windowed mode. Full-screen mode is the most useful for game development as DirectDraw is able to monopolise the users graphics display while the game is running, this in turn leads to significantly better performance in comparison to windowed mode. The following variables are used in the GameDirectDrawClass to initialise Full-Screen mode (through out the article I will be assuming that you have the statement 'using DxVBLib;' at the top of your code file):-
private DirectX7 m_objDirectX = new DirectX7();
private DirectDraw7 m_objDirectDraw;
private DirectDrawSurface7 m_objPrimarySurface;
private DirectDrawSurface7 m_objBackBufferSurface;
private DDSURFACEDESC2 m_objPrimarySurfaceDescription;
private DDSURFACEDESC2 m_objBackBufferSurfaceDescription;
m_objDirectX is the main DirectX instance that is used by the game and predictably m_objDirectDraw is the main DirectDraw instance for the game. The next two variables (m_objPrimarySurface and m_objBackBufferSurface) are DirectDraw surfaces, a surface represents the actual drawing surfaces that we use and are analogous to 'Graphics' objects in GDI+.
Every DirectDraw application must have at least one surface which represents the actual video buffer being rasterised and displayed by the graphics card, this is usually refered to as the primary surface and will be created using the m_objPrimarySurface variable. In addition to the primary surface an application will have a number of off screen surfaces that exist in memory, the second surface variable which was created is a special kind of offscreen surface known as the Back Buffer (m_objBackBufferSurface).
The Backbuffer surface is used in a process known as double buffering. Double buffering is the process of using a second surface to perform all the drawing which builds up a frame of animation, when all the drawing operations are complete the entire secondary or BackBuffer surface is copied to the Primary surface. The process of copying the Backbuffer to the Primary surface in DirectDraw full screen mode is page flipping. When the pages are flipped the Primary Surface points to the area of memory containing the BackBufferSurface and the Backbuffer points to the area of memory that the Primary surface pointed. The effect is that the Primary Surface object contains the contents of the Backbuffer and the BackBuffer object contains the contents of the primary surface. Once the pages have been flipped the BackBuffer can be cleared and the next frame drawn, figure II demonstrates the process.

Figure II
The remaining two variables m_objPrimarySurfaceDescription and m_objBackBufferSurfaceDescription are used to set the properties of the two surfaces and will be explained later on.
The method in the GameDirectDraw class that initialises the direct draw objects is InitialiseDirectXFullScreen(), it has one argument which is an integer representing the handle of the game's form. The code for this function is below:-
InitialiseDirectXFullScreen(int objDisplayFormHandle)
{
m_objDirectDraw = m_objDirectX.DirectDrawCreate("");
m_objDirectDraw.SetCooperativeLevel (objDisplayFormHandle,
ONST_DDSCLFLAGS.DDSCL_FULLSCREEN |
ONST_DDSCLFLAGS.DDSCL_EXCLUSIVE);
m_objDirectDraw.SetDisplayMode(640, 480, 16, 0,
ONST_DDSDMFLAGS.DDSDM_DEFAULT);
m_objPrimarySurfaceDescription.lFlags =
ONST_DDSURFACEDESCFLAGS.DDSD_CAPS |
ONST_DDSURFACEDESCFLAGS.DDSD_BACKBUFFERCOUNT;
m_objPrimarySurfaceDescription.ddsCaps.lCaps =
ONST_DDSURFACECAPSFLAGS.DDSCAPS_PRIMARYSURFACE
CONST_DDSURFACECAPSFLAGS.DDSCAPS_FLIP |
ONST_DDSURFACECAPSFLAGS.DDSCAPS_COMPLEX;
m_objPrimarySurfaceDescription.lBackBufferCount = 1;
m_objPrimarySurface = m_objDirectDraw.CreateSurface(ref
_objPrimarySurfaceDescription);
DDSCAPS2 ddscaps = new DDSCAPS2();
ddscaps.lCaps = CONST_DDSURFACECAPSFLAGS.DDSCAPS_BACKBUFFER;
m_objBackBufferSurfaceDescription = m_objPrimarySurface.GetAttachedSurface(ref
dscaps);
m_objBackBufferSurfaceDescription(ref m_objBackBufferSurfaceDescription);
}
The first line of code creates the DirectDraw object from the DirectX 7 object. Once this has been done the SetCooperativeLevel() method of the DirectDraw object is called, this sets the level of co-operation that the DirectDraw object will have with the operating system. The first parameter is the handle of the form that will be used for the application and the second contain the flags indicating the required co-operative level, the two flags used indicate that we will be using full screen mode and taking exclusive control of the display.
The next DirectDraw method (SetDisplayMode) sets the display mode for the game, the first two parameters indicate that the resolution will be 640 pixels by 480 pixels, the third specifies 16bit colour, the fourth is the refresh rate - by setting it to 0 DirectDraw will automatically use the best rate, the fifth argument indicates that no advanced resolutions are to be used.
Next properties are assigned to the Primary Surface descriptor variable, this variable will be used to create the Primary Surface. First we indicate that the description will specify the capabilities of the surface and it's Backbuffer count, this is done using the lflags property. The next line specifies the sets the capabilities of the surface, the first constant indicates that this will be a primary surface, the next constant indicates that we will be using page flipping to copy the backbuffer to the primary surface and the final constant is needed if we are using page flipping. The next line sets the back buffer count for the surface, it indicates that 1 backbuffer will be used. With the surface description configured the next line creates the primary surface using the CreateSurface() method of the DirectDraw object, the method takes a reference to the surface descriptor as it's one parameter.
With the Primary surface initialised the Backbuffer can then be created. We first need to set the caps property of the Backbuffer descriptor to indicate that this is a BackBuffer surface, we then use the GetAttachedSurface() method of the PrimarySurface object to create the Backbuffer.
Displaying Bitmaps
Bitmaps in DirectDraw must be loaded into new Surfaces and then copied onto the BackBuffer. Since there are a number of bitmap graphics in the game an abstract class (called BitmapObject) contains the code for bitmaps, this class is then inherited by each of the individual game object classes. The main variables used in the BitmapObject class to create a bitmap object and display it on the Backbuffer are:-
public const int BLACK_TRANSPARANT_SPRITE = 1;
public const int NORMAL_BITMAP = 0;
protected DDSURFACEDESC2 m_objDDSurfaceDescription;
private DirectDrawSurface7 m_objBitmapSurface;
protected RECT m_objSizeRECT;
The first two constants are used to indicate whether the object will be a sprite or a standard bitmap(a sprite is a non-square bitmap). Next m_objDDSurfaceDescription is a surface description object and is used to set the properties of the Surface object, m_objBitmapSurface is the DirectDraw surface object that will contain the bitmap. The remaining variable is a RECT which is an object containing rectangular information, it represents the rectangular dimensions of the surface and is used when the surface is copied to the backbuffer.
The surface description is configured in the function InitialiseSurfaceDescription() which is listed below, it contain two arguments which indicate the width and the height of the bitmap:-
InitialiseSurfaceDescription(int intBitmapWidth, int intBitmapHeight)
{
m_objDDSurfaceDescription.lFlags = CONST_DDSURFACEDESCFLAGS.DDSD_CAPS |
CONST_DDSURFACEDESCFLAGS.DDSD_WIDTH |
ONST_DDSURFACEDESCFLAGS.DDSD_HEIGHT;
m_objDDSurfaceDescription.ddsCaps.lCaps =
ONST_DDSURFACECAPSFLAGS.DDSCAPS_OFFSCREENPLAIN;
m_objDDSurfaceDescription.lWidth = intBitmapWidth;
m_objDDSurfaceDescription.lHeight = intBitmapHeight;
m_objSizeRECT.Bottom = intBitmapHeight;
m_objSizeRECT.Right = intBitmapWidth;
}