Tuesday, December 7, 2010

2D Games with Kitten-Fu, Part Two

When we left off in part one, you should have had a kitten that skated across the screen forward and backward. This would be awesome for a hover ship, but let's animate our little cat so that its legs move while it is walking. (oh, I'm also using the KFu alpha 13 library now)

The Sprites

We're going to use a sprite sheet for our cat, so that means adding all of our cat's various poses to a single .png file. I'm going to limit myself to 16x16 for each of the kitten's poses, that way I don't have to keep track of what size I made what, and it helps with the retro look too. Kitten-Fu allows you to specify any size you choose, just make sure you keep good track of your sprites if you make them irregular.

You can see four poses of my cat here. The first is the original position from part one, the second is with the right legs extended, the third is with all four legs in the middle. I was purposely vague on which leg was in front for this middle pose so that I didn't need to draw a second center pose. The last position is with the left legs extended. It took me quite a while to get the animation correct. If you are drawing your own sprites, I would recommend that you get the basic shape you want, then code the animation sequence, and then fine tune it when you can see the changes in action. I used a couple of YouTube videos to help me get the leg shapes correct.

My first attempt at animating my cat was in the middle of the night while I was almost asleep. I'm including it here as an example of what not to do!

The Code

So far we've only been working with a KFu stamp, and we really haven't tapped its full potential at all. One of the cooler things a stamp is good for is making "slices". A slice is a piece of a stamp that is cut out and used as a sprite the same way we used the stamp in part one. What makes a slice special is that it doesn't take up more space in memory to use it. We can load one sprite sheet, and then cut multiple slices from it, creating a veritable cornucopia of sprites on the actual screen!

After we add the new kitten poses, we can leave our stamp code the same, but we're going to add our slice code underneath:

  slice kitt1stand(kitt1, 0, 0, 16, 16);

We'll also want to change where we put the kitt1 stamp to refer to the slice instead:

  kitt1stand.put(k1x, k1y);

Now we're going to use an array of slices for our animated kitten. An array is a way to give a list of items numbers rather than names--this can be quite handy as we will see in a moment. In this case, we're going to pre-load all four frames of our animation, and save them as slices.

  slice* kitt1walk[4];
kitt1walk[0] = new slice(kitt1, 16, 0, 16, 16);
kitt1walk[1] = new slice(kitt1, 32, 0, 16, 16);
kitt1walk[2] = new slice(kitt1, 48, 0, 16, 16);
kitt1walk[3] = new slice(kitt1, 32, 0, 16, 16);

You can see that for each slice above (for the animation and the first pose in kitt1stand) there are a couple different parts:

kitt1stand or kitt1walk[x] are both slice names that we use later to call them into use, and after that are our options in the parentheses. kitt1 refers to the stamp that we set up earlier on line 12 of part one. The series of numbers refer to the x-position and y-position (the top left corner of the slice), and the height and width of the slice. Of course the slices could overlap if we wanted them to, but for now that would make our cat look like a mutant.

We can't just replace kitt1stand with kitt1walk, otherwise our kitten would be animated even while it was standing still, so we need a way to tell if the cat is moving, and only show the animated slice array while that is true.

We already have a walkleft and a walkright variable that is set to true while the cat is moving, so we can just multi-purpose them! The way we're going to solve our animation problem for now is to set a counter that will rotate through the numbers 0-3 every x frames. We'll feed this number into the kitt1walk array and this will flip between all of the slices of the array in order. Add the following code right before you clear the display and begin painting. (and, don't forget to add any relevant variable declarations at the top of the program!)

if (framecount%8 == 0) {
if (anim4 >= 4) {
anim4 = 0;

Then, to make the counter tick, add the following to increment the frame counter every time through the game loop (I added it directly underneath the GAME LOGIC heading.)


We can re-use this counter for anything that we want to also run with four frames, as long as we don't mind that it is running in tandem with our cat.

Now that we've set up our animation, let's get it painted to the screen. Add a /// PAINTING /// heading underneath the animation code, and let's add some if statements around where we put kitt1stand. Basically, if walkleft or walkright equal 1, then we display kitt1walk, otherwise we display kitt1stand. I could write out the code for this a little more compactly than I have it below, but since we are about to add flipping, this sets up our statement nicely for that:

  if (walkleft == 1) {
kitt1walk[anim4]->put(k1x, k1y);
} else if (walkright == 1) {
kitt1walk[anim4]->put(k1x, k1y);
} else {
kitt1stand.put(k1x, k1y);

If you make your program at this point, you'll notice that the cat, although animated, moves way too fast (even for a ninja cat.) We need a way to slow that kitten down. How about a throttle on the game process? every x frames, we will register a movement. If we were working with smaller pixels, this would be less of a problem, but when your cat moves at 30 pixels per second, and your screen is only 128 pixels across ...

In the GAME LOGIC section, underneath the frame counter, let's stick our movement controls into a throttle of sorts:

  if (framecount%2 == 0) {

Now when you make the file, the cat walks slower but, especially if you're on an older / slower computer, you'll notice that the cat fluctuates in speed depending on your CPU usage. That's because I forgot that we need to use KFu's FPS thingy. To set it up, we need to add the following lines to the top of our actual game code, right after we finish declaring our variables and slices.

  fps framerate(1000/30);

Then, at the bottom, right before we flip to the screen, add the following:


we could have named framerate anything, and set the speed to anything we wanted. I can imagine that you could change this number for underwater scenes, or something like that.

Everything should work now except for the fact that your cat can only face one direction! Let's fix that (although, the moonwalking kitten is pretty cool.)

KFu already has options set up to allow for flipping, all we need to do is activate them. First, we need to create a flipped "instance" of our stamp. This creates a complete copy of the stamp in the memory, so don't flip or rotate more than you'll actually need for your program.

Your new stamp declaration should look like this:

  stamp kitt1("kitten1.png", KFU_LOAD_FLIPH | KFU_LOAD_NORMAL);

FLIPH is for flip horizontal, and then NORMAL is so you can load the regular file. If you don't declare any positional variations, KFu assumes you only wanted NORMAL, but as soon as you start declaring variations, you have to tell it exactly what you want.

Do you remember where we made our if statement a bit long winded for our cat animation? Change your slice code to the following for when the cat is walking right:

    kitt1walk[anim4]->put(k1x, k1y, KFU_PUT_FLIPH);

Now, the only remaining 'error' is when your cat stops walking, it always faces left ... Using a variable on the key presses, see if you can flip kitt1stand to face the correct direction after he finishes walking. I have a solution in the code included at the bottom of the post--see if you can figure it out without looking.

So, I lied about showing you how to add another cat, that will have to wait until part three. We'll create a kitten object, and work on the logic that will make kitten-2 follow kitten-1, as well as some grass for them to walk on.

The Files

You can download everything from part 2 here: kittens-02.tar.gz

* * *

No comments: