From Soup To Nuts

An Odyssey Through The Land Of Geekdom

  Home  |   Contact  |   Syndication    |   Login
  421 Posts | 7 Stories | 175 Comments | 1488 Trackbacks

News

Search this blog

Article Categories

Archives

Post Categories

About Me

Friends

Projects

Z-Misc

Well, lets dive right in to the deep end, shall we? In this article we will be initializing DirectX. Actually, we will be initializing our Direct3D device. The initialization of our Direct3D device will take place entirely in a new Renderer class. This article will also be introducing our engine class, BattleEngine.

At this point, we still have a fairly simple design. When this article is done, our general class architecture will look as follows:

One of the first things you might notice is the Instance attribute. Since we only want one instance of the BattleEngine to be created at any time, we will be using the Singleton Pattern. The Singleton Pattern is a really good way to ensure that only one instance of a class exists at any time. As you can see, we only really have one subsystem at the moment: the Renderer. The Renderer class is our class that will encapsulate most access to our rendering system. We won’t be totally encapsulating access to our Render most for learning experiences. This article should teach us how to develop a basic 2d game. We will not be worrying about building a cross-platform game, and hence, will not worry about creating a “virtual system” to hide away all DirectX access (making it easier to plug in a different system like OpenGL).

Let’s go ahead and dive right into the code. The renderer is where we will actually initialize our Direct3d device. We will plan on rendering the game in windowed mode when we are debugging to make debugging easier. This is because debugging in Full Screen mode can be extremely difficult unless you are running a multi-monitor solution. When in Debug mode, it is easier to set breakpoints and step through the code while still allowing the program to be run after the debugging is finished. We will only run in Full Screen mode when we are running a Release build.

The constructor of the Renderer class is where we will initialize our device. The first thing that we want to do is to create our present parameters depending on whether we are in debug or release mode.

private System.Windows.Forms.Control _targetControl;

// no property accessor

 

private Device _device;

public Device Device

{

       get { return _device; }

}

 

public Renderer(System.Windows.Forms.Control targetControl)

{

       _targetControl = targetControl;

                    

       // presentation parameters will be different depending on whether we are

       // in Debug mode or not (Windowed in Debug, Full Screen in Release)

       PresentParameters presentParams = new PresentParameters();

 

#if DEBUG

       presentParams.Windowed = true;   

#else

       presentParams.Windowed = false;

       presentParams.DeviceWindow = this.targetControl;

       presentParams.BackBufferFormat = Format.X8R8G8B8;

       presentParams.BackBufferHeight = this.targetControl.Size.Height;

       presentParams.BackBufferWidth = this.targetControl.Size.Width;

       presentParams.PresentationInterval = PresentInterval.Default;

#endif

      

       presentParams.SwapEffect = SwapEffect.Discard;

       presentParams.AutoDepthStencilFormat = DepthFormat.D16;

       presentParams.EnableAutoDepthStencil = true;

 

The next thing that we need to do is to create our creation flags. For some of the creation flags, we will need to check to make sure that our device supports the feature we will be creating the device with. This is done by using the various Support* attributes in the device’s DeviceCaps structure.

 

// store our default adapter

       int adapterOrdinal = Manager.Adapters.Default.Adapter;

 

       // get our device capabilities so we can check

       Caps caps = Manager.GetDeviceCaps(adapterOrdinal, DeviceType.Hardware);

       CreateFlags createFlags;

 

       if (caps.DeviceCaps.SupportsHardwareTransformAndLight)

              createFlags = CreateFlags.HardwareVertexProcessing;

       else

              createFlags = CreateFlags.SoftwareVertexProcessing;

 

       if (caps.DeviceCaps.SupportsPureDevice)

              createFlags |= CreateFlags.PureDevice;

 

Now, there is only one thing left for us to do. Now that we have gathered all information we need, we will create our device. One thing to notice is that we are creating the device as well as using the DeviceReset event. Trapping this event will allow us to automatically re-create our various device-dependant elements.

 

// create our device

       _device = new Device(adapterOrdinal, DeviceType.Hardware, _targetControl,

             createFlags, presentParams);

       _device.DeviceReset += new EventHandler(this.OnDeviceReset);

       OnDeviceReset(_device, null);

}

 

