Search ObeyBrew.Com

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

Document is being finalized. It may contain flaws. This message will be removed when finalization is... well, finalized.

A Crash Course In HuC

Part 7 - The State Of The State

This particular tutorial is less about HuC and more about technique. By the end of this tutorial, you will have an almost functional game engine. Get ready!

What You'll Learn

Today, we introduce scroll wrapping and state machines. scroll wrapping is when the map scrolls beyond the range of the virtual window. 'Huh?' Well, the virtual window is normally 64x32 unless we make it something else, and yet we're going to be using a map that's much larger. By reloading portions of the virtual window, we're able to show more than normal. And state machines are wonderful pieces of code that make creating characters a complete breeze! Okay, not a real breeze... there's a lot of work involved, but as you will see, they are both flexible and powerful!

Getting Prepped

We're starting another new source this time but, as before, keep the previous sources handy to copy some stuff. Also, we're using completely new files this time... ole Bonk's getting a powerup!

Bonk's facelift

Let The Battle Commence!

Okay... this one's going to be huge. You will definitely want to practice your commenting here!

The first thing we do, of course, is set up the #include and main function. Then, we're going to load our shiny new Bonk sprite.
#incspr(bonk, "bonksprites.pcx", 0, 0, 2, 18);
#incpal(bonkpal, bonksprites.pcx);
Yes you see that right... 18 sprite blocks high, or 9 32x32 frames of sprites. Think that's a lot? Think again! The real game uses way more frames than this... we're only scratching the surface of the surface! :)

Now, we will also load our spiffy new game world and tiles:
#incbin(levelmap, tut7map.fmp");
#incchr_ex(leveltiles, "tut7pal0.pcx", 0, 0, 20, 7, 0,\
			"tut7pal1.pcx", 0, 0, 20, 2, 1,\
			"tut7pal1.pcx", 0, 16, 11, 1, 1);
#incpal(levelpal0, tut7pal0.pcx");
#incpal(levelpal1, tut7pal1.pcx");
#incpal(levelpal2, tut7pal2.pcx");
If you open tut7map.fmp in mappy, you'll notice that it's quite long... well okay, not as long as it could be, but that's got some length!

Alright, so them's the basics. But now, we need some additional things before we dive into function code. We're first going to set up a bunch of #defines for later use.
#define DIR_LEFT 1
#define DIR_RIGHT 2
#define SCR_W 33
#define SCR_H 32
#define CHAR_LEFT_THRESHOLD 96
#define CHAR_RIGHT_THRESHOLD 128
Yes, these all have a specific purpose. DIR_LEFT and DIR_RIGHT will later tell us which direction Bonk is headed in. SCR_W and SCR_H will be used for the scrolling code to tell it where to place new tiles. Finally, CHAR_LEFT_THRESHOLD and CHAR_RIGHT_THRESHOLD will be used later on to determine when to scroll the map. Everything will be explained in detail in due time, and of course we need variables. Most of these will be int this time around.
int bonkx, bonky, j1, j2, mapx, mapy, vmapx, vmapy, lastmapx, lastmapy, t3, jumpspeed, jumpdelta;
int MAP_X_THRESHOLD, MAP_X_LOW_THRESHOLD;
char tics, frame, state, direction; 
You know what bonkx is. Now, we're also going to add its counterpart, bonky. Why? We're going to make Bonk jump! j1 and j2 will be used a lot too. jumpspeed and jumpdelta are also used for Bonk's jumps. The rest of the int variables are part of the map scrolling code... yeah, it needs a lot of numbers to crunch. Of course, our chars come back to us, and joining them now is direction, which will tell HuC which direction Bonk is moving in.

Finally, let's re-copy some old source into this new program. spr_make and the original pause function from tutorial 5 will do nicely here.

The Bonk Is Back!

We'll be using some familiar code to set up the game engine. Right inside your main function, we'll take care of some init stuff.
init_satb();
tics = 0;
frame = 0;
bonkx = 104;
bonky = 153;
spr_make(0, bonkx, bonky, 0x5100, FLIP_MAS|SIZE_MAS, NO_FLIP|SZ_32x32, 0, 1);
load_palette(16, bonkpal, 1);
set_color(256, 0);
load_vram(0x5000, bonk, 0x900);
satb_update();
Yep, that should get Bonk onto the screen. Notice how we used variables to set his position on the spr_make line rather than literals.

And yes, of course, we need a game world to play in. However, it's going to be a little different this time.
MAP_X_THRESHOLD = 1055;
MAP_X_LOW_THRESHOLD = 0;
set_map_data(levelmap, 164, 28);
set_tile_data(leveltiles);
load_tile(0x1000);
load_map(0, 0, 0, 0, 34, 28);
load_palette(0, levelpal0, 1);
load_palette(1, levelpal1, 1);
Most of this you already know. However, notice that we did not load the whole map this time on the load_map line. If we did that, HuC would barf up a lung... okay it wouldn't really but it might be cool if it did... really though, it just won't work properly. Also, we're setting threshold values here... these are the limits of the game world in pixels. You'll see what those mean in a bit.

Now, we have some variables to initialize.
mapx = 0;
lastmapx = 0;
mapy = 0;
lastmapy = 0;
vmapx = 0;
vmapy = 0;
Although we really don't have to do this in HuC, it's always a wise idea to initialize variables... once you get into more complex programs, you'll thank me for drilling that wisdom into your head. :P

And finally... the game loop. It's going to be really simple this time, as the rest will be handled by custom functions.
for(;;)
{
	j1 = joy(0);
	j2 = joytrg(0);
	if(j2 & JOY_STRT)
	{
		pause();
	} else {
		bonk_state_machine();
	}
	satb_update();
	vsync();
}
Again, you should pretty much know what all of this does. However, we're adding a new function call here... bonk_state_machine. That's going to be a custom function, so you can create it now if you'd like. At this point you can compile and run the program to make sure everything works so far. For now, it won't do much... but we're going to get into the fun stuff next.

State A Fact

So then... state machines... what are they? Well, in the simplest terms, a state machine is a subprogram that controls an entity. In this case, our state machine will be a collection of functions that control Bonk. practical examples of state machines in the real world might include your car... the gear shift could be considered a state machine, as it can put your car in park, neutral, drive, reverse, etc. Each of those would be considered a state controlling the car, which would be considered the entity. Also you have what are called state controllers... these are things that can alter the state, or change it outright. The gear shift itself would be the largest state controller, as it can directly change the state of the car. However, the acceleration pedal and the brake pedal also alter the state of the car, and affect the conditions in which the gear shift can change the car's state. For instance, you can't shift from park to drive without pressing the brake. In drive, the accelerator controls the speedup of the car, thus altering the car's current state but not changing its state. Confused yet? Sorry if so.

What The Func?

So then, we're going to start writing Bonk's lil state machine. The first thing we need to know are what states Bonk can be in. For this tutorial, we're going to have a total of seven states: idle, walking, about to jump, jumping up, falling down, landing, and bonking! We'll start with the basic one: idle. Add this code to the bonk_state_machine function:
if(state == 0) bonk_state_0();
State 0 is Bonk's 'idle' state. So now, we're looking for another function. Let's create it now. You remember how to create a new function, right? So now we can fill in Bonk's idle state. Now... his idle state is just a single frame so we don't need to do much in terms of animation. So we're just going to do this:
if(tics == 0)
{
	spr_pattern(0x5100);
	tics = 1;
}
This tells us 'when the tic counter is at zero, set Bonk's idle frame but just do it when tics = 0, otherwise make tics 1 so it's not reset every frame'. That's all there is to that part. But hey... in his idle state, what can he do? Right now, all he can do is just stand there. So, we need to add some fun new features... state controllers.

First things first... let's make him bonk!
if(j2 & JOY_B)
{
	/* set bonk in bonking state */
	tics = 0;
	frame = 0;
	state = 6;
	spr_pattern(0x5700);
}
Pressing II (or B, in huc.h terms... yeah, I don't know why they did it this way either) in this state will put Bonk in state 6, which is his bonk state. Notice that we are also going to set the initial frame for his bonking animation. We don't have to do this, but because our animation set is so limited, we can get away with it. I guess we should fill in the bonk state, right? Yes, we should! So, we add a new if statement inside bonk_state_machine:
if(state == 6) bonk_state_6();
So then, we can now create bonk_state_6. It will be quite simple, really.
bonk_state_6()
{
	tics++;
	if(tics > 5) spr_pattern (0x5800);
	if(direction == DIR_LEFT)
	{
		spr_x(bonkx-8);
	} else {
		spr_x(bonkx+8);
	}
	if(tics > 10)
	{
		spr_x(bonkx);
		tics = 0;
		state = 0;
	}
}
State 6 is an animation so we'll make use of tics. Since there's only a couple of frames to it, we can do it long-form and it'll be just fine. Now, when tics reaches 5, we change the sprite to the full-on headbutt frame. But wait... it's too far to the left in the sprite image file. So hey, let's use that direction variable here. We tell HuC that we're going to shift Bonk's sprite position by 8 pixels. This will only happen once. Now, when tics gets over 10, we can go back to state 0. But of course, we should set the sprite's X coordinate back to its original position. Then reset tics and state to 0, and the state machine for Bonk's idle state will take care of setting the correct graphic. Are you starting to see how useful and powerful this system can be?

Get To A Better State

No, Bonk doesn't have car insurance. However, we need to improve our idle state. So far, Bonk can only bonk. Let's make him move. After we're done with this detail, we'll add in another new concept... scroll wrapping. Go back to bonk_state_0 and add in two new state controllers:
if(j1 & JOY_LEFT)
{
	spr_ctrl(FLIP_MAS, FLIP_X);
	tics = 0;
	frame = 0;
	state = 1;
	spr_pattern(0x5000);
	direction = DIR_LEFT;
}
if(j1 & JOY_RGHT)
{
	spr_ctrl(FLIP_MAS, NO_FLIP);
	tics = 0;
	frame = 0;
	state = 1;
	spr_pattern(0x5000);
	direction = DIR_RIGHT;
}
Again, we're checking for presses and changing the state. Now, we're changing to state 1, which is the walk state. You should be able to figure out by now what comes next...

Around The World

We are going to fill in our bonk_state_1 function next. As we do this, we're going to notice that we're in need of another new function. So, let's get to it!
char stillwalking;
stillwalking = 0;
tics++;
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);
}
Does some of this look familiar? Yeah, we're copying over some of the animation code from previous tutorials. But wait... what's the new stuff there? Well, stillwalking is what is commonly known as a flag variable... that is to say, a variable that determines whether or not something should be recognized ('flagged'). In this case, our flag variable will eventually tell us whether or not Bonk is still walking around. Unlike in our previous tutorials, Bonk could stop walking halfway through his animation. Here, we're going to fix that little flaw, and stillwalking is the key. We set it to 0 by default. Moving right along...

Next, we have to add state controllers for this state. Since we're already walking, we have to make sure Bonk keeps walking. So, we do this:
if(j1 & JOY_LEFT) stillwalking = 1;
if(j1 & JOY_RGHT) stillwalking = 1;
Pretty easy, right? Yep. Oh, while we're here, let's add in Bonk's state controller for his bonk attack. We can literally copy the code from state 0.
if(j2 & JOY_B)
{
	tics = 0;
	frame = 0;
	state = 6;
	spr_pattern(0x5700);
}
Now, Bonk can bonk in states 0 and 1, just like in the real game.

Now, back to his movement. Here's where stillwalking makes its mark.
if(stillwalking == 0)
{
	state = 0;
	tics = 0;
	frame = 0;
} else {
	move_in_world();
}
This says 'if we're not still walking, set us back to state 0 but if we are still walking, let's move Bonk around'. And thus is born yet another function. What the func?
move_in_world()
{
	char map_scroll_dir;
	map_scroll_dir = 0;
	if (direction == DIR_LEFT)
	{
		if (vmapx > MAP_X_LOW_THRESHOLD)
		{
			if (bonkx < CHAR_LEFT_THRESHOLD)
			{
				map_scroll_dir = DIR_LEFT;
			} else {
				bonkx--;
			}
		} else {
			bonkx--;
		}
	}
	if (direction == DIR_RIGHT)
	{
		if (vmapx < MAP_X_THRESHOLD)
		{
			if (bonkx > CHAR_RIGHT_THRESHOLD)
			{
				map_scroll_dir = DIR_RIGHT;
			} else {
				bonkx++;
			}
		} else {
			bonkx++;
		}
	}
	spr_x(bonkx);
	move_map(map_scroll_dir);
}
This is quite a function, no? We're finally using many of those bits n pieces we defined earlier. Let's take a look at it step by step.

The first thing you see is map_scroll_dir. This is similar to the flag variable we used before, only this one can either be 0, 1, or 2 rather than just 0 or 1. direction was set back in state 0 when we started this whole thing off and will tell us which if block to look at. Now we get into some technical details. vmapx keeps track of the current scroll of the map. We check it against the boundaries of the map. In this case, the lowest value is 0 and the highest value is 1055. How did we reach this weird number? Well, we take the total number of pixels in the map, which we can get by multiplying the number of tiles wide by 8 and subtract 256 from it, which is the size of the screen in pixels. This gives us 1056. But since we're starting from 0 and not 1, we knock 1 off of that value for 1055. Math ftw!

