Friday, February 18, 2005

Game States



In the long run we're aiming in to be able to move a character around a tiled map. This is the part of design where the code can start looking awfully complex therefore it's important to lay down a solid architecture.

Architecture


Before we starting thinking too much about maps and things let's do the basics. The first things to think about are the game states :- credits, intro screen, option, gameplay screen and all that jazz. These seem pretty modular self contained pieces of code. What we'd like is a way to keep them as seperate as possible so let's open our Programming Role Playing Games with DirectX by Jim Adams , and read up on some patterns for dealing with game creation! Yay!

States and Processes


We want to be able to handle the situation where, for instance, the user presses the [esc] key. On the title screen this may prompt a quit dialog. In the game it may jump you to the start screen. Input is handled differently for different states.

We don't want the main render loop to have a piece of code like the one shown below.


switch(CurrentState)
{
   case STATE_TITLESCREEN:
         DoTitleScreen();
         break;

   case STATE_MAINMENU:
         DoMainMenu();
         break;

   case STATE_INGAME:
         DoGameFrame();
         break;
}


We want something more elegant. So here's how it's going to work - basically we'll have a function pointer, or a stack of function pointers.

Then we could have a number of functions like DoTitleScreen or DoTheGame. The function pointers points to one or the other depending on the state of the game. Hopefully that's pretty clear but why would we want a stack of functions that are being pointed too?

Well let's imagine two functions DoTitleScreen, ProcessAreYouReallySureYouWantToQuitMessageBox. So each is called. Both render themselves we have the title screen and a message box about quitting. It's all quite clean. We can push things and pop things and all that wonderful stuff.

Instead of functions we might want to have objects on the stack. This makes it even easier as we don't need to mess about with function pointers (delgates in C#). Instead we have object pointers or in our case we'll have pointers to interfaces. The stack will call the, let's say, Render() function of each state on the stack. Everything sound a bit wishy-washy? Time to get specific.

Data Types Stacks and Stackees



On this stack we're going to have 'states' such as Playing,Title Screen, Credits etc. But how I here you cry - HOW!? We need to define some kind of state object that can be used on the stack. So what's the very least we need to say something is a state - probably a render function. What we're trying to say in C# is "if it has a Render() function then it's a state". We're going to do this by creating a state interface. State is a very broad word but I want something a little more precise. How about GameState that's better!

Class Diagram

Okay so that's how the states are going to work. What about the stack. First what is stack? Well a stack is one of the basic data types and the name is unusally quite descriptive with regards to its function.

It has two functions push and pop. The most common example used for this is a stack of plates, popping the stack you remove the top plate. Pushing the stack means you put a new plate on top. There's a little ambiguity when you attempt to pop an empty stack but I'm sure a clever programmer like yourself will be able to deal with it. So why do we need a stack? As far as I'm concerned, for my program I don't need a stack!

I know shock horror!

But Programming Role Playing Games uses a stack - why aren't you? Well I think it's personally preference but it's worth looking at why it is done in the book. It is quite clever, you have your stack maybe intialized with a Start Screen okay so let's say from the start screen you select Play Game, then the Play Game State is added to the top of the stack. Each frame you call process in the stack, it starts from the bottom and tells the start screen to render / process what ever and it does this. Then it goes to the last state - the playing game and this renders everything to the screen. This may seem to beg the question why use a stack at all why not just use a single function pointer?

Well you may want 'states' that do not take up the entire screen, for instance imagine that Play Game State is not at the top, instead we've pushed another state called Message Box. So the game is rendered to the screen and then a message box is rendered above this! This might say "Connection to server broken" or whatever. Note that this message box could also appear on the title screen - or if there was another state like a chat room lobby this could also benefit from message boxes. One can see that you could have inventory screens and all types of GUI goodness.

For me though, for now, I want to keep it simple - so only one plate in my plate stack. If things get complicated then later we may introduce the stack. If messages boxes and inventory screens are needed - like they probably will be. Then they will be confined to the game play state - so a stack can be placed here.

Okay I hope you understand all the choices avaliable now. Vaguely the same class structure as in the book will be used so we'll have a manager class. Lets see how we get the code employed!


Code Division


Generally I like to seperate code out. I don't like looking a pages of pages of dense code while I attempt to hack out something new in the middle of all. Generally for each class I make a seperate .cs file. There's no problem in doing this as namespaces can be spread across files. If I'm testing a new concept - like this architecture we see here, well then I like to start a brand new project. I only want to see the code that's immediately relevant to this new wonderful thing I'm attempting. This method also gives me something to look back on and I can play with until I get it perfect.

So we'll start up a nice empty project. I've called mine ArchitectureI. In this project we add a nice new class called Arch. Remember to also add System.Windows.Forms as a reference and then remember to make sure we're using it. We're going to be making a windows program. Okay then a few keytaps later and Arch is inheriting from form, then we rip the previous code we wrote in lesson one to get a game loop up. (don't worry there's a code listing coming up)

