Search ObeyBrew.Com

OBEYBREW.COM | Tutorials | A Crash Course In HuC Part 5

A Crash Course In HuC

Part 5 - Animaniacs

A game would look pretty bad without animation. We're going to cover one of the several methods of animating sprites in this tutorial, plus get into some more specifics about some of the functions we've been using so far.

What You'll Learn

Sprite animation. That's pretty much it.

Getting Prepped

You'll need the sourcecode from the previous tutorial for this one, as well as scene.pcx. We're adding a new sprite though, so we won't need bonk.pcx anymore. Here's the file required for this tutorial. Clicky!

Setting Up Our Program

Since we're building off of tutorial 4's code, we're already set up. Now let's get into the new stuff!

Monkey With The Code

First things first... let's import our new sprite. It's four frames of animation. Find the following line:
#incspr(bonk, "bonk.pcx", 0, 0, 2, 2);
Change this line to:
#incspr(bonk, "bonkwalk.pcx", 0, 0, 2, 8);
Be sure to change the relevant palette line to the new pcx file as well. Now we're importing 8 sprite blocks high. Next, we're going to change the following line:
load_vram(0x5000, bonk, 0x100);
to this:
load_vram(0x5000, bonk, 0x400);
Now it's high time I explain what load_vram's last parameter means. As you've probably already figured out, load_vram's first parameter is the VRAM address and the second parameter is the graphic to load. The third parameter is the amount of data to load. Each 16x16 sprite block requires 0x40 VRAM words. When we loaded 0x100 VRAM words, we were loading that whole 32x32 sprite. Now, however, we're loading 4 sprites at a time at 32x32 each. So, since 1 32x32 sprite is 0x100 VRAM words, 4 of them are 0x400 VRAM words. Savvy?

Now, we have one more change to make. On our spr_make line, we're setting the VRAM address to 0x5000. If we leave it like that right now, Bonk will appear mid-walk. That looks kinda goofy so change it to 0x5100. Compile and run, and you'll notice an important detail... if you can see the border of the screen, the so-called "overscan region", you'll notice that it's bright pink. Wtf? There's a very good explanation for this... Color index 256, or sprite palette 0 color 0, controls that region's color. We can make it black again in the code by making color 256 equal to zero after loading Bonk's palette. Remember how?

C Bonk, C Bonk Walk

We're going to go with a very basic, but drawn-out, method of animation using a few if lines. Note that although this method is primitive and drawn-out, it is also amazingly fast. The smallest code isn't always the most efficient.

First we will need two new char variables. We'll call them tics and frame. Set both to 0. Next, we're going to expand our movement code to allow for animation. This is where things get interesting. Add the following line to the source code in both the JOY_LEFT and JOY_RGHT if blocks:
tics++;
This is what is known as a tic counter. A tic counter is a variable that keeps track of time between frames of an animation. So now, when we move, this tic counter is incremented but not incremented when we're not moving anywhere.

Now, the tic counter is pretty useless if we don't do something with its value, so then here's what we do... we wait until tics reaches a certain value. When it does, we reset it to zero and increment frame. At this point, a new animation frame is shown. When all of the frames have been shown, frame also resets to zero and the cycle starts over again. These are the basics of sprite animation.

Add this code right before the satb_update:
if(tics > 4)
{
	tics = 0; frame++;
}
So we've added a new if block for tics. frame is now incrementing every 5 tics of movement. So what do we do with frame? Well, this is where our animation comes in. Right before this if block ends, we're going to add a number of new if lines to tell the sprite which frame to show. Again, this is not always the best way of doing this, but it does work and it is fast.
if(frame > 5) frame = 0;
if(frame == 0) spr_pattern (0x5100)
if(frame == 1) spr_pattern (0x5200)
if(frame == 2) spr_pattern (0x5300)
if(frame == 3) spr_pattern (0x5200)
if(frame == 4) spr_pattern (0x5100)
if(frame == 5) spr_pattern (0x5000)
Compile and run. Pretty sweet, eh?

What Else You Can Do

There's another major way to do this which involves using load_vram and just uploading the frame you need to the VRAM address. This saves VRAM, but it can slow down your program if used too much. It's a tradeoff between speed and memory conservation. Of course, you can utilize both techniques, as many games do.

I Need To Take A Break

Okay so this has been pretty cool so far, right? Well, we're going to dive into one more detail in this tutorial which is to cover another input method. Start by creating a new int variable. Call it j2. Then, right after your joy line, add this code:
j2 = joytrg(0);
joytrg, as opposed to joy, returns new keypresses since the last VBL. Because of that, it's great for things that are meant to be pressed only once, such as an attack button or setting up a Pause function. In fact... that's what we're gonna do here... make a pause function. And we'll make it as a custom function of our own.

