Legacy GM First Player state Machine.

Null-Z

Member
Well, I'm finally attempting to convert my jumbled player code into a state machine. but, as to be expected, problems ensue.
Code:
scr_PlayerStats();

enum Player_state
{
    Idle,
    Shoot,
    Move,
    MoveShoot,
    Jump,
    JumpShoot,
    death,
}

state = Player_state.Idle;
Code:
scr_GetInputs();

switch (state) 
{
    case Player_state.Idle: {scr_Idle();}; break;
    case Player_state.Shoot: {scr_shoot();}; break; 
   case Player_state.Move: {scr_Movement();}; break;
   case Player_state.MoveShoot: {scr_MoveShoot();}; break;
  case Player_state.Jump: {scr_Jump();}; break;
  case Player_state.JumpShoot: {scr_JumpShoot();}; break;
   case Player_state.death: {scr_Dieing();}; break;
}
Code:
///Initialize Variables
grav = 0.5;
hsp = 0;
hsp_carry = 0;
vsp = 0;
vsp_carry = 0;
movespeed = 2.5;

jumpspeed_normal = 8.5;
jumpspeed_powerup = 20;

jumpspeed = jumpspeed_normal;

key_down = 0;

if (global.checkpointR == room)
{

    x = global.checkpointx;
    y = global.checkpointy;

}
Code:
//Get the player's input
key_right = keyboard_check(vk_right);
key_left = -keyboard_check(vk_left);
key_jump = keyboard_check_pressed(vk_space);
key_down = keyboard_check(vk_down);
key_s = keyboard_check_pressed(ord("s"))
Code:
var hsp_final = hsp + hsp_carry; 
hsp_carry = 0; 

var vsp_final = vsp + vsp_carry; 
vsp_carry = 0; 

//Horizontal Collision 
if (place_meeting(x+hsp_final,y,BCH_G))
{
    while(!place_meeting(x+sign(hsp_final),y,BCH_G))
    {
        x+= sign(hsp_final); 
    }
    hsp_final = 0;
    hsp = 0;
}
x += hsp_final;

//Verticle Collision 

if (vsp < 20) vsp += grav;

if (place_meeting(x,y+vsp_final,BCH_G))
{
    while(!place_meeting(x,y+sign(vsp_final),BCH_G))
    {
        y += sign(vsp_final); 
    }
    vsp_final = 0;
    vsp = 0;
}

y += vsp_final;
Code:
move = key_left + key_right;
hsp = move * movespeed;

if (vsp < 20) vsp += grav;

if (place_meeting(x,y+5,BCH_G))
{
    if (key_jump) vsp = -jumpspeed
}

var hsp_final = hsp + hsp_carry;
hsp_carry = 0;

var vsp_final = vsp + vsp_carry;
vsp_carry = 0;

image_xscale = move;
if (place_meeting(x,y+1,BCH_G))
{
    if (move!=0) sprite_index = spr_AGRun  
}
///State Transitions.

if (!move) 
        {
            state = Player_state.Idle;
        }
if (key_jump)
    {
        state = Player_state.Jump
    }
if (key_s)
    {
        state = Player_state.MoveShoot;
    }
if (key_jump && key_s)
    {
        state = Player_state.JumpShoot;
    }
   
scr_collision();
Code:
sprite_index = spr_AGSht;

if instance_number(OBJ_Bullet) < 3
   
    bullet = instance_create(x+(12*image_xscale),y,OBJ_Bullet); 
    bullet.hspeed = 7*image_xscale; 
   
///State Transition
if (!key_s)
    {
        state = Player_state.Idle;
    }
if (key_jump)
    {
        state = Player_state.Jump;
    }
if (key_left || key_right)
    {
        state = Player_state.Move;
    }
scr_collision();
Code:
if key_right
{
    Player_state = Move;
}

if key_left
{
    Player_state = Move;
}

if key_s
{
    Player_state = Shoot;
}

