Saturday, October 22, 2005
Texas Quest Tutorial Part 1

Part 1: 2D Rendering in Managed DirectX
Hi there. Due to some requests I have had regarding my game, Texas Quest, I am going to post a simple but hopefully informative set of tutorials covering the main aspects of getting a 2D side scrolling game to work. The general principles herein could be applied to many types of games though, 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.
I have never done a tutorial before, but here is what I am thinking as I write this: First in this part we will cover how to get the rendering part of your game up and running using Managed DirectX. Second we will talk about images and how to draw them. After that you should be knowlegable enough to actually put a game together. Then in the third installment 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. That's pretty easy actually. Then we should probably cover the current endeavor a character is undertaking and action 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 gotta handle all that stuff. That is tough to get right, and to be honest, my game still doesn't get it right all the time ;) 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 to. And that there is a decently complete overview of a game :)
Getting Started
Man let me tell you that I am not the guy to be writing a tutorial about DirectX that will impart the kind of wisdom you need to solve any problem. I can summarize it: it does graphics. I can also write a tutorial about how to get it to work. This in my experience is what tutorials usually do anyways, so all I am saying is just check your expectations at the door. This tutorial will teach you how to get it to work, but will not explain why it works (very well at least) ;) Speaking of tutorials, I initially learned how to use Managed DirectX through some decent tutorials on the web that I am going to link here:
http://geekswithblogs.net/jolson/articles/2176.aspxhttp://www.gamedev.net/reference/articles/article1434.asp
http://pluralsight.com/wiki/default.aspx/Craig.DirectX.Direct3DTutorialIndex
You don't have to read those guys since you are already here. But if you are more interested in the why instead of just the how, or if the how isn't really working for you, then those would be good places to check.
Obviously from this point forward you are gonna need Visual Studio ... or maybe the #develop IDE ... or something that compile C# code, and the DirectX SDK. I am going to assume you are more than a novice with C# as well. It isn't hard to learn, but I really don't want to write a tutorial for that yet. I will however impart some programming tips and good-practices (e.g. I will force feed you things you should do anyways, so just do them). I am using the December 2004 one for Texas Quest and this tutorial, and is has worked pretty good. I used the Summer 2005 but I had trouble deploying that on computers where the SDK wasn't installed. I didn't really look into it though, but since I already had the 2004 SDK working I just used that. And since that is what I have working on my box that is what I will show. I recommend getting the latest though, and if anything I say in this tutorial doesn't sync up with it let me know.
Baby Steps
DirectX renders to a target of some sort, which in .NET is either a Form or a Control (technically since a Form is a Control its 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 DirectX 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 C# Windows Application project.
Before we write any code you have to add references to the Microsoft.DirectX assemblies that should have been installed with the SDK.You need to add at a minimum Microsoft.DirectX, Microsoft.DirectX.Direct3D, and Microsoft.DirectX.Direct3DX. Now that we have done that, feel free to delete the default Form1.cs class generated for you. I hate the generated code they give you. Then add a new class (plain old class) with the name you want. I called my MainWindow because that succulently describes what it does. It is the main window. Naming is a very important and often misused concept in programming. Names are important, remember that.
Now we are ready to write some code. In the MainWindow class file write a using statement for System.Windows.Forms and make your MainWindow class inherit from the Form class (i.e. public class MainWindow : Form ).
Write your new Main function:
public static void Main (string [] args) {
MainWindow mw = new MainWindow ();
mw.Show();
while (mw.Created) {
Application.DoEvents();
}
}
This goes in your MainWindow class. It should be pretty straight forward what we are doing. Cause we aren’t doing much, or anything related to DirectX at this point. All we are doing is creating a window, showing it, then saying while it is created process 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
Now we are ready to get to the good stuff. Borrowing a good point from the tutorials I listed and read myself above, 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 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 being 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. Its 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 effect 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 lets go ahead and create the Controller class I was talking about. Add a new class called Controller to your project. Add the following using statements:
using System.Windows.Forms;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
Now add the following private variables to this class:
private Control target;
private Device device;
private PresentParameters presentParameters;
private bool deviceLost;
private Renderer renderer;
That first variable is the target we talked about earlier, it is where all the rendering goes. The second is the DirectX device interface that does the rendering and stuff to the target. The third 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 fourth, which is a flag that is set when the device is lost. The fifth and final variable is the Renderer we talked about earlier.
Now change your constructor to be like this:
public Controller (Control target, Renderer renderer) {
this.target = target;
this.renderer = renderer;
deviceLost = false;
presentParameters = new PresentParameters ();
InitializeGraphics();
}
Pretty simple, just sets up the private members and calls IntializeGraphics, which should look like:
protected void InitializeGraphics () {
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:
presentParameters.Windowed = false;
((Form)target).FormBorderStyle = FormBorderStyle.None;
((Form)target).Size = new Size (800,600); //I forget if this can be removed ;)
((Form)target).ClientSize = new Size (800,600); //this is your resolution
presentParameters.BackBufferFormat = Format.X8R8G8B8; //maybe needs to be changed...
presentParameters.BackBufferHeight = this.target.Size.Height;
presentParameters.BackBufferWidth = this.target.Size.Width;
presentParameters.PresentationInterval = PresentInterval.Default;
*/
presentParameters.SwapEffect = SwapEffect.Discard;
presentParameters.AutoDepthStencilFormat = DepthFormat.D16;
presentParameters.EnableAutoDepthStencil = true;
// store our default adapter
int adapterOrdinal = Manager.Adapters.Default.Adapter;
// get our device capabilities so we can check stuff
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;
// create our device
device = new Device(
adapterOrdinal,
DeviceType.Hardware,
target,
createFlags,
presentParameters
);
// Hook the DeviceReset event so OnDeviceReset will get called every
// time we call device.Reset()
device.DeviceReset += new EventHandler(this.OnDeviceReset);
SetupDevice();
}
Pretty complicated. And heck if I know all of what its 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 DirectX 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 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:
protected void SetupDevice () {
device.RenderState.AlphaBlendEnable = true;
device.SetSamplerState( 0, SamplerStageStates.MinFilter, (int)TextureFilter.Linear);
device.SetSamplerState( 0, SamplerStageStates.MagFilter, (int)TextureFilter.Linear);
device.SetSamplerState( 0, SamplerStageStates.MipFilter, (int)TextureFilter.Linear);
device.RenderState.Lighting = false;
// get camera vectors
float width = (float)target.Size.Width;
float height = (float)target.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
device.Transform.View =
Matrix.LookAtLH(
cameraPosition,
cameraTarget,
new Vector3(0.0f, 1.0f, 0.0f)
);
device.Transform.Projection = Matrix.OrthoLH(width, height, 1.0f, 10.0f);
}
protected void OnDeviceReset (object sender, EventArgs e) {
// We use the same setup code to reset as we do for initial creation
SetupDevice();
}
This basically gets the device (camera, view, lighting) ready to draw 2D stuff normally. That’s about it ;)
Now lets add the all important Render method:
public void Render () {
if (deviceLost) {
// Try to get the device back
AttemptRecovery();
}
// If we couldn't get the device back, don't try to render
if (deviceLost) {
return;
}
device.Clear(ClearFlags.Target | ClearFlags.ZBuffer, System.Drawing.Color.Blue, 1.0f, 0);
device.BeginScene();
renderer.Render(this);
device.EndScene();
try {
// Copy the back buffer to the display
device.Present();
} catch (DeviceLostException) {
// Indicate that the device has been lost
deviceLost = true;
// Spew a message into the output window of the debugger
System.Diagnostics.Debug.WriteLine("Device was lost");
}
}
I feel most of this is straight forward too. First we see if the device has been lost previously, and if so we 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 screen saver 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 DirectX 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:
protected void AttemptRecovery() {
try {
device.TestCooperativeLevel();
}
catch (DeviceLostException) {
}
catch (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 (DeviceLostException) {
// If it's still lost or lost again, just do
// nothing
}
}
}
Lets just assume this works ;) I copied it from somewhere and it seems to work, so that’s good enough for me. Next we have a DisposeGraphics method which should release all our resources:
public void DisposeGraphics () {
device.Dispose();
}
Since we just have the device at this point we just release it. Technically I don’t think you have to have this because resources will be reclaimed anyway on exiting the program, but its nice to give people a way to free them isn’t it?
Finally we have a simple property to get and set the Renderer:
public Renderer Renderer {
get { return renderer; }
set { renderer = value; }
}
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 lets do that next.
A Renderer
Create a new class in your project called Renderer. Add the using directives:
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
Then add the Render method:
public class Renderer {
public Renderer() {
}
public virtual void Render (Controller controller) {
}
}
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 its better to make it concrete.
Wrap Up
The last part we have to do is rewrite the Main function a bit:
public static void Main (string [] args) {
MainWindow mw = new MainWindow ();
Renderer r = new Renderer ();
Controller c = new Controller (mw, r);
mw.Show();
while (mw.Created) {
c.Render();
Application.DoEvents();
}
}
Build it and run it. You should be the proud parent of a deeply blue window :) Congratulations.
OK that wraps up what I wanted to cover here. This has taken longer than I thought it would actually. I do have a newfound respect for tutorial writers now though, cause its not as easy at it seems. Mostly the next tutorial about images is more important because that will get you drawing stuff, and after that you might be ready to really start a game. It could be a little while before I have that one up though, but thanks for reading. Comments are very welcome, so please post them.
Update: Wednesday the 4th of January 2006:
I have a second part thats been up for awhile now:
Texas Quest Tutorial Part 2: Design Part 1
I forgot to link it earlier ;)
If you don't get the next one up for a while, that's alright, but I can definitely say that I have a complete foundation on the basics of DirectX.
Before, about a couple of months ago, a project team and I built or atleast tried to build a 3d fighter game, like StreetFighter. However, the foundation was not set and it was poorly designed. Our focus in the project left me and a friend of mine with DirectX entirely for the project.
We had to build the engine, the rendering, basically the entire game. What made it difficult was the structure and separation between engine and graphics. What you're doing is even better, and what you described for abstraction makes a whole lot more sense now than it did before.
In any case, I would love to see what you have in store for the next tutorial. Very nice job though.
-----------------------
Tom
tdawg22@gmail.com
-----------------------
But essentially I'm looking on the more aspects of how to design this. Not code it, I can learn how to code it easy, the hard part is designing it. If you could help me out there, that would be great!
Thanks!
-------------------------
Tom
tdawg22@gmail.com
-------------------------
I have the same thought as Tom, design docs/specs would be very nice. =)
Lots of tutorials cover the down-and-dirty details of the code, but I can't find many on design theory.
I like theory.. :D
Well I have a small one up, let me know what you think :)
Its not a huge design doc or anything... but its somethin
Thanks :)
Glad you liked it. Any other comments, or suggestions? Anything is welcome ;)
" error CS0051: Inconsistent accessibility: parameter type"
I've got the code exactly as you have it I don't understand why I get the error.
<< Home


