Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / multimedia / DirectX

TexasQuest v.2.0

5.00/5 (8 votes)
22 Mar 2013CPOL8 min read 32.5K   1.8K  
TexasQuest is a 2D side scroller platform game similiar to Super Mario

566268/screenshot.jpg

Table of Contents

Introduction

About

TexasQuest is a 2D side scroller platform game similiar to Super Mario brothers. The object of the game is for the character 'Aaron' to reach the end where he will rescue and save his girlfriend from a terrible boss who works his employees too hard. Along the way there are enemies that will try and stop Aaron from reaching his destination. However, Aaron has tools to help him (i.e. soda, freeze pops, etc.) along in his journey. Can you help Aaron reach his girlfriend and save her from an otherwise productive day of work before he is snapped up by the bosses minions?

Background

History

This is actually and update to the previous version of TexasQuest where I used Microsoft's DirectX technology. In this version I have switched over to using the open source project called SlimDX. More information about that project can be found at http://slimdx.org [^]

Second, to pulling completely away from utilizing proprietary DirectX technology I am trying out the NAudio open source project here in this release. More information can be found here [^] regarding that project.

Acknowledgements

Let me start by saying that, no this is not original code. The original C# code can be found here. I simply needed to port it over to VB.NET so that I could study it more. The original author Aaron Dail wrote it for his girlfriend. Read more and download here, and see more screenshots. Aaron has generously made the full source available for the game, so if you want some 2D scrolling sample code in C#, this would be a great place to look.

Purpose

The purpose for me in this application was to learn programming in VB.NET. I can not explain to you how many times I have searched the web looking for decent examples on how VB developers can implement and create really good games. Video games are not one of the main reasons I got into programming, but I find that a lot of people want to be entertained. So, I say, if the people want cake, give them cake.

Outcomes

The general principles learned here could be applied to many types of games, be they 2D strategy games, 2D overhead adventures, 2D puzzles, or more 2D strategy games. I am not sure about the utility of this for 3D games, except that it will be completely useless for graphics.

Goals

