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.
Post a Comment