Okay, so that covers the movement of the scroll. So, what else do we have? Well, we have the char threshold lines next... these determine whether or not we should scroll the map at all, based on where Bonk is in relation to the screen borders. If you've ever played Exile II, you'll notice that they really messed this part up! The thresholds shouldn't be too far apart as they were in Exile II. We'll use 96 and 128, as these are pretty decent values for this. So, what does this all mean? Well it goes like this... if Bonk is at the char threshold, check to see if there's any more map to scroll... if there is, scroll the map... if there's not, just move Bonk... if neither of these conditions are met (like if he's inside the two char thresholds or to an extreme side of the map), also just move Bonk. These are the basics of world movement.

Now ... we've got a new function at the end, right after we tell HuC to set Bonk's position. Here's where things get a bit more technical.
move_map(whichway)
char whichway;
{
	if (whichway == 0) return;
	if (whichway == DIR_RIGHT) vmapx++;
	if (whichway == DIR_LEFT) vmapx--;

	mapx = vmapx >> 3;
	mapy = vmapy >> 3;

	if (whichway == DIR_RIGHT)
	{
		if ((lastmapx != mapx) || (lastmapy != mapy))
		{
			t3 = (mapx+32) & 0xFF;
			load_map(t3,mapy,t3,mapy,1,SCR_H);
		}
	}
	if (whichway == DIR_LEFT)
	{
		if ((lastmapx != mapx) || (lastmapy != mapy))
		load_map(mapx,mapy,mapx,mapy,1,SCR_H);
	}
	scroll(0,vmapx,vmapy,0,223,0xC0);
	lastmapx = mapx;
	lastmapy = mapy;
}
move_map is the heart of the scroll wrapping concept. What this function does is reload a portion of the map based on the position of the virtual window... that box frame we talked about in the previous tutorial. When the box frame reaches the edge, telling it to go further wraps it around to the other side. Try doing that with a picture frame! Notice we're using Y coordinates in this function as well. We don't actually need them here, but we're going to expand on this later so we keep them filled in for now. Some of the lines may not make a lot of sense. In order to fully comprehend them, you really have to get how the BAT works. That kind of thing is really outside the scope of this tutorial.

Okay so... if we were to compile and run this right now, you'd have Bonk walking through the game world. We're still not done yet though. We're going to finish off the states we started and then fill in the last few states we've not covered yet.

No Pointer Sisters Here

Bonk's not gonna jump for love this time but we're definitely going to make him jump. Bonk's jump is the most complex sequence so far; it's made up of four individual states: start, up, down, and landing. State 2 is the first state in this sequence. So then... we can enable this by adding the following state controllers to our state 0 and state 1 functions:
if(j2 & JOY_A)
{
	tics = 0;
	frame = 0;
	state = 2;
	spr_pattern(0x5400);
}
Don't forget to also update bonk_state_machine with new if lines, calling the new functions we're going to create.

State 2 is really going to be an easy one. In state 2, there's no movement allowed, so we don't have to have any state controllers based on player input. So, its code is gonna be puny.
bonk_state_2()
{
	tics++;
	if(tics > 4)
	{
		jumpspeed = 4;
		jumpdelta = 70;
		tics = 0;
		/* tics is going to be used in a different way in state 3 ... you'll see! */
		spr_pattern(0x5500);
		state = 3;
	}
}
Yes, I have specifically left a comment in there. tics is going to have a very special purpose in state 3, so we're setting it to 0 now.

Now, let's add state 3, which is Bonk's jump up state.
bonk_state_3()
{
	bonky -= jumpspeed;
	tics += jumpspeed;
	if(tics > jumpdelta)
	{
		spr_pattern(0x5600);
		jumpspeed--;
		if(jumpspeed == 0)
		{
			tics = 0;
			state = 4;
			jumpspeed = 1;
		}
	}
	spr_y(bonky);
}
Here, we start decreasing bonky's value by the value held in jumpspeed. However, we then add the value in jumpspeed to tics. 'Huh, wtf for?' Because tics has now become a different kind of counter... it's now counting the length of the jump. 'But why not check bonky?' Because, silly neophyte, bonky isn't always going to start from 153... it'll be all over the place later on, so checking against it is fruitless. That's why we're using tics to measure the distance of the jump. When the jump has reached the value of the jumpdelta, we change the frame like in the original game and start decreasing the value of jumpspeed. Once it's hit 0, we reset the tic counter, change the speed of the jump to 1, and put ourselves in state 4. And of course, we have to update the Y position, since we've never done that before.

'Wait a second... you should be able to move while jumping, right?' Very good, grasshopper, you noticed! Or did you really? No matter... well, we will also add in some code to affect the in-flight movement of Bonk:
if(j1 & JOY_LEFT)
{
	spr_ctrl(FLIP_MAS, FLIP_X);
	direction = DIR_LEFT;
	move_in_world();
}
if(j1 & JOY_RGHT)
{
	spr_ctrl(FLIP_MAS, NO_FLIP);
	direction = DIR_RIGHT;
	move_in_world();
}
Put these right before you update the Y position of the Bonk sprite. Now, you can move in mid-air!

Coming Down With Something

You almost have a fully working jump! Now, coming down is going to be slightly different. First of all, our movement speed is only set to 1... we'll need to increase it. Also, we're now not concerned with the jump distance.
bonk_state_4()
{
	bonky += jumpspeed;
	if(jumpspeed < 4) jumpspeed++;
	if(bonky > 152)
	{
		bonky = 153;
		frame = 0;
		tics = 0;
		state = 5;
		spr_pattern(0x5400);
	}
	if(j1 & JOY_LEFT)
	{
		spr_ctrl(FLIP_MAS, FLIP_X);
		direction = DIR_LEFT;
		move_in_world();
	}
	if(j1 & JOY_RGHT)
	{
		spr_ctrl(FLIP_MAS, NO_FLIP);
		direction = DIR_RIGHT;
		move_in_world();
	}
	spr_y(bonky);
}
All we're doing here is checking to see if our jump speed is less than 4. If it is, then we increase it by 1. That way, we go from 1 to 4 over the course of a few frames. Then, we check to see if Bonk's touched the ground or not. If he has, we ensure that he's standing at the correct position and we set him in state 5, which is his landing state. And of course, we copied over the in-air movement stuff from the up-jump.

State 5 is the last state we'll add, and it too is pretty darn simplistic.
bonk_state_5()
{
	tics++;
	if(tics > 4)
	{
		tics = 0;
		state = 0;
		frame = 0;
	}
}
Yep... that's all there is to it... just set Bonk back to state 0 once a few tics have passed. Now... if you've done all of this, compile and run. You'll have a fully-working Bonk, ready to run through worlds jump over stuff and bonk his way through bad guys!

So... What's Next

You now have a largely functional game engine base. It's a basic side-scroller with scroll wrapping and a basic state machine. What else can we do? One thing that might help is to add multi-directional scrolling. All we have to do is build on top of what we already have. We can build on our knowledge of state machines to create an enemy (yes, enemies can use state machines too!). But surely there must be something new we can cover, right? Right there is! Since we're going to add vertical scrolling, it'll be a good time to introduce sprite-to-tile collision using HuC's built-in functions. Also, since we're going to be adding an enemy... or two ... or more... it'll be a good time to get more familiar with arrays.... and their limitations.

Full Program Listing

#include "huc.h"

/* defines make life easier and code more readable... */
#define DIR_LEFT 1
#define DIR_RIGHT 2
#define SCR_W 33
#define SCR_H 32
#define CHAR_LEFT_THRESHOLD 96
#define CHAR_RIGHT_THRESHOLD 128

/* our new Bonk sprites! */
#incspr(bonk,"bonksprites.pcx",0,0,2,18);
#incpal(bonkpal,"bonksprites.pcx");

/* Welcome to Bonk's world! */
#incbin(levelmap,"tut7map.fmp");
#incchr_ex(leveltiles,"tut7pal0.pcx",0,0,20,7,0,\
                      "tut7pal1.pcx",0,0,20,2,1,\
                      "tut7pal1.pcx",0,16,11,1,1);