We will cover how to get the rendering part of your game up and running using the open source project called SlimDX [^]. Second, we will talk about images and how to draw them. After that, you should be knowledgeable enough to actually put a game together. Then, we will cover level architecture and how to draw a level (and by level architecture, I mean what structures define a level, not an individual level's contents). Next up would probably be the basics of display characters and objects in levels.

Then, we should probably cover the current endeavor a character is undertaking, and actions they are executing, if any. Next, we will do 'hit detection', and one of the most complicated to get right topics: event handling. That is, when A jumps on B, X happens, but when B jumps on A, Y happens. You got to handle all that stuff. That is tough to get right. That flows right into changing a character's goal or endeavor in response to events, or what I will call thinking. Actually, user input would be covered there too. And, that there is a decently complete overview of a game.

Requirements

  • SlimDX.dll
  • NAudio.dll
  • NAudio.WindowsMediaFormat.dll

Installation

Your "Build" directory should start out looking this way.
566268/install.jpg

The Code

SlimDX renders to a target of some sort, which in .NET is either a Form or a Control (technically, since a Form is a Control, it is always to a Control). So first, you have to have a window, and optionally, you can have a Control on that window to render to (this lets you surround the rendering with other controls, like buttons or list boxes). Simple enough, right? Well, lets write a Main function that does this. So, start your IDE, and create a VB.NET Windows Application project.

VB
      Public Shared Sub Main(ByVal args As String())
   Dim mw As MainWindow = New MainWindow ()
   mw.Show()

   Do While mw.Created
       Application.DoEvents()
   Loop
End Sub

This goes in your MainWindow class. It should be pretty straightforward what we are doing. Because we aren’t doing much, or anything related to DirectX at this point. All we are doing is creating a window, showing it, then while it is created, processing its events repeatedly (when you click the Close box, the Created property of the Form becomes False).

Now, compile, build, and run. Should work like a champ.

Controller Class

Now, we are ready to get to the good stuff. I like to separate the rendering from the internals required to set up the rendering, and separate that from the game engine as well as the code to start and stop the app, as best as possible. This leads to a few more classes which can be confusing at first, but once you figure it out, it works quite nicely.

Basically, the structure I have grown to like is to have a Controller class that handles everything related to getting the rendering ready to go, but doesn't render anything. The rendering is actually done through a Renderer class of which the Controller object has an instance. The reason is that you can toss out and plug in Renderer objects for different tasks on the fly without much work. In my game, I have a Renderer for the main part of the game that displays the map and everything, and then several for menus. It is not that hard really, and it keeps your code cleaner.

Now, what I said about the engine being separate from the rendering is very important too. All the Renderer does is display the current state of the game. It does not affect game state at all, ever. The engine does this. While they usually run in tandem, that is not required. Meaning, you can pause the engine and still render, or you can play 1000 turns of the game without rendering any of them. This is basic, but important. So, let's go ahead and create the Controller class I was talking about. Add a new class called Controller to your project. Add the following Import statements:

VB
Imports System.Windows.Forms
Imports Microsoft.DirectX
Imports Microsoft.DirectX.Direct3D

Now, add the following private variables to the Controller class:

VB
Private m_Target As Control
Private m_Device As Device
Private m_Renderer As Renderer
Private m_presentParameters As PresentParameters
Private DeviceLost As Boolean

m_Target variable is the target we talked about earlier, it is where all the rendering goes. The m_Device is the DirectX device interface that does the rendering and stuff to the target. The m_presentParameters is some information we setup to create the device. The reason we keep this is that occasionally, the device could be lost, and we might need to recreate it without having to restart the app. Which is related to the IsDeviceLost, which is a flag that is set when the device is lost. The m_Renderer variable is the Renderer we talked about earlier.

Now, change your constructor to be like this:

VB
Public Sub New(ByVal target As Control, ByVal renderer As Renderer)
   Me.m_Target = target
   Me.m_Renderer = renderer
   IsDeviceLost = False

   m_presentParameters = New PresentParameters ()

   InitializeGraphics()
End Sub

Pretty simple, just sets up the private members, and calls IntializeGraphics, which should look like:

VB
Protected Sub InitializeGraphics()
   m_presentParameters.Windowed = True
'  If you want to use Full Screen mode (this assumes the target is a Form not just 
'  a Control), delete the previous line  and add:
'  m_presentParameters.Windowed = false
   
   m_presentParameters.SwapEffect = SwapEffect.Discard
   m_presentParameters.AutoDepthStencilFormat = DepthFormat.D16
   m_presentParameters.EnableAutoDepthStencil = True

   ' store our default adapter
   Dim adapterOrdinal As Integer = Manager.Adapters.Default.Adapter

   ' get our device capabilities so we can check stuff
   Dim caps As Caps = Manager.GetDeviceCaps(adapterOrdinal,DeviceType.Hardware)

   Dim createFlags As CreateFlags
   If caps.DeviceCaps.SupportsHardwareTransformAndLight Then
       createFlags = CreateFlags.HardwareVertexProcessing
   Else
       createFlags = CreateFlags.SoftwareVertexProcessing
   End If

   If caps.DeviceCaps.SupportsPureDevice Then
       createFlags = createFlags Or CreateFlags.PureDevice
   End If

   ' create our device
   m_device = New Device(adapterOrdinal, DeviceType.Hardware, target, createFlags, _
                        presentParameters)

   ' Hook the DeviceReset event so OnDeviceReset will get called every
   ' time we call m_device.Reset()
   AddHandler m_device.DeviceReset, AddressOf OnDeviceReset

   SetupDevice()
End Sub

Pretty complicated. And, heck if I know all of what it's doing. Like I said, read the other tutorials, or buy a book. Basically, most of what this does is get ready to create the device and then it creates it. Telling SlimDX a whole lot of information about how to create it. It also does two things at the end, add a handler for DeviceReset and call SetupDevice. The device reset handler calls the setup device too because that is supposed to handle when the device has been lost and can hopefully be recovered.

SetupDevice does the camera, lighting, and view setup:

VB
Protected Sub SetupDevice()
   m_device.RenderState.AlphaBlendEnable = True

   m_device.SetSamplerState(0, SamplerStageStates.MinFilter, _
                            CInt((TextureFilter.Linear)))
   m_device.SetSamplerState(0, SamplerStageStates.MagFilter, _
                            CInt((TextureFilter.Linear)))
   m_device.SetSamplerState(0, SamplerStageStates.MipFilter, _
                            CInt((TextureFilter.Linear)))

   m_device.RenderState.Lighting = False

   ' get camera vectors
   Dim width As Single = CSng(target.Size.Width)
   Dim height As Single = CSng(target.Size.Height)
   Dim centerX As Single = width / 2.0f
   Dim centerY As Single = height / 2.0f

   Dim cameraPosition As Vector3 = New Vector3(centerX, centerY, -5.0f)
   Dim cameraTarget As Vector3 = New Vector3(centerX, centerY, 0.0f)

   ' create our transforms
   m_device.Transform.View = Matrix.LookAtLH(cameraPosition, cameraTarget, _
                                             New Vector3(0.0f, 1.0f, 0.0f))

   m_device.Transform.Projection = Matrix.OrthoLH(width, height, 1.0f, 10.0f)
End Sub

Protected Sub OnDeviceReset(ByVal sender As Object, ByVal e As EventArgs)
   ' We use the same setup code to reset as we do for initial creation
   SetupDevice()
End Sub

This basically gets the device (camera, view, lighting) ready to draw the 2D stuff normally. Now, let's add the all important Render method:

VB
Public Sub Render()
   If DeviceLost Then
     ' Try to get the device back
     AttemptRecovery()
   End If

   ' If we couldn't get the device back, don't try to render
   If DeviceLost Then
     Return
   End If

   m_Device.Clear(ClearFlags.Target Or ClearFlags.ZBuffer, _
                  System.Drawing.Color.Blue, 1.0f, 0)
   m_Device.BeginScene()

   m_Renderer.Render(Me)

   m_Device.EndScene()

   Try
     ' Copy the back buffer to the display
     m_Device.Present()
   Catch e1 As DeviceLostException
     ' Indicate that the device has been lost
     m_DeviceLost = True

     ' Spew a message into the output window of the debugger
     System.Diagnostics.Debug.WriteLine("Device was lost")
   End Try
End Sub

First, we see if the device has been lost, and if so, we want to try to recover it. If after that, the device is still lost, then we return from this method without doing anything. As a note, the device can be lost when the screensaver comes on or things like that. If everything is OK, then we clear the display, and do BeginScene. All rendering must occur between the calls to BeginScene and EndScene. That’s just how SlimDX works. Between the two, we call our Renderer’s Render method which should do all the drawing. Then, after that, we try to present the work we have done, and that is when we might notice we’ve lost the device, so we set that deviceLost flag to True in this case (so, the next time Render is called, we will attempt to get the device back).

Now, AttemptRecovery:

VB
Protected Sub AttemptRecovery()
   Try
     device.TestCooperativeLevel()
   Catch e1 As DeviceLostException
   Catch e2 As DeviceNotResetException
     Try
       device.Reset(presentParameters)
       deviceLost = False


       ' Spew a message into the output window of the debugger
       System.Diagnostics.Debug.WriteLine("Device successfully reset")
     Catch e3 As DeviceLostException
       ' If it's still lost or lost again, just do 
       ' nothing
     End Try
End Sub

Let's just assume this works. Finally, we have a simple property to get and set the Renderer:

VB
Public Property Renderer() As Renderer
   Get
       Return m_Renderer
   End Get
   Set(ByVal value As Renderer)
       m_Renderer = value
   End Set
End Property

And, that is all for the Controller class for now. You can’t built it or run it yet, because we haven’t created the Renderer yet, so let's do that next.

Renderer Class

Create a new class in your project called Renderer. Add the Imports directives:

VB
Imports SlimDX
Imports SlimDX.Direct3D

Then, add the Render method:

VB
Public Class Renderer
   Public Sub New()
   End Sub

   Public Overridable Sub Render(ByVal controller As Controller)

   End Sub
End Class

And, that’s it. Right now the Render class is going to be dirt simple because it doesn’t do anything yet. Actually, the Render class itself will never really do anything, but its sub-classes will. In fact, we will make it an abstract class in the next version, because no one should ever need to use it as it is. For now though, if it was abstract, we couldn't test our fledgling program, so it's better to make it concrete.

Wrap Up

The last part we have to do is rewrite the Main function a bit:

VB
Public Shared Sub Main(ByVal args As String())
   Dim mw As MainWindow = New MainWindow ()
   Dim r As Renderer = New Renderer ()
   Dim c As Controller = New Controller (mw, r)

   mw.Show()

   Do While mw.Created
     c.Render()
     Application.DoEvents()
   Loop
End Sub

Build it and run it. You should be the proud parent of a deeply blue window. Congratulations.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)