One quick note about this game loop before we get to the code - the game loop is not as efficient as it could be. This isn't directly our fault as we've basically copied what was (they all use the new Framework thing now) suggested in the Mircosoft examples. But Application.DoEvents() actually creates and deletes more objects than is strictly necessary. But ... who really cares! The central rule of game programming is MAKE IT WORK! most people fail at this rule because they attempt to first attempt to apply the second rule of 'make it fast' (or even attempt to do this in parrallel.) To partially quote some of my old lecture notes:


If you think this is a big problem you are refered to a famous 'quote' from the early days of computing :

Programmer one says 'My program is faster than yours.' Programmer two replies - 'Yes but my program works.'


Okay enough tangent let's have a look at this base code. The code we're going to build our snappy architecture from!

ArchitectureI.cs


using System;
using System.Windows.Forms;

namespace ArchitectureI
{

public class Arch : Form
{
      static void Main()
      {
            Arch arch =
new Arch();
            arch.Show();

               while(arch.Created)
            {
                  Application.DoEvents();
            }
      }
}
}



Cool so this gives us our little window. Of course most of architecture code is gonna be happening in the background.

Go up to the Project menu and select add new item choosing a new .cs file. We'll call this GameState, yes very creative. Then we tap in the basic interface that we intend to use to identify our game states. So we're now dealing with two files. Lets have a quick mooch at the Interface.

IGameState.cs


using System;

namespace ArchitectureI
{
      public interface IGameState
      {
            void Render();
      }
}


There we go! Pretty simple hey?

Now to create the manager. The manager will swap the states around and hold the stack. Let's give it the name GameStateManager, a new class, so a new file. As before create a file and then let's poke around with the code. Minutes of poking later we'll have a basic class skeleton like below!

GameStateManager.cs


using System;

namespace ArchitectureI
{
      public class GameStateManager
      {

      }
}


We want to write this code with possible revisions in mind - imagine we wanted to turn it into a stack. It shouldn't take too much work, so this class will strive to be lightweight. We want a Process method and a Switch method. The switch method will return the current state and replace it with the new state passed in. So we give the Manger the state we'd like to now use and we get the state that was being used. (this way if we go to options or back to the title screen we can keep a reference of the playing game state and return the user there without too much trouble)

GameStateManager.cs


using System;

namespace ArchitectureI
{
      public class GameStateManager
      {
             private IGameState state;

               public void Process()
            {
            }

            public IGameState SwitchState(IGameState newState)
            {
            }
      }
}


So that's the basic class, if you try and compile you will probably get a few errors about not returning types. Time to flesh out the skeleton.

public void Process()
{
      if(state != null)
            state.Render();
}



Quite simple, we might want to do a little more programming about the situation of having a null pointer but for now we'll keep it simple (and probably a bit lacking in programming wisdom. Tangent: We might want to do a try and catch. Or might want to just exit if there is no state)

That's workable , so let's move on to SwitchState function.

public IGameState SwitchState(IGameState newState)
{
      IGameState oldState = state;
//store current state
      state = newState; //replace current state
      return(oldState); //return old value
}

This function is pretty simple. Now we probably want to add a nice construcor function, that will allow us to load in an intial state. Shouldn't be too stressful.



public GameStateManager(IGameState intialState)
{
state = intialState;
}



Right that is all the classes required, now to piece it together and have a look at some examples. We want to keep this quite simple because if we later modify it, this is the project we'll be looking at. So we're not going to pull in DirectX devices and the like, rather we'll use a string as the device and we'll display different things depending on state. Visually it looks a little like below.

Diagram

Putting it all together



To get an example we'll need to do some fiddling with the Arch class. We'll also need to make some States that will inherit from GameState! First lets look at the modifications.


using System;
using System.Windows.Forms;
using System.Drawing;


Remember to include the reference for System.Drawing aswell!


namespace ArchitectureI
{
public class StringHolder
{
private string output;

public string Output
{
get { return output; }
}

public void Change(string newString)
{
output = newString;
}
}


Where has this class come from? Why is it here? Well allow me to explain it's a reasonably dirty hack that stops me messing about with references of basic types like strings. If you want to find out why I've done this try simplifying it so, the following code will works with a simple string. Okie!


public class Arch : Form
{
GameStateManager gameStateManager;
StringHolder pretendDevice;

public GameStateManager GameStateManager
{
get { return gameStateManager; }
}


So we make a manager and a string, we're going to intialize the string and the Manager (but we'll do that later). So let's throw a constructor in.




//Arch.cs
public Arch()
{
pretendDevice = new StringHolder();
pretendDevice.Change("No State");
//yes if we added a constructor this could have been done in one statement
}

static void Main()
{
Arch arch = new Arch();
arch.Show();

while(arch.Created)
{
Application.DoEvents();
}
}



Okay we want to be able to output something to the window - this way we can show when states are changed!



//Arch.cs
protected override void OnPaint(PaintEventArgs pea)
{
pea.Graphics.DrawString(pretendDevice.output, this.Font, Brushes.Black, 0,0);
}



Some quick overriding later and we have our basic set up. This will produce something similar to the window shown below. Now we need to create a few states to play with.

Basic Window Setup

Time to create an example state or two - in this case we'll create one file but to classes! I wouldn't normally do this but these classes are going to be small and only used for example purposes so it's not really worth creating two seperate files to store them.

So let's add a new code file and call it Examples. How is it all going to work? Well the example classes will be given access to the stringholder and if the states are called then they change what the string said. Right I'm going to dump the first Example down now.


using System;

namespace ArchitectureI
{
public class TitleScreen : GameState
{
StringHolder s;

public TitleScreen(StringHolder output)
{
s = output;

}

public void Render()
{
s.Change("The Wonderful State Game");
}
}
}



As you can see we inherited from GameState and implemented the render function where we change the string value. The example is all pretty basic, but to see how the states are going to work we need to go back and fiddle with Arch again.


//Arch.cs
public Arch()
{
pretendDevice = new StringHolder();
pretendDevice.Change("No State");
gameStateManager = new GameStateManager(new TitleScreen(pretendDevice));
}

// This is in Arch.cs too! (There's no separate Program.cs file)
// (Although there is in the downloadable source code)
static void Main()
{
Arch arch = new Arch();
arch.Show();

while(arch.Created)
{
gameStateManager.Process();
Application.DoEvents();
}
}



The bolded bits are the bits we've changed. We load in the basic state in the constructor, then during each frame the process is called and this calls the render function of the state loaded. Great -yeh? I thought as much, so our state loaded in during in the construtor sets the text to "The Wonderful State Game". I hope that's all pretty clear now. Let's make another Example state (the best way to do this is copy it and paste it and then change the text - yes redundancy! But clarity! In the fight of illustration clarity wins!

Let's see the next example! It's a quick copy paste with the title and text changed


public class PlayingGameState : IGameState
{
// Think of this as a reference to the graphics card device
StringHolder s;

public PlayingGameState(StringHolder output)
{
s = output;
}

public void Render()
{
s.Change("You are now enjoying a wonderful game of game state!");
}
}



So we have a Title Screen and we have a Playing Game. Let's assume in this simple game you press "ENTER" to start playing. Enter would of course change state! How is this magically journey going to end? Hold on to your keyboard.

Back to the main code in Arch and let's do a little more overriding.

//Arch.cs
protected override void OnKeyPress(KeyPressEventArgs e)
{
if( (int)e.KeyChar == (int)Keys.Enter )
{
// MessageBox.Show("You pressed Enter");
gameStateManager.SwitchState(new PlayingGameState(pretendDevice));
Invalidate();
}
}


What enter a couple of times and you'll change from the title screen to the playing the game.


!Important! Is this code any good?



The architecture is good as is the base code but the example is NOT. There's a number of nasty bits. We're handling OnPaint then calling Invalidate and we also using Application.DoEvents() - we really shouldn't be mixing these!. Also when taking input - like the enter key - we should pass the input to the active state. The active state should then decide what action to take on a given input.

This would make everything perfect and it's what we'll be doing next.

Woo it works


Okay so we got GameState, GameStateManager and these will transfer over to DirectX without too much problem - and we'll be much more careful about conflicts (no Application.DoEvents and OnPaint, much straighter!) Now we have the files to look back on as well and refer to.

Mixing the Architecture up with DirectX and our previous progress



Time for another new project! Yay! Let's call this one LearningToCrawlI (walking is still a long way off). Let's list what we've got to do here!


  • Set the basic game loop

  • Get the DirectX linked to the Window

  • Get a few States in there

  • Abstract a tile class

  • Tile the window

  • Render a character