#incpal(levelpal0,"tut7pal0.pcx");
#incpal(levelpal1,"tut7pal1.pcx");

/* Globals ftw */
int bonkx, bonky, j1, j2, mapx, mapy, vmapx, vmapy, lastmapx, lastmapy, t3, jumpspeed, jumpdelta;
int MAP_X_THRESHOLD,MAP_X_LOW_THRESHOLD;
char tics, frame, state, direction;

main()
{
  /* set up Bonk! */
  init_satb();
  tics = 0;
  frame = 0;
  bonkx = 104;
  bonky = 153;
  spr_make(0,bonkx,bonky,0x5100,FLIP_MAS|SIZE_MAS,NO_FLIP|SZ_32x32,0,1);
  load_palette(16,bonkpal,1);
  set_color(256,0);
  load_vram(0x5000,bonk,0x900);
  satb_update();
  /* set up Bonk's world! */
  MAP_X_THRESHOLD = 1055;
  MAP_X_LOW_THRESHOLD = 0;
  set_map_data(levelmap,164,28);
  set_tile_data(leveltiles);
  load_tile(0x1000);
  load_map(0,0,0,0,34,28);
  load_palette(0,levelpal0,1);
  load_palette(1,levelpal1,1);
  /* stuff for handling the map scrollie thingo */
  mapx = 0;
  lastmapx = 0;
  mapy = 0;
  lastmapy = 0;
  vmapx = 0;
  vmapy = 0;
  /* main game loop! */
  for(;;)
  {
    j1 = joy(0);
	j2 = joytrg(0);
	if (j2 & JOY_STRT)
	{
	  pause();
	} else {
	  bonk_state_machine();
	}
	satb_update();
	vsync();
  }
}

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;
  }
}

bonk_state_machine()
{
/*
  Bonk's State Machine:
  0 - Idle (all buttons available)
  1 - Walking (all buttons available)
  2 - About to jump (all buttons disabled)
  3 - Jumping (directions available only)
  4 - Jumping down (directions available only)
  5 - Landing (all buttons disabled)
  6 - Bonking (all buttons disabled)
*/
  if (state == 0) bonk_state_0();
  if (state == 1) bonk_state_1();
  if (state == 2) bonk_state_2();
  if (state == 3) bonk_state_3();
  if (state == 4) bonk_state_4();
  if (state == 5) bonk_state_5();
  if (state == 6) bonk_state_6();
}

