Jump to content
A 2021 backup has been restored. Forums are closed and work in progress. Join our Discord server for more updates! ×
SoaH City Message Board

DW's Workshop: Day 2 - State Machine Badniks


Recommended Posts

NOTE: This is a starting template for the discussion

What is a finite state machine

A finite state machine is essentially the practice of limiting the possible operations that can run at any given time by applying a condition of "state" to the conditions for execution. Within each state, you have two essential possibilities for each frame of reference. Either you can advance to another state (any particular state, order doesn't matter) within the system, or you can stay within the state. Each state generally has its own code to execute and if you desire, you can execute additional code while in the process of changing state.

This sounds about a million times more complicated than it actually is. On the other hand, thats why we have diagrams...

example1.png

Yeah, I'm sure that still leaves you somewhat in the dark, so I'll use a more concrete example that I learned in Intro to Computer Engineering that helps it make perfect sense.

Here is the problem. You have two one bit values 0 or 1. The only operation you can perform in any given sequence is the direct change of one of these values to its opposite. When both values are the same, you want to yield an output of one. When both values are different, you want to yield an output of zero.

So lets break this notion up into state machines. We have two bits and thus four possible combinations.

0 - the state where both bits are zero.

1 - the state where the first bit is zero and the second bit is one.

2 - the state where the first bit is one and the second bit is zero

3 - the state where both bits are one

So we have four states... now we should move on to seeing how the state value can change.

From zero, you can add to one of two bits which read zero, but not both. That means from state zero you can move to states 1 and 2.

From one, you can add to the first bit or subtract from the second. Since you can't do both, that means you can't end up with the 10 result, so you omit the possibility of going to state 2, leaving 0 and 3 as the accessible states.

From two, you can subtract from the first or add to the second, but again, you can't do both, so state 1 becomes inaccessible while in state two, leaving 0 and 3 as the only accessible states.

Finally, from state three, you can subtract either, but once again since you can't subtract both, you can't go back to the 00 value. So from state three, you can move to states 1 and 2, but not zero.

Knowing that, we can construct the following chart:

exampe2.png

Now, I'm hoping this is becoming a little clearer. The reality of the matter is that finite state machines is an overly elaborate word for a concept so retardedly simple that once you've learned it, you'll want to slap yourself for not getting it. I know I did. Several times.

Section 2:

Anyway, anyway, you might be wondering "Hey DW, what the hell does any of this bullshit have to do with making games?" Well, I'm getting to that. Actually, the usage of finite state machines in games is more than just a little common. In fact, most games use some manner of finite state machine to control animations, manipulate the basic movement modes of onscreen characters, control interactive objects (or as I affectionately call them, "gimmicks"), for AI systems ranging in complexities from simple patterns with input to horrendously complicated AI like you'd expect to see in an advanced first person shooter engine or strategy game. The uses for finite state machines are boundless and if you take your programming seriously at all, you will encounter them often.

Continued on Post 2

  • Like 1
Link to comment
Share on other sites

Ok, so now we are going to concentrate specifically to how a finite state machine can apply to a gimmick. Not all gimmicks require the use of a state machine, I'll be honest. All of them however are going to affect the state machine that controls the player.

In Sonic Worlds in particular, we control the character based on what actions he is performing. As of now, we have the following actions tied to these states.

0 - Idle

1 - Jump

2 - Look Up

3 - Crouch

4 - Sliding

5 - Spindash

6 - Rolling

8 - Hurt

9 - Die

These 9 states make up the basics for the engine. You'll notice that there isn't a 7. That doesn't matter. At all. We just ignore it while programming.

Nexus also contains the following for gimmicks:

40 - Springs

41 - Hanging

42 - On Pinball Item

In addition, I've added this one for the purpose of water levels

10 - Breathing which I just slapped together real quick.

And these for normal gimmicks in Nexus's levels, I personally like to start at 100

100 - Manipulating Valve

101 - In Bubble

102 - In Tubes

And then for no apparent reason, I skipped ahead to 200 when programming these

200 - In Wind Tunnel

201 - Hanging from a bar in the wind tunnel

Anyway, the value you'll see here for this state machine is that it promotes orthogonality. As you know, the player can assume one and only one state at a time. This means that you'll have less fuckups overall due to your code conflicting, which I see all too often in fangames.

Particularly, one such project I saw at SAGE comes to mind. Dark Sonic RX, which oddly enough seems to have been using the Worlds engine (albeit the "powered by Sonic Worlds" logo wasn't in tact, but that can wait for another discussion) and there were lots of major hickups from where the programmer clearly didn't use state machines and instead opted to program his actions outside of the normal code. As a result, his various kicks and things would cause the player to come to a screeching halt and be unable to assume normal actions until they either get hit or use one of the other moves (and no, I assure you it wasn't deliberate).

Anyway, moving on, I'll be discussing how the pinball flipper uses finite state machines.

We have two primary variables associated with minor gimmicks... We have the gimmick object variable, which indicates which particular gimmick type is in use, and we have the gimmick state variable, which sets the state of any particular gimmick.

Honestly, gimmicks are a tad too interactive just to apply to a state machine, but their movements aren't.

Here is a good example of a state machine for a right facing pinball flipper

Once it is activated by the player landing on it, do the following: (pardon my sloppy psuedocode, I'm trying to do this as quickly as possible)

if (state == 1) //initiated state

{

XSpeed = 1;

if button1

state = 2;

if !PlayerNotOnGround

player action = -1;

}

if (state == 2) //launching state

{

play animation for pinball flipper flipping

XSpeed = (formula for X speed)

YSpeed = (formula for Y speed)

player action = 1 //jumping

gimmick object = 0;

gimmick state = 0;

}

And thats it for that in a nutshell.

Tomorrow's lecture is going to cover badniks, which are good deal more straight forward with state machines. I hope I didn't bore you all too much.

Link to comment
Share on other sites

Hmm. Actually quite helpful for engine implementation. In the past, since I tend to work from the bottom up, I have tended to have an excessive amount of boolean variables (such as onGround, isDucking) and I determined its state on-the-fly using if statements. It is run as though Sonic were expected to be running and jumping normally, making it very unfavorable for Gimmicks. Gimmicks would either have to be constructed from the basic collision blocks, they'd have to be instantaneous (like springs) or they'd have to yank Sonic out of his normal routines (an unpleasent process). The idea of having this state/action variable that has a finite, though ever-growing, amount of actions certainly organizes the code. The Gimmicks are programed with Sonic, though easily removable, instead of programmed to take control of Sonic. It's certainly an engine built for Gimmicks, I'll have to keep that in mind for my future builds.

Link to comment
Share on other sites

I introduced the concept of the state machine in the last part of this lab, so I'm going to go ahead and assume a moderate knowledge of state machines for this section... where we will make a single badnik using a simple state machine.

The topic for today's state machine - Sonic Nexus Badnik:

Pseudo-Pseudopod

attachment.php?attachmentid=406&stc=1&d=1186448858

This robot conceals a deadly weapon!

attachment.php?attachmentid=403&stc=1&d=1186448300

You might think you've killed it, but the nefarious pseudo-pseudopod has one last trick up its sleeve!

attachment.php?attachmentid=404&stc=1&d=1186448550

Hmm, I killed the snail, but why is its shell still here... and why... is it flashing red?

attachment.php?attachmentid=405&stc=1&d=1186448550

Ooooh, that was low!

OK. This is a simple robot all and all with 3 basic functions. These are...

1. Roam around an area and search for Sonic and friends!

2. If we see one of those do-gooders, we pull out our gun!

3. When we've finished getting our gun ready, pop a cap in his ass!

3. If we are defeated, our shell will kamikaze to protect our honor!

Ok, so I divided this into four essential states

state 0 - Patrolling

state 1 - Prepping a shot

state 2 - Shooting

state 3 - Self Destructing

While in these states, that also determines the robots animation.

We have these variables for the snail.

state - indicates which state the snail is currently assuming

initializes to 0

timeOfState - indicates how many frames have elapsed while in the state

initializes to 0

cannonExposed - This one is an extra simply used for when the robot is defeated. During certain frames of certain animations, this variable is changed to either 1 or 0, and if its 1, when the snail is destroyed, the cannon will still be shown on the shell and if its 0, the cannon will not be shown. Fairly inconsequential for the purposes of the algorithm, but its a nice touch.

initializes to 0

direction - Indicates which direction the snail is currently facing. If its -1, the snail is going left and if its 1, the snail is moving right.

initializes to -1

gunDelay - After the snail shoots, this is set to a value that decrements while in state 0 so that the snail doesn't go immediately back into shooting mode.

initializes to 0

With that in mind, lets move on to the actual states.

State 0 - Patrolling

Remember, we have two important roles to fulfill in this state... We need to walk around slowly and we need to look for the player and if we find him, change to the gun preparation state.

Unless I indicate otherwise, all code is executed every frame.

if (state == 0)  //indicates that we are in the patrolling state
{
     timeOfState+= 1;  //adds one to time of state while in the state
     if (gunDelay > 0)
          gunDelay+= -1; //subtracts one to gunDelay variable while in the state.

     if  (timeOfState == 3)
     {
          timeOfState = 0; //resets time of state to 0
          X Position += direction  //adds direction value to X coordinate of snail
     }

//Alright, now its time to start thinking about when to enter shooting mode.
//Obviously, we want the snail to be facing the player...  so let's work that out right now.

//If the player is to the left of the player, then 
//subtracting the player's X position from the snail's
//X position will yield a positive number.  The snail's
//direction is negative, so if we multiply the
//difference by the direction, the number will be
//negative when the player is to the left of the snail
//and the snail is facing left and negative when the
//player is to the right of the snail and the snail is facing right.
//Otherwise it will be 0 when the x positions are the
//same or positive  when the snail isn't facing the player.

     [I][B]if  ((X Position - Player.getXPosition()) * direction < 0)[/B][/I]

//alright, but we certainly don't want the snail to shoot from any distance...
//so lets limit how far it can get this distance from

//Once again, we can use our formula as sort of an absolute method
//for distance since we are using a convenient direction value...

          [I][B]if ((X Position - Player.getXPosition()) * direction > -120)[/B][/I]

//Okay, so that makes it to where the snail will only execute the proceeding actions
//while within 120 pixels of the player... or about a little under half a screen's length.

//Are we forgetting something?  Oh yes, we can't 
//assume the shooting state if the delay is above zero.

               [I][B]if ((gunDelay == 0)[/B][/I]

//Ok, so now our conditions are primed... lets switch to the next state!

               {
                      state = 1; //enter prepping shot state
                      timeOfState = 0; //reset the time of state
               }
}

And thats all we need to be concerned with for our patrolling state.

This post will be completed next post.

  • Like 1
Link to comment
Share on other sites

Before I continue, if you followed that shameless plug, I'd like to thank TRD for providing an awesome badnik design. It was fun to make.

State 1 - Preparring Gun

This state is a little more simple because it doesn't really do anything but advance to the shooting state when the animation is finished.

if (state == 1)
{
     if (animationFrame == animationLength)
          state = 2;
}

And then there is the final state for shooting

State 2 - Shooting

This is the state where we play the last of the animations and actually shoot our bullet. It plays out mostly like the previous state, except during one of the animation frames

In MMF, we have this somewhat annoying condition called "limit one action when event loops, which prevents it from executing the event multiple times during the same conditions if they don't change, so we'll just pretend we had another (preferably boolean, but MMF is retarded) variable up there called "bulletShot" set to 0 initially to simulate that.

{
if (state == 2)
{
     if (annimationFrame == 4) //animation frame of the shooting animation that looks like where the bullet should be shot at
          if (bulletShot == 0)
          {
               Create Object bullet with arguments of direction equal to the current direction at the desired point. //sorry, I don't know a good way to generalize this that makes sense in the scope of both gamemaker and MMF.
          }

     if (annimationFrame == animationLength)
          state = 0;
}

Ok, so we have all of the necessary states in place.

There are going to be some events outside of the primary state machine that dictate the interactions between the player and the snail. Just note that when Sonic "kills" the snail, we are going to set the state to 3 and the timeOfState to 0 rather than destroying it.

State 3 - Self Destructing

We assume this state as opposed to destroying the object when Sonic hurts it. Here a timer will count down for one second while the animation (or pallete swapping if you feel adventurous) plays on the snail and once the timer hits 60, the shell will explode, hoping to take Sonic out with it.

if (state == 3)
{
     timeOfState += 1;
     if (timeOfState == 60) // yeah, we are basically counting up to one second if we are playing at 60FPS
          {
               create big explosion on snail
               destroy snail //yeah, I don't know a good pseudocode equivalent for this either
          }
}

And thats basically it.

Sorry for the delay, I suddenly had relatives pop up.

Link to comment
Share on other sites

Nice explanation.

At first they may seem a bit tricky, but once you get it, you'll see how useful they are... I've been using state machines more frequently now but I get lazy at times and just brute force things...

I was just too lazy to apply them to enemy actions though...

Link to comment
Share on other sites

Yeah im really glad someone explained this, most of the people i help program have horrible engines. Ive been using this for a while, but DW explains it almost perfectly. Stuff like this is so essental for creating other engines like Action-Platformers and RPGs, so everyone needs to grasp and use it.

Its actully pretty simple, and once you get the hang of the overall system and orginization of it all, you can do almost anything with your engine, and create any kind of complex gimmick or move.

Link to comment
Share on other sites

I'm going to have to postpone the seminar on state machines and how they apply to bosses until Thursday due to familial affairs and last minute touches on Nexus. I would still like to see if we can't have people make demonstrations on how they can apply this to their own work on Sunday though.

Link to comment
Share on other sites

State Machine Lecture Part 3: Bosses

First, let me say sorry to a number of people for generally being an irresponsible dicktrash person who does stuff late. I feel like a major ass.

Anyway, this one is going to be a tad less code oriented and more just plain conceptual with the state machines, primarily because there are so damn many of them.

The boss I'm going to discuss is the water pump using lightning shooter that I showed in the Nexus demo. Anyway, without further ado, I'll get right to the explanation.

Variables:

state = 0

Obviously, this is our state controlling variable

statetime = 0

During some states, this will be added to. Most of the time, its used just to calculate the shift that we use for the vessel's surging motion.

juice = 0

The boss continually retreats to a water pump to draw more water which it uses to create storms. The amount of water it has is in this variable

xx = 0

The boss's x position in float form for precision addition and subtraction without rounding.

yy = 0

The boss's y position in float form for precision addition and subtraction without rounding.

health = 8

The boss's health. When it reaches zero, we go into the destroyed state.

invincibleTime = 0

This is how long the boss is unhurtable (flashing white) after he is hit by the character. While it is above zero, it constantly subtracts until it reaches zero, where it is hurtable again.

YShift = 0

This is used for the machine's dipping motions. Oddly enough, since I us

direction = 1

Indicates whether the vessel is facing left or right and dictates the vessel's motion. 1 is right, -1 is left.

Alright, now lets explain what the boss's basic motions are...

First, the boss enters the battlefield at a specific location which we make relative to the pump (actually, I was lazy when I made the boss and just found the position by plugging in values, so it isn't as malleable... which is definitely a bad thing.)

The boss in its first state drifts downwards toward the pump.

The next state involves the boss drawing water from the pump which fills the tank.

After that, it floats up and enters its primary loop...

The first part of the loop is its patrol state where it surges around the battlefield and tries to find and shoot you.

Once it finds you, it enters its main shooting phase... where it dips to a certain position and then shoots its water ball. From here, its water drains and it returns to the floating around state.

Once its out of water while in the patrolling state, it goes to refill its water... which mainly is a copy of its entering state with minor tweaks and a lack of invincibility.

Next state is the actual refilling of the tank without invincibility which is again, a carbon copy of the aforementioned state.

The next state is a carbon copy of the state where it drifts back up into its place for the main loop.

The final state is when the machine is destroyed and floats off the battlefield.

Alright, so lets discuss the states in more detail:

Activation - upon activating the boss

We first set the boss to his starting position and then activate its events in general. In some cases, this will be done by spawning the boss. It depends on your engine.

All the time, we will set the boss's positition to xx, yy + yShift

State 0 - Enter the Battlefield - Invincible

Since our starting direction for this action is always towards the right, this is how we do this...

While the boss's xx is less than the x position of the pump (and an offset if that applies, in any event, less than the x position for the boss to look good while sitting on the pump), we increment his xx by 1

Likewise, while the boss's yy is less the the appropriate pump and distance offset to make it look right while sitting, we decrement that value by 1

So long as this state is active, we will also increase the statetime by 4 and set its yShift to 8 * sin statetime. The direct result of this is the swaying up and down motion that the boss has.

Once the boss reaches his pump, we change the state to 1 and the statetime and yShift to 0. This results in a nice satisfying locking feel which we reinforce by playing an appropriate sound effect at the beginning of the next state.

Thats basically all there is to that state.

State 1: Filling the Guage - Invincible

This state is incredibly simple. We are just filling the tank. There is no fancy movement or anything...

All the time, we are incrementing the statetime by 1.

With the statetime, we use certain values to play sound effects.

Once the boss has been on the pump for 60 frames (1 second under normal operating speed), we start adding to the boss's pump ever 10 frames. The formula for this condition is statetime mod 10 == 0 and all we do is add 1 to the juice.

We'll keep doing this until the juice is set to 50 just to give the player more time to score hits and to make the boss like like its filling past the rim. Once we hit 50, we set the juice back to 12 since thats where the water guage is full on the sprite. From there, we'll also set the statetime to zero and the state to 2.

State 2 - Take to the Skies - Invincible

From here, the boss is going to do the same thing he did in state 1, only in reverse. Well, not totally to the reverse.

This time, when the state time hits a certain value, we turn around. Also, we move to the right instead of reversing the motion completely. I think this makes it look more fluid overall. It move up

Anyway, here are the basic actions...

While the state is active, we always add 4 to the state timer and use the same formula for setting the boss's yShift as before. Again, this produces the wavy vertical motion.

We will continually add 0.5 to the xx, which is less than before, but I think it looks better this time. We will also continually subtract 1 from the yy. In videogame logic, lower Ys are higher unless you know... programmed otherwise. Mostly because we read from up to down. I think. Anyway, you get the picture.

Once we get halfway up, we change the direction to -1, but the x motion still stays the same.

Whenever the statetime goes above 360, we set it to its statetime value mod 360. If you know trig, you should realize that 360 degrees compose a complete circle. This serves two primary purposes. The first is that we will eventually come to a new state that uses statetime as a generic timer without resetting it to keep the motion fluid. The second is to keep the value from overflowing. Well, actually, you'd have to leave the game on for hours and hours unpaused while dodging lightning bolts while the time overs and overs and overs for that to happen, so scratch that. I'm just making up bullshit.

Anyway, once we reach our desired yy (or higher of course), we set the state to the enter the patrol mode (state 3)

State 3 - Patrolling

Sorry guys, I'm tired. I'm going to have to cut the lesson short here and get back to it in the morning. Sorry for being a copout.

Link to comment
Share on other sites

  • 3 years later...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...