Tuesday, February 22, 2005

Time


Time can change me,
But I can't change time.



We are making some damn fine excellent process. Now we come on to an important and some time thorny area- that of time. We need to be able to say things like "every 5 seconds do this" and that kind of thing. It can produce some tricky coding.



The aim is to keep things simple for ourselves the programmer. Ideally we want to be able to say things like every two seconds change colour. Let's start by defining our goals.



Goals



We want code to be something like:




setTimeEvent(1, changeAnimation, forever);


So the above function would create a time event that every second would call the function changeAnimation and it would do this forever. This also needs a pause function and that should sort all our time needs out for the foreseeable future.





Implementation



We need a GameClock, that will most likely be static and which will store time events. So we can access the global clock from anywhere in our code, if anything deserves to be a singleton it's this.




So let's start a nice new project. First code file to introduce we'll call 'Clock.cs' and create a static class of the same name. Also create a file call 'ExampleRun.cs' we'll put the Main function in here. So we'll have to skeleton classes like so:




public class ExampleRun
{
public static void Main()
{
}
}


public class Clock
{
}



These are both in their respective files of course. For now we'll concentrate on clock and then we'll test it out in ExampleRun.




First we need some variables. Let's play it a little dangerous and fast here and rather just putting in what we essentially need (good programming practice) we'll attempt to anticipate a few things we might need further down the line (generally bad practice - but we'll do it anyway :)).




static private ArrayList timeEventList = new ArrayList();
static private float startUpTime;
static private float timeBetweenFrames;



Remember to include using System.Collections; at the top so we won't run into any problems using an array list.



TimeEvents




TimeEvents are what is to be done - i.e. a record of which function to call and when this function should be called. We will probably write another class that contains all this information so we can use it easily in the nice array we've built for it. The array should be sorted with the element thatneeds to be called soonist is checked first.




The other two variables are the time the game was started - it may be nice to know what time it is where the players playing and how long he has been playing. People have died playing games! It's your ethical responsibility as the programmer of the next super addictive game to suggest the person stops playing after 24hrs straight :)




timeBetweenFrames, is how long the last frame took to complete. This is useful to make smooth animation if the screen cannot be updated fast enough. It's something that is potentially handy to have but could defintely be missed out until needsx - it's in now and it stays :D. We can also use this to work out frames per second, eveyone likes to work out frames-per-second the filthy monkeys covered with their own excrement that they are.




Okay let's throw in our first and potentially most important function GetTime()!




static public float GetTime()
{
return System.Environment.TickCount;
}



TickCount I believe is not the most precise time information you can get but it is pretty quick and I believe it will serve us fine. Now we have this rather useful function under our belts lets record our startUpTime.




static private ArrayList timeEventList = new ArrayList();
static private float startUpTime = GetTime();
static private float timeBetweenFrames;



That's about as much as we can do in the main Clock class for now. We need to identify a TimeEvent class to help us out before we can do much more. This class is only going to concern Clock so we'll give it it's own NameSpace for now just so it's tucked out of the way.



TimeEvent




Okay you know the drill new class new file all entitled TimeEvent!




namespace GameTime
{
public class TimeEvent
{
}
}



And in Clock.cs we'll add the line to the top using GameTime; and everything will be cool.We can rename this namespace when we pull it into the main code if it doesn't sit to well.




Okay there are quite a few variables to throw into one these things lets have look:




public class TimeEvent
{
public float timeLeft; //time before it's 'played'
public float birthTime; //what time it was made

public bool kill = false; //should it be removed?
public bool immortal = false; //never killed
public int lifeLeft; //Number of times to 'play' it

public delegate void Call();
public Call call;
}



The comments explain the top ones - the botton two are a way to store a Delegate - like putting a function into a variable. The only thing remaining to do is put in a constructor that will create a fully fleshed out TimeEvent for us.




public TimeEvent(int repeatNumber, float delayToCall, Call c)
{
call = new Call(c);
birthTime = Clock.GetTime();
lifeLeft = repeatNumber;
timeLeft = delayToCall;
}



Very simple - quite pretty. We also need one to create TimeEvents that are never going to be stopped.




public TimeEvent(float delayToCall, Call c)
{
call = new Call(c);
birthTime = Clock.GetTime();
timeLeft = delayToCall;
immortal = true;
}



That's TimeEvent pretty much finished - there is one last thing that I think might be useful to add but I think we'll put that in as need arises (ability to pause events for various game states)



Back to the Clock




Let's add a nice function to add our TimeEvents it should be simple and look something like:




static public void AddTimeEvent(TimeEvent timeEvent)
{
timeEventList.Add(timeEvent);
}



We don't want to just stick a timeEvent anywhere in the Array we want them sorted by the time to execute it. How do we do this? - with the IComparable interface. Seems we need to visit TimeEvent again.




public class TimeEvent : IComparable



To implement this interface you must hae using System; at the top of the code. Also we have to actually implement the function below:




public int CompareTo(Object rhs)
{

}



Let's use comparision information that's already avaliable to us.




public int CompareTo(Object rhs)
{
TimeEvent t = (TimeEvent)rhs;
return this.timeLeft.CompareTo(t.timeLeft);
}


Now let's hop back to Clock and put in the sort command.




static public void AddTimeEvent(TimeEvent timeEvent)
{
timeEventList.Add(timeEvent);
timeEventList.Sort();
}



Because we're creating a list that's always going to be sorted this will be very efficent and will save time each frame, which is the most important place to save time. I hope you can see why! (I'm not in a diagram mood today so I'm not going to explain :D)




Next we need a function that is going to look through our list of calls see which are ready to be executed and too execute them. Because of the sorting we know that as soon as we come across a call not ready to execute we can stop iterating through the list.




This is a reasonably scary piece of code. It could possible be broken down to be simpler but it does hold together as one process.





Check the first Time Event

If it's Time to execute it then call it's function
Then check if the the TimeEvent is immortal.
If it is then check the next TimeEvent in the list.
If not the reduce it's lifetime by one.
If it has no more lives then set it to be killed
Reset the birthtime.

If it's not time to call the TimeEvent
then stop looking through the list.



A bit of a mouthful there - let's see the code:




static private void ExecuteEvents()
{

foreach(TimeEvent t in timeEventList)
{
//Test if we should call this element
if (t.timeLeft < (GetTime() - t.birthTime)) { t.call(); if(!t.immortal) { t.lifeLeft--; if(t.lifeLeft == 0) t.kill = true; } t.birthTime = GetTime(); //reset to can be called again } else { //We have found a element that it is not time to execute //There is no point searching the list any longer return; } } }



We also need a function to remove the dead TimeEvents, this is a tricky function to do, because you have to transverse the same list that you wish to shorten. So it's requires a little bit of tricky and probably bad and dangerous (dangerous as in cause bugs, not as in blow up you and all those you care for, that's next lesson) coding.




static private void PurgeList()
{
int currentListSize = timeEventList.Count;
TimeEvent t;
for(int i = 0; i < currentListSize; i++)
{
t = (TimeEvent) timeEventList[i];
if(t.kill)
{
timeEventList.Remove(t);
currentListSize--;
}
}
}


Both these functions need to be called each frame. So we need a public function that will do just that for us, we'll call this Tick and hope we're not being too obscure.




static public void Tick()
{
ExecuteEvents();
PurgeList();
}



Okay we spent quite some time (far too much time) crafting this fine piece of code - without doing any testing whatsoever! So let's do that now.



Example Time




Let's knock up a quick example to make sure this all works. All we need is do some basic setup and make sure we have a function or procedure to call. Exampe.cs should look something like below and we can do a quick basic test.




using GameTime;

public class ExampleRun
{
bool go = true;
public void procedureOne()
{
Console.Write("Procedure One called!");
Console.ReadLine(); //wait for input
go = false;
}
public static void Main()
{
ExampleRun e = new ExampleRun();
Clock.AddTimeEvent(new TimeEvent(1,1000, new TimeEvent.Call(e.procedureOne)));

while(e.go)
{
Clock.Tick();
}
}
}



So after 1000 time units (milliseconds in this case.) procedureOne is called. This causes a small message to be displayed to the window and we have to press a key, then the program closes. All very neat.




Let's make the testing a little more thourough and add another procedure.




public void procedureTwo()
{
Console.WriteLine("Procedure Two called");
}

public static void Main()
{
ExampleRun e = new ExampleRun();
Clock.AddTimeEvent(
new TimeEvent(1000,
new TimeEvent.Call(e.procedureTwo)));


Clock.AddTimeEvent(
new TimeEvent(1,
5000,
new TimeEvent.Call(e.procedureOne)));
...etc


As expected


That seems to work pretty well. Everything is looking good so far.




It's worth noting I did not write this all off the bat in one go and have it work perfectly. It's some older code I had with a few modifications and even then I had to remove a bug or two - we're not 100% currently that it's bug free - we have not tried all possible uses.






The time class seems ready to add.
I'm going to take away the name space when I move it along so things are clearer.



Mix Thoroughly




You may have already guessed - but yes it's time to start a new project to mix these two bad boys up into a nice heady brew.




To start with we'll create a new project and just get copies of all our important classes in it. We can now add groovy things like animation ... but this means more drawing for me - more crappy drawing! For now I'll just do a simple down walk / run, then later if I decide this is a character I want to flesh out I may add more.



I'm an artist damn you - ARTIST!

Once again I cannot store the .tga anywhere so please convert the above.





As you can see I've done one 'run' like picture (raising shortening arms, moving head down a bit, adding a few shadows) then flipped it for the next frame and messed with the hair. I evenly spaced the images so that they are all central in their respective box areas. I did this by taking the two pixels seperating the eyes as the center line.




In the new project I've used Find and Replace to rename the classes from 'FirstStep' to 'GraphicsAndTime'. Everything is there and all the references and re-added to get everything working.




Once you have this working you will be served with the standard - Wonderful game screen and then the dropped into the game with the character waiting and can move if you wish. So we need to start fiddling to get the animation working - probably the best place to start is at the Actor class.



We're going to be using a bigger texture and we only want our intial vertices to cut out a bit of it! So let's go to OnCreateVertexBuffer we'll make a few changes to our texture coordinates T and U.




float oneQuarter = 0.25f;
verts[0].Tu = oneQuarter;
verts[0].Tv = 0; // Top Right Hand Corner

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

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

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


We're not being overly precise here because our image is very low resolution - the greater the number of pixels the more precision you would need. Okay we aren't loading the new texture yet so we get something like this:



Chop into bits! and moved around!


At this point you may say - wait wait 3 frames of images - yet the file is the size for four - what's the beef? Well the filesize is better if it's in sizes that are powers of two. Otherwise (as I just tried) things can go wrong, if you find things going wrong (coronas of white around your character for examples) you may wish to investigate file sizes!




Only showing a quarter of our man! As cool as it looks let's make it go back to normal by changing the texture we use. To do this we have to go to our PlayingGameState!




public PlayingGameState(Device d, GameStateManager g, DXInput.Device dInput)
{
device = d;
Tile.IntializeVertexBuffer(device);

gameStateManager = g;
inputDevice = dInput;

LoadTextures();
IntializeMap();

Player = new Actor(device, @"C:\playerrun.tga");
}


I've called my .tga file containing all 3 frames (plus the forth blank one), playerrun and put it in the root of C: please change this to suit your particular condition.




Okay everything is going pretty groovy our three or so frames are in there our man is moveable and fully formed - next we need to add a little animation! The only time we can use these particular frames of animation is when the player moves down. It is probably in our best interest to create a down function. When the player is moving we want to cycle between our two frames of animation - but when we are not moving we should go back to the standing animation. So we need to know if we are moving or if we are standing, some sort of bool is probaby a good idea. Before all this though let's tie the Clock in!



Setting up the Clock



static void Main()
{
... //various setup stuff// ...

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



Well that was easy, now the clock is ticking along each frame! This would probably be a good time to add the code to work out how long each frame takes and this can be used to form a crude FPS (you need high resolution timing to make a good one). Let's fiddle with the Clocks innards then - the Tick procedure.




static public void Tick()
{
ExecuteEvents();
PurgeList();
MeasureProgramSpeed();
}



Okay we've added a call to a function we have not written so let's write it!





static private void MeasureProgramSpeed()
{
timeBetweenFrames = GetTime() - timeOfLastTick;
timeOfLastTick = GetTime();
}




Okay see how we also need to add a variable called timeOfLastTick let's just check out how we have created that:




public class Clock
{
static private ArrayList timeEventList = new ArrayList();
static private float startUpTime = GetTime();
static private float timeBetweenFrames;
static private float timeOfLastTick = GetTime();



Looking through this function as well there are many places it can be optimized but that is a concern for when it is needed so I merely made some simple comments at the top, for when the game is finished or the need arises.




We are not going to do anything with that number for now. Infact it's tempting to make another 'Tick' function called 'TimedTick' that measures the speed and normally use the leaner one but this is again something we can come back to. When we can create text output to our game screen we'll map a function key that will bring us up a FPS type thing.



Back to the Player Class (Actor)




Here we want to create a down function that will be able to show our animation. So in the Actor class we add the following function:




public void Down()
{
posY -= 0.1f;
}



Then back over in PlayingGameState in the updateInput procedure we rewrite the down keypress like so:




if(state[DXInput.Key.DownArrow])
Player.Down();



Currently we have the frames of animation in the texture but we don't have any vertices mapped to them. So we'll create 8 more vertices - we're creating a 2D game we can really throw vertices away we never going to get to the amount that is handled in an actual 3D game. We do this in the IntializeVertexBuffer() procedure in Actor.




Currently the first bit looks like this:




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

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



Using a for statement we can make it so that the correct shapes are created automatically for any four- divisble number of vertices




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

int vertexNumber = 12;
CustomVertex.PositionTextured[] verts
= new CustomVertex.PositionTextured[vertexNumber];

//Create a player shaped rectangle with each four vertices
for(int i = 0; i <>


The second part is the textures - currently we can't just throw in any old for loop because we haven't really decided on a format for textures or animation frames, all we know is that they are probably going to have rows and columns and be in numbers of powers of two. Until we make some firm choice we may as well program this bit by hand. This means rather a large chunk of code:




float oneQuarter = 0.25f;//0.333f;
verts[0].Tu = oneQuarter;
verts[0].Tv = 0; // Top Right Hand Corner

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

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

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

verts[4].Tu = oneQuarter*2;
verts[4].Tv = 0; // Top Right Hand Corner
verts[7].Tu = oneQuarter;
verts[7].Tv = 0;
verts[5].Tu = oneQuarter*2;
verts[5].Tv = 1;
verts[6].Tu = oneQuarter;
verts[6].Tv = 1;

verts[8].Tu = oneQuarter*3;
verts[8].Tv = 0; // Top Right Hand Corner
verts[11].Tu = oneQuarter*2;
verts[11].Tv = 0;
verts[9].Tu = oneQuarter*3;
verts[9].Tv = 1;
verts[10].Tu = oneQuarter*2;
verts[10].Tv = 1;




The other thing we need to do is make sure the VertexBuffer when created is created for 12 vertices. So into deviceReset!




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



Okay everything seems good now - let's do a quick check by popping over to GameObject. Let's slide down to the render function and make the following change:




public void Render()
{

QuadMatrix = Matrix.Identity;
device.SetStreamSource(0, vertexBuffer,0);
device.RenderState.AlphaTestEnable = true;
device.RenderState.AlphaFunction = Compare.NotEqual;
device.SetTexture(0, texture);
QuadMatrix.Translate(posX,posY, 0f);
device.SetTransform(TransformType.World, QuadMatrix);
device.DrawPrimitives(PrimitiveType.TriangleFan, 4, 2);
}



Displaying a nice new frame


So that seems to work rather well. Currently though we have a problem - we need to put the offset variable somewhere sensible - it can only belong to GameObject so that's our only choice. Though it feels a bit like a waste because potentially many gameobjects will never be animated. Oh well maybe something that can be made more elegant in refactoring.




In GameObject we should the following to the variables there:




protected Matrix QuadMatrix = new Matrix();

protected int vbOffset = 0; //VertexBuffer offset to render from

public float posX, posY = 0; //DirectX Position i.e. middle of the screen



Now by toggling this offset we can make the character do things - dance if we so wish. (and where willing to draw out all the frames - 3D programming is better for some things!)



Beginning to move!




We need to come back to our 'Down' procedure in the Actor class now. Let's start with something simple just to get a feel of what's happening.




public void Down()
{
posY -= 0.1f;
vbOffset = 4;
}



Try it out and see how he moves!




if(vbOffset == 4)
{
vbOffset = 8;
}
else
vbOffset = 4;



This one is better note how he runs - woo! Currently he doesn't go back to standing annnd we haven't even touched time! Let's add another function:




public void Stand()
{
bvOffset = 0;
}



Now we should call this when we are getting no movement input - let's go back to PlayingGameState.




private void UpdateInput()
{
DXInput.KeyboardState state = inputDevice.GetCurrentKeyboardState();
if (state[DXInput.Key.Return])
{
/** Enter has been pressed **/
}

if(state[DXInput.Key.RightArrow])
{
Player.posX += 0.1f;
}
else if(state[DXInput.Key.LeftArrow])
{
Player.posX -=0.1f;
}
else if(state[DXInput.Key.UpArrow])
{
Player.posY += 0.1f;
}
else if(state[DXInput.Key.DownArrow])
{
Player.Down();
}
else
{
Player.Stand();
}
}



We change this function by putting in a lot of else statements but this could probably be done in a more elegant manner - storing the players previous position may be a good idea, then extra animation and such can be added.




Trying playing with it now - pretty cool heh?




It's awful fast though - we could do with slowing things down!



Slowing things down . . .




Okay to get everything working nicely we need to do some minor code rejigging. We're going to have the concept of Animation states and we'll have a function that checks the state and makes sure the character is doing what we want - let's start with these two.




public enum States
{
standing,
down
}

States currentState = States.standing;



Just defining our current states and adding a variable to hold the current state. Now we have a function that decides what should be done with all those states and their frames. (If we also recorded say previous states, we could also do funky transitional animations)



private void Animate()
{
switch(currentState)
{
case States.standing:
{
vbOffset = 0;
}break;

case States.down:
{
if(vbOffset == 4)
{
vbOffset = 8;
}
else
vbOffset = 4;
}break;
}
}



Looks pretty familiar so far. We need to remove any vbOffset information for our other functions so Stand and Down look like below - they now change the state.




public void Down()
{
posY -= 0.1f;
currentState = States.down;
}



public void Stand()
{
currentState = States.standing;
}




Okay that's all well and good but we need to be calling this Animate function - if we call it every frame we have the speed problem as before but if we call it less often then win win for us we get time controlled animation.




Of course there are potential difficulties if some character animations should be faster than others and if this is required then we will need to make this code more robust - but for now it will serve.




If you are wondering how we are going to call this function less often - well we're going to use Clock and TimeEvents of course. They save us plenty of hassle. So in the constructor for ACtor we'll add one new line and everything will slot into place.




public Actor(Device device, string texturePath) : base(device, texturePath)
{
IntializeVertexBuffer();

Clock.AddTimeEvent(
new TimeEvent(50, new TimeEvent.Call(Animate)));


}



So we're going to call animate every 50 miliseconds, seems to work okay. Of course you can alter this to your liking. Potentially we could add a few more functions to clock so we could speed it and up and slow it down on the fly. Hopefully you will remember from when we constructed the clock that we had too TimeEvent constructors one creating an immortal timeEvent and that is the one we're using here.




The speed of how fast the character moves down is currently independant and he just goes as fast as he can. We can alter this easily. Remove the postion altering stuff from the 'Down' function and put it into the 'Down' switch in the Animate function like so:




case States.down:
{
posY -= 0.1f;
if(vbOffset == 4)
{
vbOffset = 0;
}
else if(vbOffset == 8)
{
vbOffset = 4;
}
else if (vbOffset == 0)
{
vbOffset = 8;
}
}break;



I know that once you slow it down it looks like he's tip toeing really fast. I need a little sprite help :) At this point it's useful to play around with the speed and step size until something look right :) The most satisfactory I could get was setting posY -= 0.095f; and the call time to 90.




Animation speed and movement speed can easily be seperate it's just a question of creating sperate functions and hooking them to different TimeEvent. Let's do that now! First we need a function that will control movement depending on state - this is no problem:




private void Move()
{
switch(currentState)
{
case States.down:
{
posY -= 0.095f;
}break;
}
}



Then we need to add it to the clock queue and we're away. We'll set a faster call time and everything should work swimmingly.




public Actor(Device device, string texturePath) : base(device, texturePath)
{
IntializeVertexBuffer();

Clock.AddTimeEvent(
new TimeEvent(100, new TimeEvent.Call(Animate)));

Clock.AddTimeEvent(
new TimeEvent(45, new TimeEvent.Call(Move)));

}



Now the feel is much better! And feel is something that is always hard to define and program but there it's from playing it feels good.




This would start to look really good if I put in some time to draw more frames - but from this I'm sure you get the idea!



What's next?




We've put some pretty handy architecture / data structures in this time that allow us to easily take advantage of timing. With a little bit more code we got some nifty animation going.




  • The 'Actor' class could probably do with a little revision and seperate into an inheritance chain so more stuff can be created with less code.

  • Frames for the other directions and relevant code.

  • Player position in the game world - what tile is he on?

  • The above leads on to blocking of the player by walls, trees etc.

  • Map class - loading and saving + Texture class and Texture sets



Extras - adding more poorly drawn animation!




So I've drawn left and flipped this to make a right. This covers lot of our character movement, to some extent I've cleaned the are around the sprite too, so that it looks less like black flies are buzzing around it.




We have yet to approach texture sets and the like, the current one I'm using is 128x256. 256x256 will probably cover everything we need per fully animated sprite. For now though we're going to still put everything in by hand.




First we need to do the simplest excerise update the vertices - we now have 9 images, 9 times four vertices is 36. We'll need 36 vertices. Go to Actor and make the relevant changes!




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

...etc

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

int vertexNumber = 36;
CustomVertex.PositionTextured[] verts
= new CustomVertex.PositionTextured[vertexNumber];



For the actual image positions we need to input this by - note that we have changed the height as well so we must change all the current texture information.




The running images look incredibly poor at the moment due to my laziness :) He looks like he's on a skateboard :(




Well let's get this poorly crafted animation running anyway. The next thing we need to do is set the Vertices so they map o the textures correctly. All hard coded let's see the full listing of code. (you probably won't read this code so I must point out, that unlike before nearly every vertex needs to have some information entered. The amount the top vertices have to go down and the like.)




float oneQuarter = 0.25f;
verts[0].Tu = oneQuarter;
verts[0].Tv = 0; // Top Right Hand Corner

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

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

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

verts[4].Tu = oneQuarter*2;
verts[4].Tv = 0; // Top Right Hand Corner
verts[7].Tu = oneQuarter;
verts[7].Tv = 0;
verts[5].Tu = oneQuarter*2;
verts[5].Tv = oneQuarter;
verts[6].Tu = oneQuarter;
verts[6].Tv = oneQuarter;

verts[8].Tu = oneQuarter*3;
verts[8].Tv = 0; // Top Right Hand Corner
verts[11].Tu = oneQuarter*2;
verts[11].Tv = 0;
verts[9].Tu = oneQuarter*3;
verts[9].Tv = oneQuarter;
verts[10].Tu = oneQuarter*2;
verts[10].Tv = oneQuarter;

verts[12].Tu = oneQuarter*4;
verts[12].Tv = 0; // Top Right Hand Corner
verts[15].Tu = oneQuarter*3;
verts[15].Tv = 0;
verts[13].Tu = oneQuarter*4;
verts[13].Tv = oneQuarter;
verts[14].Tu = oneQuarter*3;
verts[14].Tv = oneQuarter;

verts[16].Tu = oneQuarter;
verts[16].Tv = oneQuarter; // Top Right Hand Corner
verts[19].Tu = 0;
verts[19].Tv = oneQuarter;
verts[17].Tu = oneQuarter;
verts[17].Tv = oneQuarter*2;
verts[18].Tu = 0;
verts[18].Tv = oneQuarter*2;

verts[20].Tu = oneQuarter*2;
verts[20].Tv = oneQuarter; // Top Right Hand Corner
verts[23].Tu = oneQuarter;
verts[23].Tv = oneQuarter;
verts[21].Tu = oneQuarter*2;
verts[21].Tv = oneQuarter*2;
verts[22].Tu = oneQuarter;
verts[22].Tv = oneQuarter*2;

verts[24].Tu = oneQuarter*3;
verts[24].Tv = oneQuarter; // Top Right Hand Corner
verts[27].Tu = oneQuarter*2;
verts[27].Tv = oneQuarter;
verts[25].Tu = oneQuarter*3;
verts[25].Tv = oneQuarter*2;
verts[26].Tu = oneQuarter*2;
verts[26].Tv = oneQuarter*2;

verts[28].Tu = oneQuarter*4;
verts[28].Tv = oneQuarter; // Top Right Hand Corner
verts[31].Tu = oneQuarter*3;
verts[31].Tv = oneQuarter;
verts[29].Tu = oneQuarter*4;
verts[29].Tv = oneQuarter*2;
verts[30].Tu = oneQuarter*3;
verts[30].Tv = oneQuarter*2;

verts[32].Tu = oneQuarter;
verts[32].Tv = oneQuarter*2; // Top Right Hand Corner
verts[35].Tu = 0;
verts[35].Tv = oneQuarter*2;
verts[33].Tu = oneQuarter;
verts[33].Tv = oneQuarter*3;
verts[34].Tu = 0;
verts[34].Tv = oneQuarter*3;



Yes lots of unpleasent hard coding we'll deal with that kind of thing soon enough.




So we now have all our frames sorted, now all that's left is getting the frames to play in order - this means we need to add a few states and alter the Animation and Move procedures like so:




private void Move()
{
switch(currentState)
{
case States.down:
{
posY -= 0.045f;
}break;

case States.right:
{
posX += 0.04f;
}break;

case States.left:
{
posX -= 0.04f;
}break;
}
}

public void Left()
{
currentState = States.left;
}

public void Right()
{
currentState = States.right;
}

private void Animate()
{
switch(currentState)
{
case States.standing:
{
vbOffset = 0;
}break;

case States.down:
{
if(vbOffset == 4)
{
vbOffset = 0;
}
else if(vbOffset == 8)
{
vbOffset = 4;
}
else //if (vbOffset == 0)
{
vbOffset = 8;
}
}break;

case States.right:
{
if(vbOffset == 12)
{
vbOffset = 16;
}
else if(vbOffset == 16)
{
vbOffset = 20;
}
else if(vbOffset == 20)
{
vbOffset = 12;
}
else
{
vbOffset = 12;
}
}break;

case States.left:
{
if(vbOffset == 24)
{
vbOffset = 28;
}
else if (vbOffset == 28)
{
vbOffset = 32;
}
else
vbOffset = 24;

}break;
}
}


Make sure you remember to put the new states in as well:




public enum States
{
standing,
down,
left,
right
}



Now the input needs to be linked to the result so we need to hop over to the PlayingGameState class and make a few changes.




private void UpdateInput()
{
DXInput.KeyboardState state =
inputDevice.GetCurrentKeyboardState();
if (state[DXInput.Key.Return])
{
/** Enter has been pressed **/
}

if(state[DXInput.Key.RightArrow])
{
Player.Right();
}
else if(state[DXInput.Key.LeftArrow])
{
Player.Left();
}
else if(state[DXInput.Key.UpArrow])
{
Player.posY += 0.1f;
}
else if(state[DXInput.Key.DownArrow])
{
Player.Down();
}
else
{
Player.Stand();
}
}



Now the player can walk well every direction but up, of course walking to the left and right he looks like he's serverely crippled but if you can draw better this is something you can fix.



Advanced



If you are looking to build upon this look at this later post for some ideas.

30 comments:

ZMan said...

Looking like a great tutorial. Lots of nitty gritty detail.

Linked from http://www.thezbuffer.com

Dan said...

Thank you :D

But no one was suppose to know about these yet :D

I'm slowly putting up a series I've written offline. The bits that are currently up were written in a haze of a caffine, with lots of ranting and plenty of missing grammar.

Also some of the formatting is messed up. Over the next week or so I'll be putting the rest up and correctly the older tutorials.

Feel free to make use of them in the mean time though :D

Steve Hoff said...

Just an fyi the method static private void PurgeList(), is incomplete.

Dan said...

Cheers I've corrected it and it should show up as soon as Blogger decides to refresh.

I also think there's a problem with OnCreateVertexBuffer - it's not finished either. For this though I'll have to check my old code files. It should be updated soon.

avsilver@hotmail.com said...

Thank you for putting this series online. I've found it useful and interesting, as I'm sure many have. The constant stream-of-consciousness: code; refactor; iterate to a better solution; is slightly tiring. I think it's interesting though as it highlights one of the common airisings in development & design: One doesn't know all the issues until the implementation begins. However, some of the decisions appear to be 'let's just get it working and see' - this sort of impatience (I suffer from it) can be counterproductive in terms of time spent. My own experience suggests: explore for the best solution each time - sometimes its implementation is no more difficult (eg hashtable vs arraylist) and can make the code much more elegant, or sometimes it can be a little more difficult, but makes things much easier later.
Ok, sometimes it's much harder and doesn't help much... this is something that comes with experience or from common sense I guess.

Anyway...

I'm not sure about this method of animating sprites (adding time events). E.g. a sprite in 'walk' state gets attacked. It might now want to go immediately to 'blow up' or 'defending' mode or whatever. However, there might already be an event in the Time list telling when the next animation occurs (and the sprite object doesn't know when that is). Thus need to provide a way of 'killing' events?

Possible alternative to have a maintained list of sprites, and call each sprite's Animate() method each frame (or less frequently), and they can choose whether to animate or not, based upon elapsed time. This is mildly less efficient - as every sprite now checks the time upon animate() being called, but perhaps avoids any timing issues, or a need to remove events when sprites no longer exist.

Dan said...

Yes there should definitely be a way to remove events! (and, of course, in my current codebase there is! :D)

These were the notes I made as I attempted to use C# and DirectX for the first time. I think I somewhat stretch the truth calling them tutorials but there aren't many resources like this. They are generally awful and most of them won't work with the latest SDK :D

I'm slowy (painfully slowly) going through them and neatening them up. I've done this with the first two - but now they need a little bit of changing, to work with the current SDK.

Currently I'm half way through a massive refactor in my own code :D but you only learn my failing!

Once my code base is stable again and I've checked all these tutorials I may expand them to create a small game. I guess it would be nice to show tutorials that have a pristine plan from the start but I'm not planning anything like this in the immediate future.

Thank you for the interest!

Anonymous said...

Hello! Great tutorials!

I keep getting an error when i compile with the code in TimeEvents , the (Call c) argument in the constructor keeps getting the error " denotes a field where a class was expected". For the life of me I can't seem to figure it out? Is there a mistype in the tutorial or am I just blogheaded?

Dan said...

Yeh seems to be a typo

public delegate void call();
public call Call;

should be


public delegate void Call();
public call Call;

I believe that will fix it. I'll update this now!

Dan said...

Gah sorry it should be:

public delegate void Call();
public Call call;

Okay now it should work!

Anonymous said...

hiya, awesome tutorials!

Anonymous said...

Hello again.

Thank you for the quick response to the Call c argument. I am not much of a programmer yet and had alot of troubles with that. Tried I thought every combo of upper and lower case to no avail. Your tutorials are great for learning C# and learning a nice beginning block for biulding a game. I haven't programmed since Basic was the whip language, things sure have changed in the world , for the better definately. I have bought a few books to help me get back into it but they can't seem to explain things too well, you on the other hand are a very good teacher.Thank you again for a set of great tutorials, and they just happen to be about my interest area of game programming to boot!

kportertx said...

HUH... ... Either I’m getting too tired to operate this pc or there are some serious problems with the clock and timeevent classes...

Well... I guess sleep may be needed. :P.

O... I just read that these are not complete... Is this still the case?

If so O well it still the best I have come across as you said most OTHER tutorials do not work with the latest SDKs. I have looked at about 20 or so only to find they are out dated, and will not compile... even a tutorial Microsoft recommended...

BTW it would be a shame if you didn't find a better host for this information. Depending on your intentions with this document... I might tempted by Steve's 100mb offer. Heh cant beat free :)

Again Thanks for the awesome material

Dan said...

I think the clock and time event classes are fine.

I also think that I stuck a link at the end to another blog page where I extended them a bit.

Strangely enough I was playing with these classes today. I put them in a dll because I find them quite handy for a small mess-about projects.

But this set of tutorials is entirely complete - but not all up to date with the latest SDK, not all formatted nicely and not all checked and corrected :D

Anonymous said...

I am having a problem with these 2. no where in all the tutorials do you mention Player being defined so im trying to figure out how to make anything that has to do with PLayer to work i've tried Actor as well and that did not work either.

if (state[DXI.Key.DownArrow])
Player.Down();

Player = new Actor(device, @"C:\playerrun.png");

Dan said...


Now we make a new class called Actor which inherits from GameObject and we'll store everything in here and finally encapsulate our sprite. So same ol' new class new .cs file.



public class Actor : GameObject
{
public Actor(Device device, string texturePath) : base(device, texturePath)
{

}

public override void IntializeVertexBuffer()
{

}

}


Actor is defined here: http://einfall.blogspot.com/2005/02/walking-first-steps-architecture-is.html

In this particular tutorial it's intialized here


public PlayingGameState(Device d, GameStateManager g, DXInput.Device dInput)
{
device = d;
Tile.IntializeVertexBuffer(device);

gameStateManager = g;
inputDevice = dInput;

LoadTextures();
IntializeMap();

Player = new Actor(device, @"C:\playerrun.tga");
}


In most browsers you should have search function. I'd used that to pick up to these two!

Anonymous said...

i have everything u said the problem is in order for Player to equal something it has to exsist and it doesnt, is all that exsists is actor and u just told me what i allready know lol you cannot have player = new actor if player doesnt even exsist....im using visual studio 2005 if that means anything...i've gotten everything to work up untill now though.

These are all thats defined in PlayingGameState.cs for me which is where u put the Player = new Actor part

private ArrayList Map;
private Texture grassTexture;
private Texture stoneTexture;
private Device device;
private GameStateManager gameStateManager;
private DXI.Device inputDevice;

In order for player to equal something it has to exsist alleady. It has to be something. you cant just say Player = something unless player allready exsists. For Example:

private Device device;

public PlayingGameState(Device d, GameStateManager g, DXI.Device dInput)
{
device = d;
the only reason device can = d is because its allready defined, where is player defined?

Dan said...

I confess it's been a while since I've written these and source code is backup somewhere.

So I'll guess put it in PlayingGameState

Probably:

protected Actor Player;

Anonymous said...

ok thank you i got it working also at the bottom inside Render() i had to do this for it to render.

//Actor a = new Actor(device, @"C:\sprite.png");
//a.Render();
Player.Render();

once again thanks...i prolly would of givin up if it wasnt for u lol.

Zi said...

hi i am having extremely major issues with the movement and vboffset is there anywhere i can download the source code up untill now? when i try to run it with everything this part of the tutorial says. i can only move up and the picture is huge and only part of his face. this happened after i put
int vertexNumber = 36;

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

and i also put all of those new vert coords. everything worked fine up untill now. for some reason it seems like the movement made from the states never change like they are suppost 2. its like its stuck in states.standing.

Anonymous said...

Hi very nice , but im having a problem with animation. The vbOffset variable, when i put it here

...
device.DrawPrimitives(PrimitiveType.TriangleFan, vbOffset, 2);
...

(I put it there beacouse this isn't written what to do with it) So when i do so my character is appearing not in one place. It looks like the whole image with 4 states is being moved down, and there is only changing the part which is displayed. The down movement looks like this :

0... - here character is standing

then we start moving down and it looks like this :

.0..
..0.
.0..
..0.
.0..
0... - here we stopped

Can u tell me what i do wrong

Anonymous said...

I've stumbled on some problems on my way here, but fixed most of them myself and found some answers on other sites. This one is a tricky tho. 2 Problems:

1:
I Don't get how that forloop of yours should be in OnCreateVertexBuffer()
because you end writing in the middle of it.

2:
I dont understand where you actually use the vbOffsett variable that we change so much?

probably the same issue since the offset is probably used here but I need help getting this to work.
Thx

Anonymous said...

i was wondering how i would go about character movement if i wanted to move one tile at a time on each key press and not have continuous movement

Dan said...

You could increment the character X and postion by the tile length / height

Hom said...

Hello !!
Excellent tutorials BUT I have a great problem :/
There isn't the end of the function :
[quote]


private void OnCreateVertexBuffer(object sender, System.EventArgs e){ VertexBuffer buffer = (VertexBuffer)sender; int vertexNumber = 12; CustomVertex.PositionTextured[] verts = new CustomVertex.PositionTextured[vertexNumber];//Create a player shaped rectangle with each four verticesfor(int i = 0; i <>
[/quote]
Have you got the "suite", please?
Again : gret tuto !! :)

Dan said...

Woah sorry about that. It seems other people have mentioned it too but I've been totally oblivious. Erm I'll check my source.
Maybe later today or this weekend I'll have it fixed up.

Hom said...

Thanks !!
But I have found it via google (this is strangy but it worked with the same adresse but in different style). So the end is :
[CODE]
for(int i = 0; i < vertexNumber; i+=4) {
verts[i].Position = new Vector3(0.25f,0,1f);
verts[i+1].Position = new Vector3(0.25f,-0.50f/2,1f);
verts[i+2].Position = new Vector3(0,-0.50f/2,1f);
verts[i+3].Position = new Vector3(0,0,1f);
}
[/CODE]

Hom said...

mmm, it's me again, I have a problem with the image "contour".
Here is the screenshot : http://frontmission.free.fr/img/tuto_img1.gif
It's the same problem when I use a .tga or a .png file. I just don't find any solution.
It would be great if someone can help me :)
++

Hom said...

Sorry, it's me again. I just to others to know how they can handle this :
With Potoshop, edit the "Image mode" into "indexed colored". Then in the Dialogbox choose in the "Cache field", black (or try another depending on your version). Then save it as a .png and it will works perfectly.

Sorry again to spam !!

wow power leveling said...

Day night,gold für wow the moon or on world of warcraft gold the tree,cheap wow gold Hao Jie pouring down the moonlight, as if accompanied by Xiaotu Feifei enter sweet dreams. In the dream, a dream Feifei about his sister to the moon night. Will open the door,wow gold kaufen go down the moon sister.mesos Xiaochanzouxia take is that they did not see the moon sister. At that time, anchored at the tree on the moon sister saw Xiaochan, they yelled loudly: "Feifei, Feifei, I tree, the tree, I." Xiaochan sit at the moon to his sister, who Daizhaoxiaochan came wow geld to the beautiful pond. Only, water,maple story mesos everywhere in the lush leaves and beautiful flowers.maple story items A frog squatting lotus leaf, see Xiaochan, surprised and said: "Xiaochan,wow gold farmen you can even sit on the moon. You simply It's amazing!maple story money I am sure that you are the first animals to the moon by the animal. good,wow leveling I envy you!Maple Story Accounts "Xiaotu listening, happy to smile. Then, with the moon sister Xiaotu to its home.powerlevel Only, the moon sister's home stars are everywhere. The eyes of a star a Zha Zha,world of warcraft power leveling like Xiaotu greeted the arrival of a mouth, like: "Xiaochan, Hello, we at the Moon Palace waiting for your arrival."maple story powerleveling Xiaotu listened

Anonymous said...

I can get twelve sky Gold cheaply,
Yesterday i want to buy 12sky goldfor my brother.
i hope him like it. i will buy twelvesky Gold for him
as birthday present. i like the 12Sky Silver Coins very much.
I usually buy 12 sky gold and keep it in my store.