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!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.
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.
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.
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.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:
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)
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));
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.
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.
16 comments:
This tutorial has been a great help for me! thaks for giving such a detailed explanation of how to make a game!
Just wanted to draw attention to an error that I was getting in case anyone else runs into it.
When setting cooperation levels for input I was getting an error about too many arguements so I changed the comma between Background and nonexclusive to a | sign and it compiles. So in my code I have:
deviceInput.SetCooperativeLevel(this, DXInput.CooperativeLevelFlags.Background | DXInput.CooperativeLevelFlags.NonExclusive);
Not setting the NonExclusive parameter would make the program loose my keyboard for some reason :/
Can't wait to finish the tutorial!
Hello,
I am using visual studio 2005 beta 2. I have been following along your tutorial quite easily, and I thank you for the tutorial. I have run into a few problems along the way and I thought I would put them here so to help anyone else that may run across them.
A)
On crawl.cs I could not use Microsoft.DirectX.DirectImput.CooperativeLevelFags.* it said out of range. So I manually added to background and nonexclusive. Ooh 8+2=10 (yes its rocket science XD)
public void InitializeInput()
{
deviceInput = new DXInput.Device(DXInput.SystemGuid.Keyboard);
deviceInput.SetCooperativeLevel(this, (Microsoft.DirectX.DirectInput.CooperativeLevelFlags)10);
/* NoWindowsKey 16
Background 8
Foreground 4
NonExclusive 2
Exclusive 1
*/
deviceInput.Acquire();
}
B)
The very last step it will not change states. This could be a mistype in my code.. But my solution was to have render return IGameState and I added a getState() to GameStateManager. So no render returns gameStateManager.getState() and in gameStateManager process was changed to this:
public void process()
{
IGameState newState;
IGameState oldState;
if (state != null)
{
newState = state.render();
oldState = this.getState();
if (newState != oldState)
{
this.switchState(newState);
}
}
}
Sorry this may not be as easy to follow.. I am going with the excuse that it is 4:00 am here and I need some sleep. But in all actuality I am not as gifted as the author is at making things crystal :P.
Thanks
O I forgot C)
D) verts[].setPosition does not seem to exist?!? instead I have to modify it ot say the following.
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);
Thanks again ;)
I'm glad you found these helpful.
I'm still meaning to revise these tutorials (I've done 1 and 2 :)) because they were written an SDK version ago - therefore there's a lot of breaking code with the current version :(.
Hi I have been having a problem displaying the texture properly in my program. (Which is exactly the same as yours) The texture appears slanted and cant find the function to change this. I am new to graphical games programming but not new to programming itself. Here is a screenshot of the problem, the left one is the program display and the right one the bmp image I made.
http://i.domaindlx.com/anothershrubery/wtf.bmp
What has gone wrong and how can I fix it? Cheers, great tutorial.
Check your UV coordinates as one of them is probably wrong!
I'm about halfway down the page on this section of the tutorial, but I am afraid to move on because my project does not look how it should.
I fixed the setProperty() bug, and the program compiles without any errors, but it does not display my titlescreen... instead I just get a blank, grey, window.
I know it is loading the correct file because if i change the filename it handles the exception and issues me an "oops" error.
Any clue what could be going wrong?
I am still a beginner with DX so it's hard for me to locate the problem. I have been tweaking bits of code to try and spot the problem, but everything seems to be in order.
Gray like a creamy colour? Like the background in the images above.
Or gray as in dark slate gray?
:D
In the previous tutorials did you have stuff displaying?
I really, really need to go through these tutorials and add source code / update them.
Wow thanks for the quick reply :D
It is grey as in a blank windows form.
And yes, in the previous tutorials I was able to get tiles to display.
However, I don't think I was able to ever get the black screen to show as you did in "blackground4tn.png"
Hmmm find this bit
public void Render()
{
if (device == null)
return;
device.Clear(ClearFlags.Target,
System.Drawing.Color.Black, 1.0f, 0);
device.BeginScene();
device.EndScene();
device.Present();
}
in your code. I assume your using VS.net? If so click over to the left of the code in the bar by the side you should get a red dot.
Anywhere after the return; statment is fine. Now when you run it, if the code reaches the red dot it will halt the program. Then back in Vs.net you can go backwards and forwards.
If for some reason, the program never gets to this function. Then the program won't halt on the red dot - and you have your first clue! For some reason the render function isn't being called.
Then work backwards - where is this render function called from? Try sticking a red dot there and see if it halts.
I'm going to sleep now but if you're still having problems tomorrow. I'll go throught this tutorial and write out the source code.
God going back through this and parts of it are pretty awful. My coding style was all over the place when I wrote this. Also my logic definitely seems twisted in places :D
I'm about two thirds through and I've written up two sets of source code. The next set is the DirectX stuff. So I'll do that and then upload it.
Ok new source codes up. The example code is also corrected in some places.
Should be smoother to follow.
I corrected any awful grammar or spelling where my eyes fell on it but for the most part I didn't read the text.
Thanks for the tutorial but I must have missed something somewhere. I have an error:
while(arch.Created)
{
gameStateManager.Process();
}
ArchitectureI.cs(48): 'ArchitectureI.Arch.gameStateManager' denotes a 'field' where a 'class' was expected
The links are broke can you help me with this?
Oh my Example.cs is as follows:
using System;
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Design;
namespace ArchitectureI
{
public class TitleScreenState : GameState
{
StringHolder s;
public TitleScreenState(StringHolder output)
{
s = output;
}
public void Render()
{
s.Change("The Wonderful State Game");
}
}
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!");
}
}
}
thanks in advance,
James
hi,
this is very confused tutorial.. IGameState and GameState are where? I'm lost in this code...
EX
public class TitleScreenState : GameState
is a typo should be
public class TitleScreenState : IGameState
The rest seems ok
I know, it's been a long time since you've written these tutorials, but there are quite a few typo's and stuff left out. One thing I found while working it through, and may help someone else was that you need to add this line in the main loop of the Crawl class:
form.gameStateManager.Process();
Add this just before the line:
Application.DoEvents(); //Let the OS handle what it needs to
And that should fix the problem of it not displaying the titlescreen.
Post a Comment