GML [SOLVED] How to play an animation?

Dr_Nomz

Member
I'm trying to have an enemy attack, and then switch back to it's normal state after attacking. Problem is I can't figure out how to make it play the animation properly. It'll start, stay on the first frame, and do nothing.

I start off in the CREATE event with a simple state engine, one for attacking, one for normal action.
GML:
///state

enum enemy_state{
  normal,
  attack
}

state = enemy_state.normal;
After that I use the step event to hit a simple key (for testing) to make the enemy attack.
GML:
///STEP
if keyboard_check_pressed(vk_space){
  state = enemy_state.attack
}
If the key is pressed, the enemy switches to an attack state, which is handled in the draw event:
GML:
///DRAW
if state = enemy_state.normal{
  draw_sprite_ext(spr_Enemy,image_index,x,y,1,1,0,c_white,1);
}
if state = enemy_state.attack{
  draw_sprite_ext(spr_Attack,image_index,x,y,1,1,0,c_white,1);
}
And the enemy is supposed to switch back to it's normal state after the animation plays out. (animation end event)
GML:
if sprite_index == spr_Attack{
  state = enemy_state.normal
}
But when I press the key, it just switches to the first frame of the animation and does nothing. Why isn't it playing?
 

Dr_Nomz

Member
Wow, didn't think I had to set it manually. Okay, now it plays, but it completely ignores the animation end event. Why is that?
 
If it's not blatantly clear from @Nidoking, this line:
if sprite_index == spr_Attack{ state = enemy_state.normal }
Is checking the sprite_index. Can you see sprite_index = sprAttack anywhere in your code? So you could either ACTUALLY set the sprite_index in your state machine, instead of simply drawing a sprite, and then your code would run fine, or you could change the end animation code to this:
Code:
if (state == enemy_state.attack) {
   state = enemy_state.normal;
}
 

Dr_Nomz

Member
It didn't work, for some reason it just flashes a frame of the attack sprite before switching immediately back to the normal state/sprite.

I used the state suggestion you gave, btw.

GML:
///Animation End
if state == enemy_state.attack{
  state = enemy_state.normal
}
It seems like GM is completely ignoring the fact that this is an animation END event, and just runs the code when the animation STARTS, which makes no sense.

Is there some way to make it wait until the animation ends? Because this event is broken.
 
Last edited:
Because this event is broken.
That is the refrain of the novice, avoid using it. Something actually being a bug in GMS is way more unlikely than something being wrong with your code. @MaxLos' suggestion is good and is probably the real culprit. If you are changing sprites, whether through sprite_index or simply drawing different ones, you ALWAYS need to reset image_index to 0, otherwise it'll just start playing the new animation at whatever index it was at just before the switch (usually not specifically 0 index).
 

Dr_Nomz

Member
Good suggestion Max, but that didn't change anything.

Current draw event:
GML:
image_speed = 30;
if state == enemy_state.normal{
  draw_sprite_ext(spr_Enemy,0,x,y,1,1,0,c_white,1);
}
if state == enemy_state.attack{
  draw_sprite_ext(spr_Attack,0,x,y,1,1,0,c_white,1);
}
I tried setting image index to 0 in the step, tried it in the draw, and even this in the animation end:
GML:
if state == enemy_state.attack && image_index > 5{
  state = enemy_state.normal
}
Nothing worked. Any other ideas?
 

Jezla

Member
Your second argument for your draw_sprite_ext calls indicate that when drawing the sprite it should always draw frame 0 of the sprite. You need to change it to image_index or -1 to draw the current animation frame, while continuing to reset your image_index whenever you reset your sprite.

EDIT: Sorry, saw that you already tried that. See my suggestion below.



Also, image speed is a multiplier, so setting it to 30 makes your animation play 30 times it's normal speed. Unless that's what you intend, you probably should set it to 1.
 
Last edited:

Dr_Nomz

Member
GML:
if state == enemy_state.normal{
  draw_sprite_ext(spr_Enemy,image_index,x,y,1,1,0,c_white,1);
}
if state == enemy_state.attack{
  image_speed = 2;
  draw_sprite_ext(spr_Attack,image_index,x,y,1,1,0,c_white,1);
}
What you suggested helped, but the problem seems to be image speed. If it's 1, it doesn't move at all. Only at 2 does it play correctly. Why is that?