Add the following code after your direction-checking if blocks:
if(j2 & JOY_STRT) pause();
Now, we're using joytrg to check to see if the Run button has been pressed. If it's pressed, we'll call a new function called pause... which we've yet to write. I guess we'd better get right on that right now.

We can add the following function to our source code. Remember that functions are always outside of other functions, never inside of them.
pause()
{
	vsync();
	for(;;)
	{
		if(joytrg(0) & JOY_STRT) return;
		vsync();
	}
}
Note the vsync at the top of the function... this is to wait a full VBL before starting to check for the keypress. If we don't do this, there is a high chance that we will still be in the same VBL as the button press that brought us here, and it'll exit early. That's not what we want, so we wait one VBL before checking. Also note that we're using joytrg directly here; since we're only checking for one keypress, no need to set up a variable for it. If Run is pressed, we use the classic C return to break out of this function... otherwise, we just wait for a VBL. This is about as basic as pause routines get.

I wrote the pause function this way to emphasize the importance of the VBL in relation to joytrg. However, if you feel the need to optimize this function, you can actually get rid of the initial vsync call and swap the lines inside the infinite for loop.
pause()
{
	for(;;)
	{
		vsync();
		if(joytrg(0) & JOY_STRT) return;
	}
}
It works the same way with slightly less code.

So... I said I'd clear up the meaning of j1 before, now didn't I? Well, here's what... the "j" just means "joypad". So "j1" would just mean "joypad input 1". When we add "j2", it's like saying "joypad input 2". This is not a detail that is set in stone... it's just an easy way of remembering which particular input method we need for a particular segment of our game engine. Call it "fishmonkey90210" if you want, as long as you remember what fishmonkey90210 actually does. :) When I make a game with more than one input, I suffix "j1" and "j2" with the player number. So, I end up with "j1p1" and "j2p1" for player 1, "j1p2" and "j2p2" for player 2, and so on.

So... What's Next?

Hell, this is almost a playable game! Well okay not quite but you know... it's closer than before. So where do we go from here? Well, in the next tutorial, we'll start looking more into background graphics... it's hard to make a real scrolling game without a level to run around in. So we're going to start looking into HuC's map and tile functions. There will be a lot of information to cover... be prepared. :P

Full Program Listing

#include "huc.h"

#incspr(bonk,"bonkwalk.pcx",0,0,2,8);
#incpal(bonkpal,"bonkwalk.pcx");

#incchr(scene_chr,"scene.pcx");
#incpal(scene_pal,"scene.pcx");
#incbat(scene_bat,"scene.pcx",0x1000,32,28);

main()
{
	int j1, j2, bonkx;
	char tics, frame;
	bonkx = 104;
	init_satb();
	spr_make(0,104,153,0x5100,FLIP_MAS|SIZE_MAS,NO_FLIP|SZ_32x32,0,1);
	load_palette(16,bonkpal,1);
	set_color(256,0);
	load_vram(0x5000,bonk,0x400);
	satb_update();
	load_background(scene_chr,scene_pal,scene_bat,32,28);
	for(;;)
	{
		vsync();
		j1 = joy(0);
		j2 = joytrg(0);
		if (j1 & JOY_LEFT)
		{
			tics++;
			spr_ctrl(FLIP_X_MASK,FLIP_X);
			if (bonkx > -8) bonkx--;
		}
		if (j1 & JOY_RGHT)
		{
			tics++;
			spr_ctrl(FLIP_X_MASK,NO_FLIP_X);
			if (bonkx < 232) bonkx++;
		}
		if (j2 & JOY_STRT)
		{
			pause();
		}
		spr_x(bonkx);
		if (tics > 4)
		{
			tics = 0;
			frame++;
			if (frame > 5) frame = 0;
			if (frame == 0) spr_pattern(0x5100);
			if (frame == 1) spr_pattern(0x5200);
			if (frame == 2) spr_pattern(0x5300);
			if (frame == 3) spr_pattern(0x5200);
			if (frame == 4) spr_pattern(0x5100);
			if (frame == 5) spr_pattern(0x5000);
		}
		satb_update();
	}
}

spr_make(spriteno,spritex,spritey,spritepattern,ctrl1,ctrl2,sprpal,sprpri)
int spriteno,spritex,spritey,spritepattern,ctrl1,ctrl2,sprpal,sprpri;
{
	spr_set(spriteno);
	spr_x(spritex);
	spr_y(spritey);
	spr_pattern(spritepattern);
	spr_ctrl(ctrl1,ctrl2);
	spr_pal(sprpal);
	spr_pri(sprpri);
}

pause()
{
	for(;;)
	{
		vsync();
		if (joytrg(0) & JOY_STRT) return;
	}
}

See also

A Crash Course In HuC - Part 6 | joytrg
This site is ©2013 Eponasoft. Obey daily.