State machine for beginner - how to shoot state AND jump state

Started by
5 comments, last by DekuTree64 7 years, 7 months ago

Hi all,

My first post. I am not very skilled yet in programming though have a good idea of the ideas behind how a lot of it works. I've done codecademy courses on Javascript, GIT and am working through their Python course now. I am using Godot as my preferred engine, due to this I've put Python as the topic prefix as it's the closest I could find.

The above is to give an idea of where I'm at.

I've in time found that If Then Else is a bad idea for controlling a character due to the complexity that comes from such a structure and the code turns spaghetti. So I've been researching and have come to state machines. I love the idea of them, but I can't get my head around this issue below. (remember I'm a newb)

[attachment=33096:FSM-export01.png]

If you look at the image attached I have various states and I think I know how to move between them in theory. My issue though is the shooting state. You see I can shoot and idle, shoot and jump, shoot and fly, shoot and run. As it's a state machine and I can only have one state at a time, does that mean I need 4 new states for those combinations? It's a shame if so due to the duplication of so much code. So I thought I'd check here first and see what the community thinks.

Thanks so much and I'm glad to have found this place.

Rob

Advertisement
Generally you only want to use a state machine when it contains mutually exclusive states. If you ever have two or more things that you can combine at the same time (like shooting and any movement state), either split them in two different state machines, or use something else instead of a state machine.

Sometimes you don't need a state machine at all and can do everything in a single Update function that gets called once a frame.

such as:

If health is <= 0 and dying animation not playing, play it and return.
If dying animation is done, do game over and return.

Check arrow keys, update velocity.
Check whether the player is touching the ground, if so reset the jumping flag.
If jumping flag is false and jump key is pressed, set jump flag and add lots of upward velocity.
If jumping flag is set and jump key is released quickly, subtract a bit of upward velocity to let the player perform a smaller jump.  Potentially add another flag to prevent the user from repeating this multiple times in the same jump.
If player is capable of flight, holding the jump key while jumping adds a little bit of upward velocity.

If fire key is down, and firing is possible now, then fire.

Apply velocity to position.

If in air, use jumping/flying animation.
Else If on ground and speed > large threshold, use running animation.
Else If on ground and speed > small threshold, use walking animation.
Else if on ground use idle animation.

For typical player operations, state machine's are really the right tool. Being an early game you're making, you should really be doing something simple. For example, assume you're characters have apdate() method called once a frame, for your player character, you'd do something like this:

void Player::Update()
{
  if (Input.KeyPressed(LEFT_KEY)) {
    Velocity.X = -5; // move left
  }
  else if (Input.KeyPressed(RIGHT_KEY)) {
    Velocity.X = 5; // move right
  }
 
  if (IsOnGround) {
    if (Input.KeyPressed(JUMP_KEY)) {
    Velocity.Y = -30; // set player velocity negative to jump, we'll slow down each frame
    IsOnGround = false;
  }
  else {
    // we're in the air, decrease Y velocity each frame to simulate gravity
    Velocity.Y += 2;
  }
 
  if (Weapon.RefireTimer < GetCurrentTime() && Input.KeyPressed(FIRE_KEY)) {
    // shoot a bullet and set refire timer to however long the current weapon's refire time is
    Weapon.RefireTimer = GetCurrentTime() + Weapon.RefirePeriod;
    AddBullet(Weapon.GetBullet());
  }
  // store old location, and move player in each axis
  // If we're colliding with something, set the velocity to 0 in that direction and reset player's position in that axis
  Vector2d OldPosition = Position;
  Position.X += Velocity.X;
  if (CheckCollision(Position)) {
    Position.X = OldPosition.X;
   Velocity.X = 0;
  }
  Position.Y += Velocity.Y;
  if (CheckCollision(Position)) {
    // if we hit the ground, set onGround
    IsOnGround = CheckHitGround(Position);
    Position.Y = OldPosition.Y;
    Velocity.Y = 0;
  }

My Gamedev Journal: 2D Game Making, the Easy Way

---(Old Blog, still has good info): 2dGameMaking
-----
"No one ever posts on that message board; it's too crowded." - Yoga Berra (sorta)

I love the idea of them, but I can't get my head around this issue below.



State machines are standard everywhere in games. Learn to love them.

I put together this article where I used state machines in several different ways in some simple little prototypes.

You see I can shoot and idle, shoot and jump, shoot and fly, shoot and run



As Nypyren points out, usually they are best when used as discrete things. That is, you are in A, then B, then C. You can also do work in the transition between A and B, do work in the transition between B and C.

That doesn't mean you can't make it work. Depending on the animation system you are using, you might be able to control the shooting arm separately from the rest of the body. You'll have one set of body actions that control most things, and another set of arm actions for your shooting.

Some systems will allow more complex animation blending to make it work. Others will use inverse kinematics (IK) which is a fancy way to say you tell the system where the hand goes and where the shoulder goes and it uses physics to figure out how all the joints fit together. These take a bit of work to put together and thankfully most professional-grade engines have them implemented already.

For typical player operations, state machine's are really the right tool. Being an early game you're making, you should really be doing something simple. For example, assume you're characters have apdate() method called once a frame, for your player character, you'd do something like this:


void Player::Update()
{
  if (Input.KeyPressed(LEFT_KEY)) {
    Velocity.X = -5; // move left
  }
  else if (Input.KeyPressed(RIGHT_KEY)) {
    Velocity.X = 5; // move right
  }
 
  if (IsOnGround) {
    if (Input.KeyPressed(JUMP_KEY)) {
    Velocity.Y = -30; // set player velocity negative to jump, we'll slow down each frame
    IsOnGround = false;
  }
  else {
    // we're in the air, decrease Y velocity each frame to simulate gravity
    Velocity.Y += 2;
  }
 
  if (Weapon.RefireTimer < GetCurrentTime() && Input.KeyPressed(FIRE_KEY)) {
    // shoot a bullet and set refire timer to however long the current weapon's refire time is
    Weapon.RefireTimer = GetCurrentTime() + Weapon.RefirePeriod;
    AddBullet(Weapon.GetBullet());
  }
  // store old location, and move player in each axis
  // If we're colliding with something, set the velocity to 0 in that direction and reset player's position in that axis
  Vector2d OldPosition = Position;
  Position.X += Velocity.X;
  if (CheckCollision(Position)) {
    Position.X = OldPosition.X;
   Velocity.X = 0;
  }
  Position.Y += Velocity.Y;
  if (CheckCollision(Position)) {
    // if we hit the ground, set onGround
    IsOnGround = CheckHitGround(Position);
    Position.Y = OldPosition.Y;
    Velocity.Y = 0;
  }

Thank you for the large effort above. What I found though, having already tried a bunch of if / else type of logic that in time it gets very hard to keep track of and a bit sphagetti, hence my looking into state machines. I'll definitely have a look over your code though and see what I can learn from it. I've decided to hold off a week or more whilst I do some more Python lessons to make better use of the available then come back to this thread. Thanks again.


I love the idea of them, but I can't get my head around this issue below.



State machines are standard everywhere in games. Learn to love them.

I put together this article where I used state machines in several different ways in some simple little prototypes.

You see I can shoot and idle, shoot and jump, shoot and fly, shoot and run



As Nypyren points out, usually they are best when used as discrete things. That is, you are in A, then B, then C. You can also do work in the transition between A and B, do work in the transition between B and C.

That doesn't mean you can't make it work. Depending on the animation system you are using, you might be able to control the shooting arm separately from the rest of the body. You'll have one set of body actions that control most things, and another set of arm actions for your shooting.

Some systems will allow more complex animation blending to make it work. Others will use inverse kinematics (IK) which is a fancy way to say you tell the system where the hand goes and where the shoulder goes and it uses physics to figure out how all the joints fit together. These take a bit of work to put together and thankfully most professional-grade engines have them implemented already.

Thank you for the well written answer. I used to be a full time animator so am quite aware of IK etc. It's made me think that maybe I need to look at a modular approach to the character if that's what you're referring to? I'll do some research in Godot and see if I can force an animation of one body part over the rest. Still, what I also told the other commentor, I'm going to wait a week or more until I learn more Python. I just want to get some more fundamentals down so I have the tools to tackle this. I think I understand it from a conceptual perspective. I just need some actual tool knowlege (the language). Once I have more of that I'll come back to this thread. I just wanted to say thank you for now until I come back here. I realised I was jumping the gun a little. Thanks again


If you look at the image attached I have various states and I think I know how to move between them in theory. My issue though is the shooting state. You see I can shoot and idle, shoot and jump, shoot and fly, shoot and run. As it's a state machine and I can only have one state at a time, does that mean I need 4 new states for those combinations? It's a shame if so due to the duplication of so much code. So I thought I'd check here first and see what the community thinks.

Shoot and Idle, Shoot and jump, could be two separate states, or it could be three possible states with two OR-ed together. This obviously really depend on the game you are making and the kind of logic you are aiming for. Most of the time, if you are looking at a 2D game (I'm assuming so since you mentioned Godot), it's safer to keep them separate.

I also prefer to present my states in a table rather diagrams. List your states on the Y axis, and the conditions on the X axis, then the values is what happens when condition is met. For example:


+-----------------+------------------+-------------------+
| State\Condition | BTN_FIRE_PRESSED | BTN_FIRE_RELEASED |
+-----------------+------------------+-------------------+
| STATE_IDLE      | STATE_IDLE_FIRE  |  -                |
| STATE_IDLE_FIRE | -                | STATE_IDLE        |
| STATE_JUMP      | STATE_JUMP_FIRE  | -                 |
| STATE_JUMP_FIRE | -                | STATE_JUMP        |
+-----------------+------------------+-------------------+

Column are the conditions, and each row represent that state you are in, and what state is next if the condition is met. You can organize this into a two-dimensional array (or one-dimensional if you are old-school), then switching from one state to another is a matter of assigning your current state to the value in the table.


nextState = STATE_TABLE[currentState][condition]
if (nextState != null) {
   currentState = nextState
}

Your state machine chart looks good. All the states are mutually exclusive, but shooting is something that can be done in all of them except death. Therefore, call your shooting code in addition to updating the state machine. Perhaps with a check to make sure you're not dead (which I would call a special case). Or if you end up needing multiple states where you can't shoot, then instead of special casing each one, you could add a bool canShoot to your data table of stuff related to each state (or create a table if you don't have one already).

Choosing which animation to display can also be done by a table lookup, where you have two animations for each state (one shooting and one not).

This topic is closed to new replies.

Advertisement