if key_Jump
{
    Player_state = Jump;
}
if (!place_meeting(x,y+14,BCH_G))
{
    state = Player_state.Jump;
}


scr_collision();
Code:
sprite_index = spr_AGJump;

///State Transitions. 

if (vsp = 0) 
{state = Player_state.Idle;}

if (key_s)
    {
    state = Player_state.JumpShoot;
    }
   
scr_collision()
Code:
///haven't started working on this one yet.
sprite_index = spr_AGD

audio_stop_all();
audio_play_sound(snd_PlayerDeath,5,false);
Code:
sprite_index = spr_AGRunSht

if instance_number(OBJ_Bullet) < 3
    {
    bullet = instance_create(x+(12*image_xscale),y,OBJ_Bullet);
    bullet.hspeed = 7*image_xscale;
    }
    
///State Transitions
if (!key_s)
    {
        state = Player_state.Move;
    }   
if (!key_s && !key_left + !key_right)
    {
        state = Player_state.Idle;
    }
if (key_jump && key_s)
    {
        state = Player_state.JumpShoot;
    }
if (key_jump)
    {
        state = Player_state.Jump;
    }
    
scr_collision();
Code:
sprite_index = spr_AGjumpSht
if instance_number(OBJ_Bullet) < 3
    {
    bullet = instance_create(x+(12*image_xscale),y,OBJ_Bullet);
    bullet.hspeed = 7*image_xscale;
    }

a REAL mess I know, and probably making things more complicated than need be. but I'm still very unsure how to do this for a player controlled object. all help is appreciated.
 
Last edited:

Rob

Member
Personally, I don't tend to use a lot of scripts. I only use them if I'm repeating code. I think your problem is that you have to look into a script to see what it does whenever you want to change something etc? If so then take the code out of scripts and just use if statements:

Code:
if (state == "IDLE"){

}

if (state == "MOVING"){

}
etc

You need to find your own way of coding so that you know where everything is and can find it easily.

If you take a look at some of my longer videos you'll see a sprawling mass of if statements, but I know where everything is, even after a month of not touching it (my last tutorial video for example).
 

TheouAegis

Member
You can use the script IDs as the enum values, I'm pretty sure. I could be wrong, but just try it. Then you can just use script_execute() instead of the switch. But that's not really the problem.

First and foremost: You call scr_playerStats in nearly every script. The scr_playerStats script should only ever be called in the Create Event, nowhere else.

Second and foremost: Your scr_getInputs script should only be called from the Begin Step Event. Always, always, always get inputs before doing anything else.

Your scr_collision script should be at the very end of any state that involves movement. Currently you call it at the beginning. Big no-no.

You need some way to break out of the moving state by checking if the player is shooting or jumping. You only do that in the idle state right now.

The problem with this block of code in your movement script....
if (place_meeting(x,y+1,BCH_G)) { if (move!=0) sprite_index = spr_AGRun }
....is it will also run in your jumping state, which means if you have pass-through platforms, you will be in the running sprite as you pass through the platforms.