Before moving on the OnDeviceReset method, I would like to explain how we are setting up the camera. Since this is a two dimensional game, I want to create a static camera that will be pointing directly at the middle of the positive quadrant. So, if we have the following setup of a two dimensional grid:

 

                                  +y

|//////////////////

                                    |//////////////////

                                    |//////////////////

                                    |//////////////////

                                    |//////////////////

       -x ------------------  z -----------------  +x

                                    |

                                    |

                                    |

                                    |

                                    |

                                   -y                                             

 

The shaded quadrant is the quadrant that out game will take place in. By dealing with all positive coordinates, and having the center of the camera (and the screen) directly in the center of the positive quadrant, our code should be significantly more intuitive to read. With that explained, let’s look at the creation of our various device-dependant elements:

 

public void OnDeviceReset(object sender, EventArgs e)

{

       Device newDevice = (Device)sender;

 

       // set device states

       newDevice.RenderState.Lighting = false;

                    

       // get camera vectors

       float width = (float)_targetControl.Size.Width;

       float height = (float)_targetControl.Size.Height;

       float centerX = width / 2.0f;

       float centerY = height / 2.0f;

       Vector3 cameraPosition = new Vector3(centerX, centerY, -5.0f);

       Vector3 cameraTarget = new Vector3(centerX, centerY, 0.0f);

 

       // create our transforms

       newDevice.Transform.View = Matrix.LookAtLH(cameraPosition, cameraTarget,

             new Vector3(0.0f, 1.0f, 0.0f));

       newDevice.Transform.Projection = Matrix.OrthoLH(width, height, 1.0f, 10.0f);

}

 

As of now, there are only two more methods left to define in the Renderer class: BeginScene( ) and EndScene( ). Both methods are relatively self explanatory.   

 

public void BeginScene()

{

       _device.Clear(ClearFlags.Target | ClearFlags.ZBuffer, System.Drawing.Color.Black, 1.0f, 0);

       _device.BeginScene();

}

 

public void EndScene()

{

       _device.EndScene();

       _device.Present();

}

 

Now that our Renderer is initialized and setup, we need to code up our BattleEngine class. The first thing we need to is the Singleton Pattern implementation mentioned above. Along with the Singleton implementation, we also have our various attributes and properties used to access our subsystems and various engine data.

 

public sealed class BattleEngine

{

       // Singleton implementation

       private BattleEngine() { }

       public static readonly BattleEngine Instance = new BattleEngine();

 

       // Attributes/Properties

       private System.Windows.Forms.Control _targetControl;

       public System.Windows.Forms.Control TargetControl

       {

              get { return _targetControl; }

       }

 

       private Renderer _renderer;

       public Renderer Renderer

       {

              get { return _renderer; }

       }

 

Next we need to initialize our engine. Initializing our engine at this point will mostly consist of initializing our various subsystems (currently only the Renderer).

 

       public void Initialize(System.Windows.Forms.Control targetControl)

       {

              _targetControl = targetControl;

 

              // Initialize Subsystems

              _renderer = new Renderer(_targetControl);

       }

 

That was pretty easy I must say! The only thing left in the current engine is the Frame function. All we currently do in the Frame function is to call the Render function (since we have no subsystems to update). The Render function just cals the BeginScene( ) and EndScene( ) functions for now since we don’t have any game objects to render.

 

       public void Frame()

       {

              // TODO: Update Subsystems Here

 

              Render();

       }

 

       private void Render()

       {

              _renderer.BeginScene();

              // render game objects here

              _renderer.EndScene();

       }

}

 

That is it for our BattleEngine class for now. All that is left for this article is to hook our BattleForm into our BattleEngine. The only two hooks that are needed in the form is to initialize our engine and to update our game in the OnPaint( ) method. Below are the new implementations of the functions that we ironed out in the last article for the BattleForm class:

 

public void Initialize()

{

       BattleEngine.Instance.Initialize(this);

}

 

protected override void OnPaint(PaintEventArgs e)

{

       BattleEngine.Instance.Frame();

      

       // IMPORTANT: Make sure that we are called again as soon as we are drawn

       this.Invalidate();

}

 

Before we run our application, let’s go ahead and add an ability to exit the application. We will be doing that by simply overriding the OnKeyDown event on the BattleForm class and checking to see if the user has pressed “Escape”.

 

