GameMaker Trouble with attack animation not working/playing only one frame

L

Lovecraft

Guest
Hello! I'm really new to programming and game dev, and have run into a problem with my code. No matter what I've looked up and tired nothings been able to fix my problem. Does anyone know what i'm doing wrong? Any help would be appreciated, thank you. Below is my step code. I have a creation code and step code only.

Code:
//Get Player Input
key_left = keyboard_check(vk_left);
key_right = keyboard_check(vk_right);
key_jump = keyboard_check_pressed(vk_space);
key_attack = keyboard_check_pressed(vk_shift);

//Calculate Movement
var move = key_right - key_left;

hsp = move * walksp;

vsp = vsp + grv;

if (place_meeting(x,y+1,oWall)) && (key_jump)
{
    vsp = -10;
}
//Horizontal Collision
if (place_meeting(x+hsp,y,oWall))
{
    while (!place_meeting(x+sign(hsp),y,oWall))
    {
    x = x + sign(hsp);
    }
    hsp = 0;
}

x = x + hsp;

//Vertical Collision
if (place_meeting(x,y+vsp,oWall))
{
    while (!place_meeting(x,y+sign(vsp),oWall))
    {
    y = y + sign(vsp);
    }
    vsp = 0;
}
y = y + vsp;

//Animation
if (!place_meeting(x,y+1,oWall))
{
    sprite_index = sPlayerA;
    image_speed =0;
    if (sign(vsp) > 0) image_index = 1; else image_index = 0;
}
else
{
    image_speed = 1;
    if (hsp == 0)
    {
        sprite_index = sPlayer;
    }
    else
    {
        sprite_index = sPlayerR;   
    }
}

if (hsp != 0) image_xscale = sign(hsp);

if (hsp = 0)
{
    if (key_attack)
    {
        sprite_index = sPlayerGA;
        image_speed = 1;
    }
}
 

samspade

Member
You are resetting your sprite_index each step.

this code:
Code:
if (hsp = 0)
{
    if (key_attack)
    {
        sprite_index = sPlayerGA;
        image_speed = 1;
    }
}
is immediately overwritten with this code:
Code:
if (!place_meeting(x,y+1,oWall))
{
    sprite_index = sPlayerA;
    image_speed =0;
    if (sign(vsp) > 0) image_index = 1; else image_index = 0;
}
else
{
    image_speed = 1;
    if (hsp == 0)
    {
        sprite_index = sPlayer;
    }
    else
    {
        sprite_index = sPlayerR;   
    }
}
The animation code sets the sprite. The attack code sets the sprite, but since it only runs on key_pressed, the very next step the animation code sets the sprite back.

A simple fix would be to add a variable and set it to true during attack and false at animation end of the attack and only allow the animation code to run if that variable is false. However, a better solution would be to look into state machines for the player and put your animation into there - e.g. an animation for the walking state, jumping state, and attacking state.
 
A

Alex_Beach

Guest
You need to set an alarm so the animation happens for longer than 1 frame. What's happening is the step event will run your code every frame, thus if your attack variable is not true just 1 frame after it was true, it will not perform it.
Add this to your if: if (key_attack&&alarm[0]<1). Then, in the if statement add: alarm[0]=30.

You will also have to change the "ifs" of your other code that changes the sprite_index to match this
 
L

Lovecraft

Guest
State machines, okay. I'll try and look that up and see what I can find. And I'll give the alarm a try also.
 
L

Lovecraft

Guest
You need to set an alarm so the animation happens for longer than 1 frame. What's happening is the step event will run your code every frame, thus if your attack variable is not true just 1 frame after it was true, it will not perform it.
Add this to your if: if (key_attack&&alarm[0]<1). Then, in the if statement add: alarm[0]=30.

You will also have to change the "ifs" of your other code that changes the sprite_index to match this

Where in the code do I place the alarm and "if" change though? I'm extremely new to programming, this is my first time ever.
 
L

Lovecraft

Guest
You are resetting your sprite_index each step.

this code:
Code:
if (hsp = 0)
{
    if (key_attack)
    {
        sprite_index = sPlayerGA;
        image_speed = 1;
    }
}
is immediately overwritten with this code:
Code:
if (!place_meeting(x,y+1,oWall))
{
    sprite_index = sPlayerA;
    image_speed =0;
    if (sign(vsp) > 0) image_index = 1; else image_index = 0;
}
else
{
    image_speed = 1;
    if (hsp == 0)
    {
        sprite_index = sPlayer;
    }
    else
    {
        sprite_index = sPlayerR;  
    }
}
The animation code sets the sprite. The attack code sets the sprite, but since it only runs on key_pressed, the very next step the animation code sets the sprite back.

A simple fix would be to add a variable and set it to true during attack and false at animation end of the attack and only allow the animation code to run if that variable is false. However, a better solution would be to look into state machines for the player and put your animation into there - e.g. an animation for the walking state, jumping state, and attacking state.