  • Add movement





We'll start off by adding a class that we'll call Crawl. Then get our main loop sorted and work from there.

From our previous project ArchitectureI we can copy paste the files that contain the classes GameState and GameStateManager. Either copy the files over from one project directory to our current one and then add existing item and choose them. Or just create two new .cs files and copy the code into them. Whatever you feel most comfortable doing! (Just remember to change the namespace from ArchitectureI to LearningToCrawlI, or whatever you've called it.) After a frenzy of activity you should have a similar setup as that shown below.


You better being having fun!

We now have a version of our pure state architecture ready to rumble. We can now modify it as the need arises. For instance we might want to change the GameState interface so it will take in information about input. But we'll get to that later! Or we may have second thoughts and actually change the GameStateManage to a stack - it's world of endless possibility.

Fleshing out Crawl with a bit of Game Looping and DirectX!



We need to add references for:

  • System.Drawing

  • System.Windows.Forms

  • Microsoft.DirectX

  • Microsoft.DirectX.Direct3D




Then the basics of DirectX and the game loop need adding in. So that's just putting the game loop code in, and making sure the device is there and adding the code to set it up. We're not putting the VetexBuffer in because we want that to be associated directly with the tile rather than been some random variable in the main class. Therefore we have a OnCreateDevice function but it's blank for now. So after this what does the code look like? Well it looks like the code pasted in below.



using System;
using System.Drawing;
using System.Windows.Forms;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;

namespace LearningToCrawlI
{
public class Crawl : Form
{
Device device = null;

public Crawl()
{
}

static void Main()
{
Crawl form = new Crawl();
form.InitializeGraphics();
form.Show();

while (form.Created)
{
Application.DoEvents(); //Let the OS handle what it needs to
}
}

public void InitializeGraphics()
{
try
{
// Now let's setup our D3D stuff
PresentParameters presentParams = new PresentParameters();
presentParams.Windowed = true;
presentParams.SwapEffect = SwapEffect.Discard;
device = new Device(0,
DeviceType.Hardware,
this,
CreateFlags.HardwareVertexProcessing,
presentParams);

device.DeviceReset += new System.EventHandler(this.OnResetDevice);
}
catch (DirectXException e)
{

MessageBox.Show(null, "Error intializing graphics: "
+ e.Message, "Error");
Close();
}
}

public void OnResetDevice(object sender, EventArgs e)
{
Device dev = (Device)sender;
dev.RenderState.Lighting = false;
}
}
}



Notice how the IntializeGraphics has been ripped wholesale from previous lessons! Yay! Now we have the basics of what we have done in the two previous lessons - we now need to get them to work with each other. This means defining some states and a little more hacking with the game loop.

State Managment into the GameLoop



We're goin to be using our shiny new GameStateManager so we need this declared in the form. We're going to be calling it from the main loop and because the main loop is static, we need to define it as static, or call it through form that has already been created. It's takes little effort to get things set up as below:


public class Crawl : Form
{
GameStateManager gameStateManager;
Device device = null;

public Crawl()
{

}

static void Main()
{
Crawl form = new Crawl();
form.InitializeGraphics();
form.gameStateManager = new GameStateManager(null);
form.Show();

while (form.Created)
{
form.gameStateManager.process();

... etc



We intialize GameStateManager just after intializing the device - currently just with null. This means that once run, very little is going to happen, it's nice to see that it doesn't crash though :). To see some action we need to craft some game states.

Crafting a 'title screen' game state



As before we will have to create a game state using our game state interface. So Project>Add New Item and choose a nice new code file. I'm calling mine TitleScreenState. First we'll type out the skeleton structure of our class, so we will end up with something like:



namespace LearningToCrawlI
{
public class TitleScreenState : IGameState
{
public void Render()
{

}
}
}



So what do we want to do when we call render? Display our title screen of course and give instructions for what the player should be doing. This means displaying some graphics so cue VertexBuffers, textures all that crazy jazz.

So in this game state we'll throw in a nice VertexBuffer variable, we can keep this private. Of course we also want to include the necessary using ... lines of code.

We also need to drop in a constructor - we're going to have to setup the vertex buffer there and get access to the D3D device. So the inner guts of this new State look a bit like below:



private VertexBuffer vertexBuffer;
private Device device;

public TitleScreenState(Device d)
{
device = d;
vertexBuffer =
new VertexBuffer(typeof(CustomVertex.PositionTextured),
4,
device,
0,
CustomVertex.PositionTextured.Format,
Pool.Default);
}

public void Render()
{

}


Reasonably standard so far - infact so standard it's almost tempting to start playing around with Abstract Classes to generalize things. But for now let's keep things simple and avoid tangents. At this point we can jump back to Crawl.cs and add TitleScreenState in as the default state to start up with.


static void Main()
{
Crawl form = new Crawl();
form.InitializeGraphics();
form.gameStateManager =
new GameStateManager(new TitleScreenState(form.device));
form.Show();
...etc


Once again this runs without crashing, all we need do is tweak our Render function so we actually begin to display something. Back to TitleScreenState!

At this point it would be nice to put in some kind of check to see if everything is working as we hope. Let's scroll on down to our Render() function. We'll stick some basic code in here, to set the background colour and present the results to the screen. We're going for a moody black title screen colour so the code looks as below:


public void Render()
{
if (device == null)
return;
device.Clear(ClearFlags.Target,
System.Drawing.Color.Black, 1.0f, 0);

device.BeginScene();

device.EndScene();
device.Present();
}



With this in place we get:


Blackground

Perfect, eveything is working well.

The next step is to get the VertexBuffer full of vertices to show the expectant user. A lot of this is copying code we've written before. So let's put in those vertices, we'll make a rectangle that covers the entire window, and put a pretty starting screen there. So we need to go to the constructor and add a few lines and put in a delegate.



public TitleScreenState(Device d)
{
device = d;
// We'll implement the DeviceReset function is a short while.
device.DeviceReset += new System.EventHandler(DeviceReset);


vertexBuffer = new VertexBuffer(typeof(CustomVertex.PositionTextured),
4,
device,
0,
CustomVertex.
PositionTextured.Format,
Pool.Default);

vertexBuffer.Created += new System.EventHandler(this.OnCreateVertexBuffer);
this.OnCreateVertexBuffer(vertexBuffer, null);
}



We've made reference to an EventHandler (OnCreateVertexBuffer) that we have yet written, so that's what should be tackled next. In this event handler we'll set up Vertex Positions, as we want to use the entire window there isn't all that much to think about.



private void OnCreateVertexBuffer(object sender, System.EventArgs e)
{
VertexBuffer buffer = (VertexBuffer)sender;

CustomVertex.PositionTextured[] verts = new CustomVertex.PositionTextured[4];
verts[0].Position = new Vector3(2, 0, 0);
verts[1].Position = new Vector3(2, -2, 0);
verts[2].Position = new Vector3(0, -2, 0);
verts[3].Position = new Vector3(0, 0, 0);
buffer.SetData(verts, 0, LockFlags.None);
}



This code is very much the same as before with only changes in the vertex positions. Next we want to publish this to the screen - onward to the Render() procedure!



public void Render()
{
if (device == null)
return;

Matrix QuadMatrix = new Matrix();
QuadMatrix = Matrix.Identity;
QuadMatrix.Translate(-1f, 1f, 0f); // Start at the top corner of the window

device.Clear(ClearFlags.Target, System.Drawing.Color.Blue, 1.0f, 0);

device.BeginScene();

device.SetStreamSource( 0, vertexBuffer, 0);
device.VertexFormat = CustomVertex.PositionTextured.Format;

device.SetTransform(TransformType.World, QuadMatrix);
device.DrawPrimitives(PrimitiveType.TriangleFan, 0, 2);

device.EndScene();
device.Present();
}



For now we'll just whack in the above code - before we'll be able to see too much though we're going to need a texture! So I'm going to fire up MSPaint and create a beauty this very instant.

Title Screen Bitmap

Truely it beings a tear to my eye. Let's get this wonder in to a texture format ASAP. Cue copy lots of code we've previously written!


private VertexBuffer vertexBuffer;
private Texture titleTexture;
private Device device;


In the variables we add a nice new texture variable appropiately named. Next we'll take the LoadTexture function we wrote previously and slightly modify to load our new texture.


public void LoadTextures()
{
try
{
System.Drawing.Bitmap title = (System.Drawing.Bitmap) System.Drawing.

Bitmap.FromFile(@"C:\titlescreen.bmp");
titleTexture = Texture.FromBitmap(device, title, 0, Pool.Managed);
}
catch(Exception e)
{
MessageBox.Show("There has been an error loading the textures:"
+ e.ToString(), "oops");
}
}



Of course we have to add a call to this in the constructor as well!



public TitleScreenState(Device d)
{
device = d;
device.DeviceReset +=new System.EventHandler(deviceReset);

LoadTextures();
...etc


Cool now just to knock in those good old Tu and Tv values then a small edit to the Render procedure and we're good to go!

As I'm sure you will recall Tu and Tv control where each vertice is going to be placed on the texture, then the vertices and joined up and that shape is cut out. In this case we want a square shape from a square texture so it's very simple. So in the OnCreateVertexBuffer function before the lock we add the following texture information:



verts[0].Tu = 1.0f;
verts[0].Tv = 0.0f; // Top Right Hand Corner

verts[3].Tu = 0.0f;
verts[3].Tv = 0.0f;

verts[1].Tu = 1.0f;
verts[1].Tv = 1.0f;

verts[2].Tu = 0.0f;
verts[2].Tv = 1.0f;



Before running - there's needs to be some minor cleaning. We want at this stage to be able to resize the window and just have the graphics resize. Also we need the lighting to be turned off, especially when reset, briefly we cut the constructor down to the following:



public TitleScreenState(Device d)
{
device = d;
device.DeviceReset += new System.EventHandler(DeviceReset);
DeviceReset(d, null);
LoadTextures();
}



Then we build up the DeviceReset function so that it looks like below:



private void DeviceReset(object sender, System.EventArgs e)
{
Device d = (Device) sender;
d.RenderState.Lighting = false;
vertexBuffer = new VertexBuffer(typeof(CustomVertex.PositionTextured),
4,
d,
0,
CustomVertex.PositionTextured.Format,
Pool.Default);

vertexBuffer.Created += new System.EventHandler(this.OnCreateVertexBuffer);
OnCreateVertexBuffer(vertexBuffer, null);
}


We've loaded the texture now all we need do is tell the device to use. So in the title screens render function let's add one line of code.



public void Render()
{
if (device == null)
return;

Matrix QuadMatrix = new Matrix();
QuadMatrix = Matrix.Identity;
QuadMatrix.Translate(-1f, 1f, 0f); // Start at the top corner of the window

device.SetTexture(0, titleTexture);
device.Clear(ClearFlags.Target, System.Drawing.Color.Blue, 1.0f, 0);

device.BeginScene();

device.SetStreamSource( 0, vertexBuffer, 0);
device.VertexFormat = CustomVertex.PositionTextured.Format;

device.SetTransform(TransformType.World, QuadMatrix);
device.DrawPrimitives(PrimitiveType.TriangleFan, 0, 2);

device.EndScene();
device.Present();
}



And we get this: (currently there's an annoying line of corruption, that I can't see why it should be there hopefully I stumble across the answer at some point)

Looking pretty spiffy

Still it's looking pretty spiffy and we can be very pround of how far we have come!

Aside: Annoying Line of corruption



Currently I'm writing on a computer without hardware acceleration, upon setting up the Device I set it up for hardware accleration - hence problems!


device = new Device(0,
DeviceType.Hardware,
this,
CreateFlags.HardwareVertexProcessing,
presentParams);


Should have been:


device = new Device(0,
DeviceType.Hardware,
this,
CreateFlags.SoftwareVertexProcessing,
presentParams);


Huzzah!

Code for TitleGameState




using System;
using System.Windows.Forms;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;

namespace LearningToCrawlI
{
public class TitleScreenState : GameState
{
private VertexBuffer vertexBuffer;
private Texture titleTexture;
private Device device;

public TitleScreenState(Device d)
{
device = d;
device.DeviceReset +=new System.EventHandler(deviceReset);
deviceReset(d, null);
LoadTextures();
}

public void Render()
{
if (device == null)
return;

Matrix QuadMatrix = new Matrix();
QuadMatrix = Matrix.Identity;
QuadMatrix.Translate(-1f, 1f, 0f);

device.Clear(ClearFlags.Target, System.Drawing.Color.Beige, 1.0f, 0);

device.BeginScene();

device.SetStreamSource( 0, vertexBuffer, 0);
device.VertexFormat = CustomVertex.PositionTextured.Format;

device.SetTransform(TransformType.World, QuadMatrix);
device.SetTexture(0, titleTexture);
device.DrawPrimitives(PrimitiveType.TriangleFan, 0, 2);

device.EndScene();
device.Present();
}

private void deviceReset(object sender, System.EventArgs e)
{
Device d = (Device) sender;
d.RenderState.Lighting = false;
vertexBuffer = new VertexBuffer(typeof(CustomVertex.PositionTextured),
4,
d,
0,
CustomVertex.PositionTextured.Format,
Pool.Default);

vertexBuffer.Created += new System.EventHandler(this.OnCreateVertexBuffer);
OnCreateVertexBuffer(vertexBuffer, null);
}

private void OnCreateVertexBuffer(object sender, System.EventArgs e)
{
VertexBuffer buffer = (VertexBuffer)sender;

CustomVertex.PositionTextured[] verts = new CustomVertex.PositionTextured[4];
verts[0].SetPosition(new Vector3(2,0,1f));
verts[1].SetPosition(new Vector3(2,-2,1f));
verts[2].SetPosition(new Vector3(0,-2,1f));
verts[3].SetPosition(new Vector3(0,0,1f));

verts[0].Tu = 1;
verts[0].Tv = 0; // Top Right Hand Corner

verts[3].Tu = 0;
verts[3].Tv = 0;

verts[1].Tu = 1;
verts[1].Tv = 1;

verts[2].Tu = 0;
verts[2].Tv = 1;

buffer.SetData(verts, 0, LockFlags.None);
}

public void LoadTextures()
{
try
{

System.Drawing.Bitmap title =
(System.Drawing.Bitmap) System.Drawing.Bitmap.FromFile(@"C:\titlescreen.bmp");
titleTexture = Texture.FromBitmap(device, title, 0, Pool.Managed);
}
catch(Exception e)
{
MessageBox.Show("There has been an error loading the textures:"
+ e.ToString(), "oops");
}
}
}
}


How about another state?



We need two things:


  • A game state

  • Some method for passing from one state to another - input handling!



For now we'll stick to what we know and bash out another state, basically we can copy
TitleGameState and then patch it with bits of our previous project to get to the stage where we show a few tiles with different textures.

Once again we go to Project>Add New Item and choose a nice empty .cs file. This one we'll call PlayingGameState. Then we copy and paste our title screen because at this stage it makes a great base to work from. If we'd used an abstract class maybe we would have just been implementing a few things here but what are you going to do :)

We need to make a few changes, first we'll rename the class and constructor to 'PlayingGameState'. We also need two textures - at least. At some point we'll add a texture management class which will probably use a dictionary datatype, maybe a hash table. Seems to make sense but when the time comes we'll also consult the experts!

Let's get back to good old grassTexture and stoneTexture, our variables will look like thus:


private VertexBuffer vertexBuffer;
private Texture grassTexture;
private Texture stoneTexture;
private Device device;


Next it makes sense to alter the LoadTexture function, so we load the correct textures.



public void LoadTextures()
{
try
{

System.Drawing.Bitmap grassBitmap = (System.Drawing.Bitmap)
System.Drawing.Bitmap.FromFile(@"C:\grass.bmp");
System.Drawing.Bitmap stoneBitmap = (System.Drawing.Bitmap)
System.Drawing.Bitmap.FromFile(@"C:\stone.bmp");
grassTexture = Texture.FromBitmap(device,
grassBitmap,
0,
Pool.Managed);
stoneTexture = Texture.FromBitmap(device,
stoneBitmap,
0,
Pool.Managed);
}
... etc


It's important to ensure that the bitmaps are where you say they are. You can just give the names if they'll be in the same directory as where you executable file (.exe) is generated.

The vertex buffer needs altering so the tiles won't be humongous.


verts[0].Position = new Vector3(0.25f, 0, 1f);
verts[1].Position = new Vector3(0.25f, -0.25f, 1f);
verts[2].Position = new Vector3(0, -0.25f, 1f);
verts[3].Position = new Vector3(0, 0, 1f);



All the rest is the same, we're still taking the entirity of the bitmaps image, so nothing else to change here. Just a tweak or two to the Render procedure and we'll be where we where before.


device.BeginScene();

device.SetStreamSource( 0, vertexBuffer, 0);
device.VertexFormat = CustomVertex.PositionTextured.Format;

device.SetTransform(TransformType.World, QuadMatrix);
device.SetTexture(0, grassTexture);
device.DrawPrimitives(PrimitiveType.TriangleFan, 0, 2);


QuadMatrix.Translate((-1 + 0.25f),1f, 0f);

device.SetTransform(TransformType.World, QuadMatrix);
device.SetTexture(0, stoneTexture);
device.DrawPrimitives(PrimitiveType.TriangleFan, 0, 2);


...etc



So far so good - we need to test all of this though. Just to test we'll change a bit of code in the Crawl class so that PlayingGameState is loaded by default.


form.gameStateManager = new GameStateManager(new TitleScreenState(form.device));


to:


form.gameStateManager = new GameStateManager(new PlayingGameState(form.device));


Game State GOOOoooo!

Okay that seems cool, see how I seemed to have change the background colour to beige at some point. Now we flick back to title screen and ponder, ponder ponder ponder.

TUPNI



Okay so our last challenge is to recognise that the Enter key has been pressed and then change states. So two things, we want the states to handle their own input annnnndddd we want the states to be able to switch state - I know mind bogglingly but I assure you it's posible just follow.


Hoops



We need to add another device to the Crawl class. This device though is a DirectInput Device and we are already using a variable type called Device namely the Direct3D - oh noes. Well the simplest way around this is to add Microsoft.DirectX.Direct3D. to the start of all Devices in crawl. This can be done using a Find Replace, find all 'Device' replace with 'Microsoft.DirectX.Direct3D.Device'. Make sure you get the case correct if you do this or you are in for trouble.

Not only do you have to do this for device but you must do it for DeviceType. So do another replace 'DeviceType' with 'Microsoft.DirectX.Direct3D.DeviceType' then everything should work.

If everything doesn't work you will get errors saying 'Device' is an ambiguous reference . If this happens you need to go to the line in error and see if it's a DirectInput device or a Direct3D device and make sure it starts with either Microsoft.DirectX.Direct3D. or Microsoft.DirectX.DirectInput. accordingly. So thats a little bit of trouble we have to overcome.

DirectInput device



Let's start off with a small tip - if you don't want to have to be typing out Microsoft.DirectX.DirectInput. all the time, as it seems we may have to due to name clashes, you can lever the using keyword and make an alias. So at the top of our Crawl.cs file, (or whatever you've called the .cs file where you house the object with the Main function) we can write:


using DXInput = Microsoft.DirectX.DirectInput;


Now we can just use DXInput to play with our stuff. Groovy.

Okay so lets stop talking about this device and actually add it to the code.


public class Crawl : Form
{
GameStateManager gameStateManager;
Microsoft.DirectX.Direct3D.Device device = null;
DXInput.Device deviceInput = null;


Next we need a IntializeInput function, so we'll write a call in for one next to the IntializeGraphics function like so:


static void Main()
{
Crawl form = new Crawl();
form.InitializeGraphics();
form.InitializeInput();
...etc


Now we're calling it, it may be a good idea to write the actual code. Basically it's setup code, where a lot of interesting things an be done, but we just want standard safe keyboard use ... and this is how we get it:


public void InitializeInput()
{
deviceInput = new DXInput.Device(DXInput.SystemGuid.Keyboard);
deviceInput.SetCooperativeLevel(this,
DXInput.CooperativeLevelFlags.Background |
DXInput.CooperativeLevelFlags.NonExclusive);
deviceInput.Acquire();

}


First we create the input device, its need a unique identifying number which we get from DirectXInput. Then we set the CooperativeLevel, Background means that if the window looses focus we don't unaquire the keyboard. NonExclusive means other programs can use the keyboard - imagine some was playing your game but also wanted to using an instant messenger program, well there you go. There are a few other flags - such as turning off the windows key but these can looked up in the MSDN library at your leisure. Finally we aquire the device, which if succesful means that we are now ready to go.

Poking the GameStates



We need access to the Input device and GameStateManager - this will allow us to swap states on a keypress. Of course you know what this means? Yes that's right name conflicts abound - I suggest heavy use of alias to make things easier to handle.

We're going to start off with the TitleScreenState first removal of ambiguity. If using DXInput = Microsoft.DirectX.DirectInput; is added then upon running all the graphics ambiguities will come out of the wood work as errors and can be corrected. Here I'm also going to alias the Graphics as DXGraphics.


using Microsoft.DirectX.Direct3D;
using DXGraphics = Microsoft.DirectX.Direct3D;
using DXInput = Microsoft.DirectX.DirectInput;



If you do the above the ambiguities should be reduced to zero!

So once it's compiling without ambiguity let's fiddle with the constructor and variables.



...
private Device device;

private GameStateManager gameStateManager;
private DXInput.Device inputDevice;

public TitleScreenState(Device d, GameStateManager g,
DXInput.Device dInput
)
{
device = d;
device.DeviceReset +=new System.EventHandler(deviceReset);
deviceReset(d, null);

gameStateManager = g;
inputDevice = dInput;

LoadTextures();
}
...etc


Now, of course, some minor alterations must be made to Crawl.


static void Main()
{
Crawl form = new Crawl();
form.InitializeGraphics();
form.InitializeInput();
form.gameStateManager = new GameStateManager(null);

TitleScreenState titleScreenState =
new TitleScreenState(form.device,
form.gameStateManager,
form.deviceInput);

form.gameStateManager.SwitchState(titleScreenState);

...etc


Everything runs okay, the same changes should be made to PlayGameState also so it has access to the same things as the title screen. They are so similar it will not take too long. (So similar clever people will probably have reduced their coding using some kind of abstract class but I'm going to resist this for now, because it's not absolutlely necessary.

Now we want to be able to check the input each frame - to do this we need to put a procedure into the Render function, which as it's no longer just associated with Rendering we should rename to Process - just use a find replace, or don't bother as its only syntatic sugar. (If you do though you may overright RenderState, and you'll have to rewrite these ones - though it's much less work :)

On to the function - in TitleScreenState we want to act on keypresses so we need a function to look at the keyboard. We start with a simple skeleton function like so:


private void UpdateInput()
{

}


Then we need to put in some code to find out what's up with the keyboard and if Enter has been pressed.


private void UpdateInput()
{
DXInput.KeyboardState state =
inputDevice.GetCurrentKeyboardState();
if (state[DXInput.Key.Return])
{
/* Enter was pressed */
MessageBox.Show("It works");
}
}



Okay let's hook this up, at the bottom of the the TitleScreenState.Render() (or TitleScreenState.Process() depending on what you've called it). We add a call to the UpdateInput procedure.




device.Present();

UpdateInput();
}


Okay now upon running and pressing enter a message box will pop up saying it works - cool we have some basic input stuff working - over different game states no less - well nearly.


Working input

Yes the pictured image says something different - that's because I can use my uberl33t programming skills to alter the code!

The final test




private void UpdateInput()
{
DXInput.KeyboardState state =
inputDevice.GetCurrentKeyboardState();

if (state[DXInput.Key.Return])
{
gameStateManager.switchState(new PlayingGameState(device,
gameStateManager,
inputDevice));
}
}


If you're looking at this thinking - wait my PlayingGameState constructor isn't like that - then I suggest you change it. I only made a brief comment early that it should be done. Just do the basic changes as in intialize the variables and expand the constructor and that's all that is required for now.

I think everyone must agree though the end result is pretty polished and cool. One could imagine a number of options on the start menu say 'Continue' where a new PlayingGameState would be created but perhaps with an extra arguement to load a saved game.

We have now learnt the crawling. We have our nice expandable archetiture in place and it works well.

Maybe a little tidying up could be done with the input - possibly some kind of intermediate abstract class - but that could always be done in refactoring. It's quite tight and pleasent at the moment.

One could easily imagine encorparating things like intro - maybe the StateManager itself could have a number of fades and wipes included in it.

Next!



So what is next?!? How could we possibly top this. Well we're going to actual tile the window! That's correct! To do this we'll make a Tile datatype, something pretty simple and probably use an array to handle the map for now. Hopefully that shouldn't be too trying. Then we'll put in a man or something similar and use the keys to move him round the screen. If we really want to show off, we'll animate him though this means a small tangent into the world of TIME and then that will pretty much wrap up this section of making a tile based roleplaying game in Direct3D and C#.

Notes



There's a lot of unmanaged code here vertexbuffers and textures are unmanaged. That means when we stop using them, like when we switch state, we should call their Dispose methods. The easiest way to do this would to be to make IGameState inherit from IDispoe and then handle all the clean up per state. If you don't call IDispose your program may leak memory :(

Source code



[Game State Source Code 1-3]

Some people where having problems with this. And having briefy gone through it I can understand why! So here's some source code to help. Unfortunately it doesn't follow the above code exactly - in the source code the static main function is in a seperate file, in the examples above the main function is in the Crawl.cs.


References



Post a Comment