protected override void OnKeyDown(KeyEventArgs e)

{

       base.OnKeyDown(e);

 

       if ((int)(byte)e.KeyCode == (int)Keys.Escape)

              this.Close();

}

 

Well, that’s it for this article folks. Stay tuned for next time where we will create our input system that will drive our game. ‘Til next time :~).

 

 

posted on Monday, February 16, 2004 5:55 PM

Feedback

# re: The One Where DirectX Gets Initialized 2/22/2004 4:24 AM d.battenfeld@re-think.de
Looking forward to the next part.

Never saw an implementation of the singelton pattern like yours, nice ... anyway nice coding style

greetings from Germany

Dennis



# re: The One Where DirectX Gets Initialized 2/22/2004 11:11 AM Jason Olson
Thanks Dennis! As for the Singleton implementation, I was using the old classic C++ way with a GetInstance( ) method. And then when I actually read my own link to some Singleton Design Pattern information from Microsoft, I saw this implementation. I thought it was rather clever and makes quite a bit of sense if you are familiar with how the runtime initializes static variables. Not only that, but it definitely seems more streamlined and elegant this way.

# re: The One Where DirectX Gets Initialized 3/14/2004 3:11 AM Fidelio
Hi,
I'm curious why you would use
private Renderer _renderer;
public Renderer Renderer
{
get { return _renderer; }
}

I suspect it's C++ style for readonly. What if you just did
public readonly Renderer Renderer;


# re: The One Where DirectX Gets Initialized 3/14/2004 4:07 AM Owain Cleaver
Fedelio, using accessor methods/properties for member variables can make it easier to debug. Also it's just good practice for 100s of other reasons.

# re: The One Where DirectX Gets Initialized 3/14/2004 11:06 AM Jason Olson
Fidelio,

To me, it is all about one of the tenets of object-oriented programming: encapsulation. More than making it easier to debug, it seperates a class's interface from its implementation. It's a bad idea anytime outside classes know intimately about how another class is implemented. Hence the importance of encapsulation.

One can also think of encapsulation as to be used in heirarchies also. It's not a very good "code smell" when a child knows too much about a parent's private parts. After all, that is not accepted well in society either ;-).

# re: The One Where DirectX Gets Initialized 3/16/2004 3:42 PM Spong
Uh, respectfully, that depends where you grew up.

# re: The One Where DirectX Gets Initialized 5/20/2004 7:15 PM Xian
I appreciate the 2d setup information. It seems that documentation on Managed DirectX is few and far between. I have been working in 2d for a while, but using DirectDraw rather than Direct3D and I've really seen the loss of functionality as a result. I'm looking forward to the UI portion and perhaps some information on using textured quads for tilemaps/tilesets.

Thanks.

# re: The One Where DirectX Gets Initialized 8/12/2004 1:44 PM Jerry Wu
Thanks Dennis!

# re: The One Where DirectX Gets Initialized 9/12/2004 5:36 AM Arik
This looks nice, but...

how does the actual drawing done??
I've been searching all over the internet for it and can't find an example anywhere....

# re: The One Where DirectX Gets Initialized 10/23/2004 12:52 PM Mike Davey
Great code, and very helpful. I just have two quibbles:

1) Regarding Properties that merely return member variables: I generally feel this is psuedo-encapsulation. To use your disturbing private-parts analogy, it's the equivalent of see-through pants.

2)If your creating a singleton in C#, why bother with "Instance"? Why not simply make all your relevant interfaces static?

# re: The One Where DirectX Gets Initialized 10/23/2004 4:10 PM Jason Olson
Mike,

1) I believe properties that merely return member variables are more than fine. They are not pseudo-encapsulation, they are full encapsulation. You have to think about it as an outside user would see it. To the outside user, the class should be like a black box. Whether you're simply returning a member variable doesn't matter because the outside user (in this case, other code) has no idea *how* the property is implemented. External code has no clue that it just returns a member variable. If I needed to change the property to return a calculated value, clients using the code would not have to change. This is the whole point of true encapsulation.

As for the comparison to see-through pants, the analogy does not work. Like stated above, external code does not know that the property simply returns a member variable. If the code was actually &