Also, you typically want to avoid repeated, unnecessary calls. Your dying state is going to stop the audio and play the death jingle every step. This should only happen right when the player dies (and you should have code elsewhere that prevents anyone else from making noises until desired, such as a global variable. Wherever you set the player's state to dead is where you should stop the sounds and play the death jingle.
 

dannyjenn

Member
The way people are doing it these days is like this:
Code:
scr_GetInputs();
scr_PlayerStats();

state = scr_Idle;
Code:
script_execute(state);
This has some advantages, though I myself am still in the habit of using a switch statement as your original code had.

edit - Ninja'd
 

dannyjenn

Member
About your enums... is there any reason that death is lowercase when the rest are capitalized? You can do it that way if you want, but that really annoys me.
 

Slyddar

Member
I like to lay out the code in my player state scripts like this:

Code:
//get input
//This captures all inputs

//calculate movement
//This includes using the movement inputs, applying slowdowns, max speed checks, etc

//check state change
//This includes the state change checking code

//collision checks
//This has your collision check code

//apply animations
//This chooses the correct sprite and direction, based on the state
You really should do movement changes before you do collision checks. Doing animation at the end makes more sense too, as that is the final movement for that step, so you want to show what it looks like at that point.
 
Last edited:

Neptune

Member
Whatever you end up doing, my personal advice is to avoid the headache of this approach:
Code:
switch(state)
{
case STATE.this: break;
case STATE.that: break;
}
And do something similar:
Code:
if state == STATE.this
{
   if !state_ini_this
   {
      state_ini_this = true;
      //INITIALIZE STATE
   }
}
else
{
   if state_ini_this
   {
      state_ini_this = false;
      //RESET STATE
   }
}
Look less clean than a switch, but way more functional...
 

NeoShade

Member
Whatever you end up doing, my personal advice is to avoid the headache of this approach:
Code:
switch(state)
{
case STATE.this: break;
case STATE.that: break;
}
And do something similar:
Code:
if state == STATE.this
{
   if !state_ini_this
   {
      state_ini_this = true;
      //INITIALIZE STATE
   }
}
else
{
   if state_ini_this
   {
      state_ini_this = false;
      //RESET STATE
   }
}
Look less clean than a switch, but way more functional...
Can you explain what you gain from this approach? Switch statements are pretty standard for state machines.
 
  • Like
Reactions: Yal

Neptune

Member
@NeoShade I've found that having the "else" directly connected with the state, allows it to be more organized.
For resetting anything that needs to be reset at the end of the state (when switching to a new state).

Trying to do this with the switch approach ends up looking like this:

Code:
switch(state)
{
cases
}
if !particular_case {/*reset*/}
if !particular_case2 {/*reset*/}
It has been my experience, when some of the states get fairly large (or running large scripts), that the flow control can get wonky when this kind of thing starts to happen.
I prefer the "run" else "reset" to be specifically in one spot.

Not to mention, there really is no reason to use a 'switch' as far as processing goes... You might save yourself a few "if" checks per step, but thats not a normal scenario thing to be optimizing.
 

NeoShade

Member
I guess I can understand that. I tend to include those sort of resets in the exit condition of my state script though.
It's probably somewhat personal preference, I was just curious about your method.
 

dannyjenn

Member
Can't you solve the problem of initializing the state by simply adding more possible states? e.g.
Code:
switch(state){
    case states.idle_initialization:
        // initialize the idle state
        state = states.idle;
        // don't break
    case states.idle:
        // code for the idle state
        break;
}
Then instead of ever switching directly to the idle state, you'd switch to the idle_initialization state (which first initializes it and then changes it to the idle state).
 

immortalx

Member
What @TheouAegis said is the best suggestion you could get. I started my game with 5 states for the player and ended up with this:
Code:
enum states
{
    idle,
    run,
    jump,
    fall,
    cast,
    fireball,
    crouch,
    run_jump,
    run_fall,
    crouch_walk,
    jump_cast,
    fall_cast,
    crouch_cast,
    death
}
It may seem an overkill but if you plan it carefully, you'll have a system where there's no need for dozens of flags to be set in order to do multiple things in a single state. As you see above whenever there are "combined" states, you can make a new state out of them. Care must be taken to ensure there's a way to enter/exit the state and define your rules about which states are "allowed" to be entered from a specific state.
 

Null-Z

Member
UPDATE: Added some more states as well as some exit conditions for the states.
currently, the object is affected by physics, but blinks in and out of visibility, and can only run right.
 

Null-Z

Member
Bump because I still need help in understanding why my state change conditions aren't working, and why my object can only move to the right.
 

Yal

šŸ§ *penguin noises*
GMC Elder
Even simpler state machine:
Code:
///Create event
state = pst_idle;//This is a script

///Step event
if(state >= 0){
  script_execute(state);
}
To change states, change the state variable. Set it to -1 or noone to disable behavior altogether.
 
Top