Thank you! Im looking into state machines and see if I can figure out how to make a simple one. At the moment all I need is the run, jump, and ground attack. The air attack is animated but it dosent need to be coded until im sure i can get any attack animation to work.
 

CMAllen

Member
I should also point out that you're only checking for your attack animation key using keyboard_key_pressed(). This function only checks if the key has been pressed between the previous step and the current step. Which means that in the next step, it won't be true, and your attack stops. The player would have to rapidly press the attack key to ever get it to run more than a single step. If you switch to keyboard_check() you'll have a different issue, where the player has to press and hold the attack key to continue its execution or it immediately stops.

One of the things you'll need to rethink with state machines is that you only check for input if a given state should listen for any, and then, only inputs that are relevant to that state. In the case of a basic attack state, unless you want the player to stop an attack early, you shouldn't be checking for input. The input is what puts your player into the attack state to begin with. Only once the attack has completed (like the associated animation is finished, or timer has counted down, etc) do you revert back to some other state that checks for user input again. Hope that helps gets you thinking about how to use and apply state machine logic.
 
G

Goemon

Guest
Hi all, I'm following the same tutorial so have the similar initial code and I've tried to create a state machine for a normal state and an attacking state. However I'm still getting the same problem with the game only playing the first frame of the attack.

So I have the following code

//Attacking
if (key_attack)
{
hsp = 0;
vsp = 0;
state = states.attack;
}

Then within my states.attack script I have

//Animation
sprite_index = spr_player_attack;

if (key_attack = 0)
{
state = states.normal;
}

As I understand it the code is getting to the attack script as I do get the first frame but don't know why it's not finishing before moving back to the normal script.

Sorry for my terrible code, I'm literally just starting to learn this myself. :)
 

CMAllen

Member
How and when are you checking for your attack input? If you check for your attack input with or while running your attack state code, then the instant the attack_key returns false, your attack terminates. As I said above -- when you use states, it is important that you restrict your input checking to only the kinds of input that are relevant to the state. I realize it may seem counter-intuitive, but an attack input (unless you want players to be able to prematurely terminate the state) is not relevant to a basic attack state. That input is what changes an object to the attack state while in some other state. You have to think about states in terms of what they can or cannot do. What affects the attack state while it's running? For a simple attack, not player input. The only thing that alters the attack state is when it finishes. So how do you capture when it's 'finished'? Does this state have its own sprite animation? If so, the attack state is altered when that animation has finished, and the object in question changes to another state (such as its default state), at which point you start checking for relevant input for that state.

Let's break down the input and states for a simple Zelda clone.

The player has the following states: stationary, moving, attacking, using a secondary item, using their inventory, paused, or defeated. While stationary, the player can change to the following states: move (d-pad), attack (A button), use their secondary item (b button), toggle their inventory (select), or pause the game (start). That would be the normal or default state, as this is how the player object 'acts' when it has been given no input. While moving, a player can do all of the same as while stationary, so those two states are nearly the same, with one exception -- after checking for input, the player needs to move accordingly. Since the states are so similar, they could be combined into one. However, when attacking, the player cannot change to following states: move, attack (because they're already in it), use a secondary item, or toggle their inventory, but they can still pause the game, so the only input to check is the (start) button. Once the attack sprite has finished its animation, the player's state is changed back to the default state. Using a secondary item is similar to attacking, except that the 'attack' action varies based on what the secondary item is (so that'd be a variable holding a script id for the secondary item's effect -- bombs, arrows, boomerang, etc).

So what about the last 3 states? Well, ideally, these shouldn't be controlled by the player object, so the player object only needs to know under what circumstances it should enter them or leave them. The pause state ends when the player hits the (start) button again. The inventory state ends when the player exits the inventory with the (B button) or (select). And the dead state persists until the player reloads or starts over. When ended, both the dead and inventory state revert back to the default state. The pause state, however, reverts back to whatever state it was in prior to entering the pause state.
 
G

Goemon

Guest
I have a get inputs script which I add at the top of my normal and attack states

scr_getinputs ()

Within the get_inputs script I have:
key_left = keyboard_check (vk_left)
key_right = keyboard_check (vk_right)
key_jump = keyboard_check_pressed (vk_space)
key_attack = keyboard_check_pressed (vk_shift)

As far as I understand it I am doing what you say...
Within my normal scr, if someone presses key_attack I tell the code to go to scr_player_attack and then it has the animation to play.

If I remove the get_inputs () from the attack state, will that fix the issue? (I'm not in front of my PC right now to check)
As I understand it telling the code to move to scr_player_attack will move it to the next step at which point no inputs have been pressed so nothing should affect the animation if I don't input anything.

Sorry if these questions are dumb, I am super new to this.
 
Top