EDIT: Alright that's it I'm giving the project files. It's literally JUST what I've been asking here, nothing else.
GMS 1.4 btw. Can you see ANYTHING out of place?
 

Jezla

Member
Okay, your main problem is that your main sprite for the enemy is only one frame, so it essentially doesn't animate. The second problem is that you weren't actually using the animation end event, you were using the press-<Left> event. I changed that to the animation end event, left the image_speed at default (I commented that line out of the draw event), and in the end step event, set the image_index to 0. Once I set the enemy sprite to the same number of frames as the attack sprite, it worked like you want it to. I also got rid of the press-<up> event, as I didn't see what purpose it served to set the room_speed to 1000.

I would suggest, however, that if you want to switch between sprites with different animation lengths, that you change it by assigning the sprite_index, rather than drawing the sprites manually in the draw event. Then you can change the sprite back in the animation end event.
 

Dr_Nomz

Member
I think I got it, but it's acting a bit odd. I looked at my original code (in a different project, using the draw_sprite method) and for some reason that one would end the animation a frame early, without playing the final frame in the animation.

As for this project, for some reason the first frame wasn't playing at all and just got skipped, but I think I fixed that by setting the sprite index in the end step event.

But while that problem was happening it would also play the first frame AFTER the animation played before stopping it. Any way to fix that?

I tried the manual:
In GameMaker: Studio sprites are not static things as they can have sub-images and be animated at different speeds. Each frame of an animation (called a sub-image) has its own number, starting at 0, which can be checked in code or even through actions, but sometimes all you really need to know is when the animation has ended. That's when this event is triggered, right at the end of the animation, the moment that the sub image index loops back to the beginning (0). This event is really useful for many things, for example an explosion object where you can set the instance to destroy itself after the last frame of the animation has been shown.

NOTE: Because the animation has already looped when this event fires, if you want to freeze the animation on the exact last frame then you will need to manually reset the image_index to the last frame (image_number - 1) in this event.
But image_number didn't help, since it's read only. I tried setting the image index to -1 in the animation end event, and that helped, but now it just plays the last frame for 2 steps.

Just gonna paste the whole object code so I don't leave anything out:
GML:
[B]Information about object: obj_Enemy[/B]
Sprite: spr_Enemy
Solid: false
Visible: true
Depth: 0
Persistent: false
Parent:
Children:
Mask:


No Physics Object

Create Event:
execute code:

///state
canshoot=0;

enum enemy_state{
  normal,
  attack
}

state = enemy_state.normal;

End Step Event:
execute code:

canshoot-=1;
if keyboard_check_pressed(vk_space){
  image_index = 0;
  state = enemy_state.attack
  sprite_index = spr_Attack;
}

Other Event: Animation End:
execute code:

///animation end
if state == enemy_state.attack{
  image_index = -1;
  state = enemy_state.normal
}

Draw Event:
execute code:

draw_self();
draw_text(100,100,canshoot);
if state == enemy_state.normal{
  //draw_sprite_ext(spr_Enemy,image_index,x,y,1,1,0,c_white,1);
  sprite_index = spr_Enemy;
}
if state == enemy_state.attack{
  //image_speed = 2;
  //draw_sprite_ext(spr_Attack,image_index,x,y,1,1,0,c_white,1);
  //sprite_index = spr_Attack;
}

Key Press Event for <Up> Key:
execute code:

room_speed = 30;

Key Press Event for <Down> Key:
execute code:

room_speed = 2;

Key Press Event for R-key Key:
execute code:

room_restart();
 

Dr_Nomz

Member
OH and the up and down keys are to set the room speed, so I can examine the sprite's animation more closely.

EDIT: Also nevermind about all that, turns out I don't need the draw event at ALL, I just need to keep it all in the step event. Thanks for all the help and suggestions.
 
But image_number didn't help, since it's read only. I tried setting the image index to -1 in the animation end event, and that helped, but now it just plays the last frame for 2 steps.
Just so you know, the manual is not telling you to set image_number to something, it's telling you to set image_index = image_number-1. Because image_index counts from 0, if you have 7 frames, the image_number will be 7, but the last frame will have an image_index of 6, so image_number-1 is what image_index needs to get set to to be the last frame.
 
Top