bonk_state_0()
{
  /* this is Bonk's idle stance... if the tics are equal to 0, set his frame to default and his tic counter to 1 */
  if (tics == 0) spr_pattern(0x5100);
  tics = 1;
  /* most other states can be set in this state */
  if (j1 & JOY_LEFT)
  {
    /* start Bonk moving to the left! */
	spr_ctrl(FLIP_MAS,FLIP_X);
	tics = 0;
	frame = 0;
	state = 1;
	spr_pattern(0x5000);
	direction = DIR_LEFT;
  }
  if (j1 & JOY_RGHT)
  {
    /* start Bonk moving to the right! */
	spr_ctrl(FLIP_MAS,NO_FLIP);
	tics = 0;
	frame = 0;
	state = 1;
	spr_pattern(0x5000);
	direction = DIR_RIGHT;
  }
  if(j2 & JOY_B)
  {
    /* set Bonk in bonking state! */
    tics = 0;
	frame = 0;
	state = 6;
	spr_pattern(0x5700);
  }
  if(j2 & JOY_A)
  {
    /* make Bonk jump! */
    tics = 0;
	frame = 0;
	state = 2;
	spr_pattern(0x5400);
  }
}

bonk_state_1()
{
  /* Bonk's walking state! */
  char stillwalking;
  stillwalking = 0;
  tics++;
  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);
  }
  if (j1 & JOY_LEFT)
  {
    stillwalking = 1;
  }
  if(j1 & JOY_RGHT)
  {
    stillwalking = 1;
  }
  if(j2 & JOY_B)
  {
    /* set Bonk in bonking state! */
    tics = 0;
	frame = 0;
	state = 6;
	spr_pattern(0x5700);
  }
  if(j2 & JOY_A)
  {
    /* make Bonk jump! */
    tics = 0;
	frame = 0;
	state = 2;
	spr_pattern(0x5400);
  }

  if (stillwalking == 0)
  {
    state = 0;
	tics = 0;
	frame = 0;
  } else {
    move_in_world();
  }
}

bonk_state_2()
{
  /* Bonk's pre-jump prep state! all controls are disabled in this one */
  tics++;
  if (tics > 4)
  {
    jumpspeed = 4;
	jumpdelta = 70;
	tics = 0; /* tics is going to be used in a different way in state 3... you'll see! */
	spr_pattern(0x5500);
	state = 3;	
  }
}

bonk_state_3()
{
  /* Bonk's jumping up state! */
  bonky-=jumpspeed;
  /* here, we're going to use tics to determine how high Bonk has jumped, and compare it to the jump delta */
  tics+=jumpspeed;
  if (tics > jumpdelta)
  {
    /* if the total jump distance exceeds the jump delta, reduce the upward motion until it reaches zero */
	/* the reduction in upward motion makes the jump appear a little more natural than just immediately changing directions */
    spr_pattern(0x5600);
	jumpspeed--;
	if (jumpspeed<0)
	{
	  /* when the jump speed decreases past 0, we will need to set Bonk in state 4 */
	  tics = 0;
	  state = 4;
	  jumpspeed = 1;
	}
  }
  if (j1 & JOY_LEFT)
  {
	spr_ctrl(FLIP_MAS,FLIP_X);
	direction = DIR_LEFT;
	move_in_world();
  }
  if (j1 & JOY_RGHT)
  {
	spr_ctrl(FLIP_MAS,NO_FLIP);
	direction = DIR_RIGHT;
	move_in_world();
  }
  spr_y(bonky);
}

