2D Game Development with Managed DirectX: Tutorial 01 - Setting up DirectX
The first thing you need to prepare when creating a 2D game in Managed DirectX is the device. The Direct3D device is what
you will be using to render all of your objects to the screen. It manages all the video card and display junk that you
don't need to concern yourself with unless you're the ZMan
and are really into that sort of thing.
For 2D games we can ignore most of that "extra" junk and just set the device up with the basics. In this tutorial, we'll
create the main game loop, the device class and a game engine class. The results, you will get to see a form launched with
the Direct3D device rendering a black background on it.
::Create the Solution::
To start the ball rolling, open up Visual Basic Express (free from Microsoft) and create a new project using the "Windows
Application" template. Give it a snazzy name like "MyCoolGame" or "TheNextWorldOfWarcraft". I stuck with some very
unoriginal and called it "SettingUpDirectX".
Next, because we really don't need it (well, we kind of do, but I don't like seeing it in the project list) remove the
"Form1.vb" object from the solution. You'll have an error in the error list as a result ('Form1' is not a member of
'SettingUpDirectX') but don't worry, we will get that error cleaned up before the tutorial is complete.
::Add the DirectX References::
Well, since we are making a solution that is going to work with DirectX, we had better give it the power to do that.
Double-click on the "My Project" object in the solution explorer. And add references to the DirectX dll we will be using
in this tutorial.
Referenced Dlls:
Microsoft.DirectX
Microsoft.DirectX.Direct3D
I'm using the 1.0.1901.0 version of the Managed DirectX dlls. There isn't any particular reason that I'm using those, just
that those are the one's I'm familiar with and I got tired of upgrading and having to redo my projects to work with the
newer Managed DirectX dll releases. I've provided the versions of the dlls that I use so that you can get the same results
that I do and later you can try to upgrade to a newer release version if that sort of thing makes your heart go pitter
pat.
::Naming Standards::
Before we begin any coding, I want to spend a little time talking about naming standard. First, if you don't currently use
any naming standards start. In my tutorials I will be attempting to use the following naming standards.
Class level objects are named using camel case and are prefixed with an "m".
Example:
Private mRenderTarget as Form
Properties for objects are named using camel case and are typically prefixed with "my"
Example:
Public Property myRenderTarget as Form
The exception for this is for Properties that wrap the more typical objects like integers, strings, etc.
Example:
Public Property Height as
Integer
Procedure level objects are named using camel case and are prefixed with an "a"
Example:
Objects being passed into a procedure are named using camel case and are prefixed with "the" or "is"
Example:
Public Sub CreateDevice(ByVal isWindowed as Boolean, ByVal theHeight as Integer, ByVal theWidth as Integer)
I've been using this naming standard for quite a while now so it's just what I'm used to. I like it because it's easy to
determine the scope of an object and it's fairly simplistic. Do I care if you use the same standard? No, but I would
stress that you come up with some kind of written standard and use it constantly and consistently no matter the size or
importance of the project.
Ok, now that we have that behind us, let's write some code.
::Creating the DirectX3D Device Class::
Now that we the DirectX dll references made and I've shared with you my naming standard we will add our first "Class"
object to the solution. I named my class "DX3DEngine" because it's an object that is going to manage the DirectX 3D device
object and using the word "Engine" just makes it sound really cool and all powerful.
We will begin to add some substance to our DX3DEngine (see doesn't that sound awe inspiring?) but I'm not sure what order
makes the most sense in creating the class so if it seems that I'm jumping around a bit or you're not sure why I added
something in particular order, it's because I am and I don't either.
First start with creating the class level objects for the DX3DEngine
Class level objects and properties in DX3DEngine
'The present parameters for the DX3D Device object
Private mPresentParams As
Microsoft.DirectX.Direct3D.PresentParameters = Nothing
'Indicates whether the device has been lost
Private mLostDevice As Boolean = False
'The D3DDevice object
Private mDirect3DDevice As Microsoft.DirectX.Direct3D.Device
= Nothing
Public ReadOnly Property myD3DDevice() As Microsoft.DirectX.Direct3D.Device
Get
Return mDirect3DDevice
End Get
End Property
'The Control that is connected to the device and used as the render
target
Private mRenderTarget As System.Windows.Forms.Form = Nothing
Public ReadOnly Property myRenderTarget() As System.Windows.Forms.Form
Get
Return mRenderTarget
End Get
End Property
'The Heigth the device backbuffer
Private mDeviceHeight As Integer =
1024
Public ReadOnly Property DeviceHeight() As Integer
Get
Return mDeviceHeight
End Get
End Property
'The Width for the device backbuffer
Private mDeviceWidth As Integer =
768
Public ReadOnly Property DeviceWidth() As Integer
Get
Return mDeviceWidth
End Get
End Property
'The color the device will be "cleared" with
Private mDeviceColor As Color = Color.Black
Public Property DeviceColor() As Color
Get
Return mDeviceColor
End Get
Set(ByVal value As
Color)
mDeviceColor = value
End Set
End Property
Here we've created the objects for the DirectX3D device, an object to store the render target and a couple of other
objects and properties dealing with the device.
Next we'll being making something procedures to initialize and manipulate these objects.
First we'll make the constructors for the class.
Class constructors for DX3DEngine
'Description: The class constructor. Setup the device to run in either windowed mode
or full screen
' mode with the default class values for height and width
Public Sub New(ByVal theRenderTarget As System.Windows.Forms.Form, ByVal isWindowed As Boolean)
'If the device is to be run in FullScreen mode then the default heights and widths will be
used
Call Initialize(theRenderTarget, isWindowed, mDeviceHeight,
mDeviceWidth)
End Sub
'Description: The class constructor. Setup the device to run in full screen mode witht he given
height and width
Public Sub New(ByVal theRenderTarget As System.Windows.Forms.Form, ByVal theHeight As Integer, ByVal theWidth As Integer)
'The device will be setup to run in fullscreen mode with the heights and widths passed in
to the constructor
Call Initialize(theRenderTarget, False,
theHeight, theWidth)
End Sub
Here you can see the Dx3DEngine is created by passing in a form as a render target and indicating whether the device is
going to run in Windowed mode or FullScreen (with the option of using the default height and width for Fullscreen mode or
passing in a set height and width)
Since we are calling an Initialize procedure now might be an excellent time to created one and it might be a great idea
if it looked something like this.
Initialize procedure for DX3DEngine
'Description: Initialize the class objects
Private Sub Initialize(ByVal
theRenderTarget As System.Windows.Forms.Form, ByVal isWindowed As Boolean, ByVal theHeight As Integer, ByVal theWidth As Integer)
'Store the rendering target
mRenderTarget = theRenderTarget
'Store the device backbuffer height and width
mDeviceHeight = theHeight
mDeviceWidth = theWidth
'Create the DX3D Device object
Call CreateDevice(isWindowed, theHeight, theWidth)
End Sub
The Initialize procedure stores the render target, the height and the width and then creates device by calling the
aptly named procedure "CreateDevice". The "CreateDevice" procedure is where the Direct3D device is created and setup and
is the real meat and potatoes of the class so why don't we mosey over and take a look?
CreateDevice in DX3DEngine
'Description: Create the DX3D Device object
Private Sub CreateDevice(ByVal
isWindowed As Boolean, ByVal theHeight As Integer, ByVal theWidth As Integer)
'Use the system's default adapter
Dim aAdapter As Integer =
Microsoft.DirectX.Direct3D.Manager.Adapters.Default.Adapter
'Setup the presentation parameters
mPresentParams = New Microsoft.DirectX.Direct3D.PresentParameters
'Determin whether the device will be run in fullscreen or windowed
mode
mPresentParams.Windowed = isWindowed
If mPresentParams.Windowed = False Then
'The device will be fun in fullscreen mode so additional presentation parameters need
to be set
mPresentParams.Windowed = False
mPresentParams.BackBufferCount = 1
mPresentParams.BackBufferWidth = theWidth
mPresentParams.BackBufferHeight = theHeight
mPresentParams.BackBufferFormat = Microsoft.DirectX.Direct3D.Manager.Adapters.Default.CurrentDisplayMode.Format
mRenderTarget.Cursor.Dispose()
End If
mPresentParams.SwapEffect = Microsoft.DirectX.Direct3D.SwapEffect.Discard
mPresentParams.PresentationInterval = Microsoft.DirectX.Direct3D.PresentInterval.Immediate
'Creation flags
Dim aFlags As
Microsoft.DirectX.Direct3D.CreateFlags
'See if we can use hardware vertex processing
Dim aCaps As Microsoft.DirectX.Direct3D.Caps =
Microsoft.DirectX.Direct3D.Manager.GetDeviceCaps(aAdapter, Microsoft.DirectX.Direct3D.DeviceType.Hardware)
If aCaps.DeviceCaps.SupportsHardwareTransformAndLight = True Then
aFlags = Microsoft.DirectX.Direct3D.CreateFlags.HardwareVertexProcessing
Else
aFlags = Microsoft.DirectX.Direct3D.CreateFlags.SoftwareVertexProcessing
End If
'Check to see if pure devices are supported
If aCaps.DeviceCaps.SupportsPureDevice = True Then
aFlags = aFlags Or
Microsoft.DirectX.Direct3D.CreateFlags.PureDevice
End If
'All the data has been collected, create the device
mDirect3DDevice = New Microsoft.DirectX.Direct3D.Device(aAdapter,
Microsoft.DirectX.Direct3D.DeviceType.Hardware, myRenderTarget.Handle, aFlags, mPresentParams)
'Hook up the device resizing event
AddHandler myD3DDevice.DeviceResizing, New
System.ComponentModel.CancelEventHandler(AddressOf OnDeviceResizing)
End Sub
I tired to be liberal with my comments in the "CreateDevice" procedure to explain what is going on but I can sum it up
like this, basically the parameters for the Direct3D device are discovered and/or set and then the device is created.
The last little piece of the procedure adds an event to handle resizing, so that's the next procedure we'll cover
OnDeviceResizing procedure for DX3DEngine
'Description: Handles device resizing
Private Sub OnDeviceResizing(ByVal
sender As Object, ByVal e As System.ComponentModel.CancelEventArgs)
e.Cancel = True
End Sub
Basically all this procedure does is handle the resizing event when it fires, so lets move on to some more interesting
code that handles the rendering.
When you want to start rendering objects to the screen, you typically start with a call to "BeginRender" and our engine
provides just suce a procedure
BeginRender procedure for DX3DEngine
'Description: Sets up the device for rendering. Clears the device and begins the
scene
Public Sub BeginRender()
If DeviceAvailable() = True Then
myD3DDevice.Clear(Microsoft.DirectX.Direct3D.ClearFlags.Target, DeviceColor, 1.0F, 0)
myD3DDevice.BeginScene()
End If
End Sub
The "BeginRender" procedure first checks to make sure that the device is avaialble (we'll be making that proceudre in jsut
a few minutes) and then Clears the device to the current color of the DeviceColor property we setup when we first started
making the class. Then it tells the device to begin rendering the scene.
If we are going to begin rendering something, then I guess it makes sense to end rendering at some point as well and lo
and behold that's what the next procedure we're going to look at does.
EndRender procedure for DX3DEngine
'Description: Ends the rendering for the device. Ends the scene and presents it tot
he render target
Public Sub EndRender()
If DeviceAvailable() = True Then
myD3DDevice.EndScene()
Call Present()
End If
End Sub
Again, before the attempting anything a check is made to make sure the device is available (and again, I promise we'll get
to that). Then the device is told to end the scene. The next step is to present the scene to the render target since all
rendering is now complete. That's what the "Present" procedure does and that is what we will look at next.
Present procedure for DX3DEngine
'Description: Presents the rendered scene to the connected render
target
Private Sub Present()
If DeviceAvailable() = True Then
Try
'Show the back buffer on screen
myD3DDevice.Present()
Catch ex As
Microsoft.DirectX.Direct3D.DeviceLostException
mLostDevice = True
End Try
End If
End Sub
What do you know? Before an attempt is made to present the scene, we check yet again to see if the device is available
first (we'll do that one next, I promise). Then we attempt to present the rendered screen to the render target (in our
case a form). We wrap this in a Try..Catch.. because there's a chance that the device has been lost and we want to catch
that error from bubbling up to the user. Instead we'll set an internal variable indicating the device is lost and we will
attempt to recover it. Where do we do that? Why in the DeviceAvailable function (see I told you we'd get to it, you really
need to learn some patience!)
DeviceAvailable procedure for DX3DEngine
'Description: Determines if the device is available (not lost or
disposed)
Private Function DeviceAvailable() As Boolean
DeviceAvailable = False
'The device is not available if it has been disposed
If myD3DDevice.Disposed = True Then
Return False
End If
'The device is not available if it has been lost
If mLostDevice = True Then
Call Recover()
Return False
End If
Return True
End Function
In the "DeviceAvailable" procedure we determine if the device is available. Obviously if the device has been disposed then
it's not available. Then we check to see if the lost device flag has been set, if so then we will make an attempt to
recover it. If the device hasn't been disposed and it hasn't been lost, then it's avaialable.
Now we'll take a look at our attempt to recover a lost device.
Recover procedure for DX3DEngine
'Decription: Attempt to recover the device when it has been lost
Private Sub Recover()
Dim aResult As Integer
myD3DDevice.CheckCooperativeLevel(aResult)
Select Case CType(aResult,
Microsoft.DirectX.Direct3D.ResultCode)
Case Microsoft.DirectX.Direct3D.ResultCode.DeviceLost
mLostDevice = True
Case Microsoft.DirectX.Direct3D.ResultCode.Success
mLostDevice = False
Case Microsoft.DirectX.Direct3D.ResultCode.DeviceNotReset
'The device isn't lost anymore, but needs to be reset
Try
myD3DDevice.Reset(mPresentParams)
mLostDevice = False
Catch ex As
Microsoft.DirectX.Direct3D.DeviceLostException
mLostDevice = True
End Try
End Select
End Sub
Basically, you take the device and call it's CheckCooperativeLevel procedure. Depending on what result you get, you have
either recovered the device, it's still lost or you have a chance of resetting the device to recover it.
Finally, we will write the final procedures of our DX3DEngine and they consist of the destruction and disposal of the
objects created in the class. It's always a good practice to remember to clean up the mess you made.
Finalize and Dispose procedures for DX3DEngine
'Description: Class destructor, destroy the objects
Protected Overrides Sub
Finalize()
Call Dispose()
MyBase.Finalize()
End Sub
'Description: Dispose of the objects created in the class
Private Sub Dispose()
'Destroy the Present Parameters object
mPresentParams = Nothing
'Destroyt he Direct3D Device object
If Not (mDirect3DDevice Is Nothing) Then
mDirect3DDevice.Dispose()
End If
mDirect3DDevice = Nothing
End Sub
There really isn't much to these. When the class is being destroyed, it first calls the Dispose procedure which destroys
each of the objects created in this class.
Well, that's the DX3DEngine in it's entirety and we're just a hop skip and a jump from acutally being able to use the
engine and see it's amazing results
::Creating the Game Engine class::
Add a new class to your solution and call it "GameEngine" because it sounds pretty cool and we already know that
adding an "engine" to any class name makes it uber awesome.
To be honest for this simple tutorial, there really isn't a need to make all the seperations I'm making but I do it so
you can start to think about each of the pieces in a seperate fashion.
The game engine's code is fairly simplistic and we'll look at it all at once and then discuss it.
GameEngine Class code
Private mGraphics As DX3DEngine
'Description: The class constructor. Create the objects for the class
Public Sub New(ByVal theRenderTarget As Control)
mGraphics = New DX3DEngine(theRenderTarget, True)
End Sub
'Description: Setup the device for rendering
Public Sub Render()
mGraphics.BeginRender()
mGraphics.EndRender()
End Sub
'Description: The class destructor. Destroy the objects for the class
Protected Overrides Sub
Finalize()
Call Dispose()
MyBase.Finalize()
End Sub
'Description: Dispose of any object created in the class
Private Sub Dispose()
mGraphics = Nothing
End Sub
We start by adding a class level object for the DX3DEngine we just made. Then the contructor for the GameEngine class
receives a render target and creates an instance of the DX3DEngine object.
The Render procedure just begins and ends the rendering and then the class destructor disposes of the DX3DEngine
object.
Liks I said, not really much there and it might not be apparent at this point why we needed a GameEngine, but trust me,
this class will begin to have great importance and you will be glad for the seperation.
Well, we are almost there, only one more class to create and you'll be able to see the final results. With this last class
we'll finally clean up that pesky error that has been haunting you from the start of this tutorial as well.
::Creating Main::
Add a new class to your solution and call it "Main". Main will only contain one procedure for now but it basicall gets
your game running and keeps it running and then shuts it down when appropriate so that one function has quite a lot to do.
Main Class code
'Description: The main entry point for the project. Sets up the game engine and
contains the game loop
Public Shared Sub Main()
'Create a form to attach the DirectX device to
Dim aMainForm As New
Windows.Forms.Form
'Create the Game engine
Dim aGameEngine As New GameEngine(aMainForm)
'Display the form
aMainForm.Show()
'Continue the game loop while the form is still running
Do While aMainForm.IsDisposed = False
'Give the computer a chance to process some other things
Application.DoEvents()
'Render the scene
aGameEngine.Render()
Loop
'Destroy the Game Engine
aGameEngine = Nothing
'Destroy the Main form
aMainForm = Nothing
End Sub
In Main you can see we create a new form, that's where we will be rendering too with our device. Then we create a new
GameEngine object and give it the form as the rendering target. Next we show the form and begin the game loop. Finally
after the form has been disposed we clean up our objects.
But wait, we still have an error! Well to take care of the error we need to double-click on the "My Project" in the
solution explorer and on the "Application" tab uncheck the "Enable application framework" checkbox. Now select Main as
your startup object and the error that has been haunting our dreams since the beginning of this tutorial will finally go
away.
Well, that's it you can now run the project and see a wonderfully rendered black background on a form. I know it's not
a lot, but it's a very good base to start with and really lays the groundwork for a future 2D game.
I would suggest you play with clearing the device with different colors and seeing the behavior when you change to
FullScreen mode and play with the buffer heights and widths while in that mode. Just note, when changing to full screen
mode, you won't have a way of easily closing the application. You will have to Alt-Tab back to the projcect and stop the
execution there. We'll be covering a tutorial on Keyboard input at a later point so that we can easily close it when in
full screen mode and oh, I don'tk now maybe move some things around or something.
Hopefully that can keep you entertained long enough for me to finish another tutorial.
The source code for the tutorial is available here.
In closing, I've never done tutorials before so I welcome any and all feedback from layout to styling to whatever. I
really want these tutorials to be instructional and motivational (as in, Wow! Managed DirectX doesn't look as intimidating
as I thought, I think I could do that!), so please help me out if you have suggestions or advice for improving them. If
I'm doing a great job, let me know that too, just a simple comment can be really motivating and are always appreciated.