Using Managed DirectX to Write a Game

Writing the Game

You can jump right into Visual Studio and get the project started. Create a new C# Windows Application project called "Dodger". The default name of the form that has been created for you is Form1. Replace each instance of Form1 with DodgerGame, which is the name of the class that the code in this chapter will represent. You'll want to add references to the three Managed DirectX assemblies you've been using in your projects thus far, and include a using statement for them in your code file. You should set this project up much like you did most of the others. You will have a private Direct3D device variable and should modify your constructor as follows:

public DodgerGame()
{
  this.Size = new Size(800,600);
  this.Text = "Dodger Game";
  this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.Opaque, true);
}

This will set the window size to 800x600 (which should be plenty good enough), and establish the window title and the style, so the rendering code will work normally. Then you should modify the entry point for the application by replacing the main method with the one found in Listing 6.1.

Listing 6.1 Main Entry Point for Your Game

static void Main()
{
  using (DodgerGame frm = new DodgerGame())
  {
    // Show our form and initialize our graphics engine
    frm.Show();
    frm.InitializeGraphics();
    Application.Run(frm);
  }
}

This should be familiar; it's essentially the same code you've used to start all of the examples thus far. You create the windows form, show it, initialize the graphics engine, and then run the form, and thus the application. Inside the InitializeGraphics function, though, is where you will start to make some changes. Add the method found in Listing 6.2 to your application.

Listing 6.2 Initializing Your Graphics Components

/// <summary>
/// We will initialize our graphics device here
/// </summary>
public void InitializeGraphics()
{
  // Set our presentation parameters
  PresentParameters presentParams = new PresentParameters();
  presentParams.Windowed = true;
  presentParams.SwapEffect = SwapEffect.Discard;
  presentParams.AutoDepthStencilFormat = DepthFormat.D16;
  presentParams.EnableAutoDepthStencil = true;
 
  // Store the default adapter
  int adapterOrdinal = Manager.Adapters.Default.Adapter;
  CreateFlags flags = CreateFlags.SoftwareVertexProcessing;
  // Check to see if we can use a pure hardware device
  Caps caps = Manager.GetDeviceCaps(adapterOrdinal, DeviceType.Hardware);
  // Do we support hardware vertex processing?
  if (caps.DeviceCaps.SupportsHardwareTransformAndLight)
    // Replace the software vertex processing
    flags = CreateFlags.HardwareVertexProcessing;
 
  // Do we support a pure device?
  if (caps.DeviceCaps.SupportsPureDevice)
    flags |= CreateFlags.PureDevice;
  // Create our device
  device = new Device(adapterOrdinal, DeviceType.Hardware, this, flags, presentParams);
  // Hook the device reset event
  device.DeviceReset += new EventHandler(this.OnDeviceReset);
  this.OnDeviceReset(device, null);
}

At first you create the presentation parameters structure much like you've done before, ensuring that you have a depth buffer. However, what's next is new. First, you store the adapter's ordinal, which you get from the default adapter. The creation flags that will be used when creating the device are also stored, and you default to software vertex processing much like all of our examples have used thus far.

However, most modern-day graphics cards can support vertex processing on the actual graphics hardware. Why would you want to spend valuable CPU time doing something the graphics card can do much faster anyway? The easiest answer is that you don't; however, you don't currently know whether or not the adapter being used supports this feature. This brings us to the next section of code.

You will now get and store the capabilities ( Caps for short) of the device before you actually create it, so you can determine the flags you want to use for creating the device. Since you will be creating a hardware device, these Caps are the only set you will store. You may remember back from Chapter 2, "Choosing the Correct Device," when we displayed the entire list of capabilities of the device, that the structure is huge and broken up into many different sections. The section of interest right now is the DeviceCaps section, which stores the Caps specific to a driver.

When you want to check to see whether a particular feature is supported, you can simply check the Boolean value that maps to this feature: If it is true, the feature is supported; otherwise, it is not. You first check to see whether hardware transform and lighting is supported on this device. If it is, you can create the device with the hardware vertex processing flag, so we assign this to our stored flags instead. You then check to see whether you can create a pure device (which can only be created if hardware vertex processing is enabled), and if you can, you do a bitwise OR of the flags, adding this feature as well. The most efficient type of device you can create is a pure hardware device, so if these options are available to you, you should use them.

You then create your device using the flags you've stored, so depending on your graphics cards capabilities, you may have a pure hardware device, or some other variation. If you remember when you were using our vertex buffers and needed to hook the created event for when the device was reset, there is a similar situation here. Whenever the device is reset, you will want to set all of the default state of the device. You should hook the device's reset event, and then call it the initial time to set the state. The event handler method can be found in Listing 6.3. Add this method to your application.

Listing 6.3 Setting Default Device State

private void OnDeviceReset(object sender, EventArgs e)
{
  device.Transform.Projection = Matrix.PerspectiveFovLH((float)Math.PI / 4,
    this.Width / this.Height, 1.0f, 1000.0f);
  device.Transform.View = Matrix.LookAtLH(new Vector3(0.0f, 9.5f, 17.0f), new Vector3(),
    new Vector3(0,1,0));
  // Do we have enough support for lights?
  if ((device.DeviceCaps.VertexProcessingCaps.SupportsDirectionalLights) &&
    ((unit)device.DeviceCaps.MaxActiveLights > 1))
  {
    // First light
    device.Lights[0].Type = LightType.Directional;
    device.Lights[0].Diffuse = Color.White;
    device.Lights[0].Direction = new Vector3(1, -1, -1);
    device.Lights[0].Commit();
    device.Lights[0].Enabled = true;
    // Second light
    device.Lights[1].Type = LightType.Directional;
    device.Lights[1].Diffuse = Color.White;
    device.Lights[1].Direction = new Vector3(-1, 1, -1);
    device.Lights[1].Commit();
    device.Lights[1].Enabled = true;
  }
  else
  {
    // Hmm.. no light support, let's just use
    // ambient light
    device.RenderState.Ambient = Color.White;
  }
}

Once again, the beginning of this function is pretty similar to things you've done before. You set up the camera by setting the view and projection transforms on your device. For this game, there will be a static non-moving camera, so this will only need to be done once after each device reset (all device-specific state is lost during a reset).

Using ambient light isn't preferred since we've already seen that the ambient light isn't overly realistic, so a directional light would be better. However, you can't be sure if the device actually supports these lights. Once the device has been created, you will no longer need to use the Caps structure that was stored before, since the device maintains this information for you. If the device can support directional lights, and can support more than one of them, you will use them; otherwise, you can default to an ambient light. It may not be all that realistic, but it's better than an all black scene.

Finally, you need to override the OnPaint function to enable your rendering, much like you've done before. You don't need to do much yet; just get something rendered. Add the following function:

protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
{
  device.Clear(ClearFlags.Target | ClearFlags.ZBuffer, Color.Black, 1.0f, 0);
  device.BeginScene();
  device.EndScene();
  device.Present();
  this.Invalidate();
}

You might also like...

Comments

Contribute

Why not write for us? Or you could submit an event or a user group in your area. Alternatively just tell us what you think!

Our tools

We've got automatic conversion tools to convert C# to VB.NET, VB.NET to C#. Also you can compress javascript and compress css and generate sql connection strings.

“The trouble with programmers is that you can never tell what a programmer is doing until it's too late.” - Seymour Cray