bonk_state_4()
{
  /* Bonk's falling down state! */
  bonky+=jumpspeed;
  if (jumpspeed < 4) jumpspeed++;
  if (bonky > 152)
  {
    bonky = 153;
	frame = 0;
	tics = 0;
	state = 5;
	spr_pattern(0x5400);
  }
  if (j1 & JOY_LEFT)
  {
	spr_ctrl(FLIP_MAS,FLIP_X);
	direction = DIR_LEFT;
	move_in_world();
  }
  if (j1 & JOY_RGHT)
  {
	spr_ctrl(FLIP_MAS,NO_FLIP);
	direction = DIR_RIGHT;
	move_in_world();
  }
  spr_y(bonky);
}

bonk_state_5()
{
  /* Bonk's landing state; same frame as state 2, but reverts to state 0 when finished rather than state 3 */
  tics++;
  if (tics > 4)
  {
    tics = 0;
	state = 0;
	frame = 0;
  }
}

bonk_state_6()
{
  /* Bonk's bonking state! all controls are disabled in this one; this is just an animation handler/state controller */
  tics++;
  if (tics == 5)
  {
    spr_pattern(0x5800);
	/* we need to adjust Bonk's position, as this sprite is a few pixels to the left normally... this is why we need 'direction'! */
	if (direction == DIR_LEFT)
	{
	  spr_x(bonkx-8);
	} else {
	  spr_x(bonkx+8);
	}
  }
  if (tics > 10)
  {
    /* as this animation ends, restore the sprite to its normal posision and set the state back to 0 */
    spr_x(bonkx);
	tics = 0;
	state = 0;
  }
}

move_in_world()
{
	char map_scroll_dir;
	map_scroll_dir = 0;
	/*
		how this should work:
			-if character is at a direction threshhold but not at the map threshhold, scroll the map
			-move character's sprite otherwise
	*/
	/* are we moving left? */
	if (direction == DIR_LEFT)
	{
		/* check for map threshhold */
		if (vmapx > MAP_X_LOW_THRESHOLD)
		{
			/* map threshhold has not been reached, so check for screen threshhold */
			if (bonkx < CHAR_LEFT_THRESHOLD)
			{
				/* we're at the screen threshhold, so we'll be scrolling the map now! */
				map_scroll_dir = DIR_LEFT;
			} else {
				/* just move */
				bonkx--;
			}
		} else {
			/* we're at the map threshhold...no scrolling allowed! */
			bonkx--;
		}
	}
	/* are we moving right? */
	if (direction == DIR_RIGHT)
	{
		/* check for map threshhold */
		if (vmapx < MAP_X_THRESHOLD)
		{
			/* map threshhold has not been reached, so check for screen threshhold */
			if (bonkx > CHAR_RIGHT_THRESHOLD)
			{
				/* we're at the screen threshhold, so we'll be scrolling the map now! */
				map_scroll_dir = DIR_RIGHT;
			} else {
				/* just move */
				bonkx++;
			}
		} else {
			/* we're at the map threshhold...no scrolling allowed! */
			bonkx++;
		}
	}
	spr_x(bonkx);
	move_map(map_scroll_dir);
}

move_map(whichway)
char whichway;
{
  if (whichway == 0) return;
  if (whichway == DIR_RIGHT) vmapx++;
  if (whichway == DIR_LEFT) vmapx--;

  mapx = vmapx >> 3;
  mapy = vmapy >> 3;
  
  if (whichway == DIR_RIGHT)
  {
      if ((lastmapx != mapx) || (lastmapy != mapy))
      {
        t3 = (mapx+32) & 0xFF;
        load_map(t3,mapy,t3,mapy,1,SCR_H);
      }
  }
  if (whichway == DIR_LEFT)
  {
      if ((lastmapx != mapx) || (lastmapy != mapy))
        load_map(mapx,mapy,mapx,mapy,1,SCR_H);
  }
  scroll(0,vmapx,vmapy,0,223,0xC0);
  lastmapx = mapx;
  lastmapy = mapy;
}

See also

A Crash Course In HuC - Part 8 (coming soon)
This site is ©2013 Eponasoft. These are not the droids we are looking for.