GML Trouble with Enemy Attack Sprite Manipulation

I have just completed the wonderful GML2 Complete Platformer Tutorial series by Shaun Spalding on Youtube. The code is identical to how he set up his simple shooter, I have just changed the art and little details and values here and there, and everything works!

However, my enemies do not have guns that they "hold". I instead have fireballs that shoot from the enemy's mouth. The gun object is a tiny, red, transparent square on the head of the enemy. It detects where the player is, aims, and fires appropriately.

I have a second enemy sprite that preforms the action of shooting a fireball. The problem is that I can't figure how to switch to the attack_fireball sprite, shoot the fireball, then switch back to the enemy_run sprite and have it continue running around until it wants to attack again.

Code is as follows:

In (obj_enemy > create event), I have the following code that creates the tiny, red square ("obj_invisible_gun") on the head area of the enemy instances where the "hasweapon" variable is true.


if (hasweapon){
mygun = instance_create_layer(x,y,"Gun",obj_invisible_gun)
with (mygun){
owner = other.id;
}
} else mygun = noone;



In (obj_invisible_gun > step event) the invisible gun finds the player if within a certain radius and shoots a fireball if there are no walls in between them.


if (instance_exists(obj_player)){
if (obj_player.x < x) image_yscale = -image_yscale;
if (point_distance(obj_player.x,obj_player.y,x,y) < 600){
image_angle = point_direction(x,y,obj_player.x,obj_player.y);
countdown--;
if (countdown <= 0){
countdown = countdownrate;
if (!collision_line(x,y,obj_player.x,obj_player.y,obj_wall,false,false)){
attacking = true;
audio_sound_pitch(snd_gun3,choose(0.8,1.0,1.2));
audio_play_sound(snd_gun3,5,false);
//Shoot
with(instance_create_layer(x,y,"Bullets",obj_fireball)){
image_speed = 1;
spd = 5;
//Offset direction of bullet
direction = other.image_angle + random_range(-3,3);
image_angle = direction;
}
}
}
}
}


I've been trying different "with" statements, and moving code into different objects, grabbing IDs and I even tried creating an "enum" function for different states of an enemy but I almost screwed everything up by forgetting what I changed!! LOL!

I just can't seem to find the right combination and order of code to have an enemy see a collision path to the player with their invisible gun, stop moving, change to the attack sprite, shoot the fireball with their invisible gun, then return to the run sprite and resume it's horizontal and vertical speeds.

Any tips or advice?
Thanks
Zack
 

MaxLos

Member
Hmm, I don't really think a timeline is necessary here. If he just wants the enemy to stop and play an animation whenever they shoot a fireball, just changing the enemy's sprite_index and settting their hsp to 0 whenever their about to shoot a fireball would be easier. Then he could use an animation end event or alarm to set them back to their regular sprite
 

Nidoking

Member
True, you could do that, but for something with that many steps that will generally happen at specific intervals, I think a timeline is simpler. There's a whole section in the help file on timelines, which I recommend. The first post asked for tips or advice, so that's my advice.
 
Thanks I will read it!

I have updated the enum a bit, and I have two states at the moment until they work correctly. An idle and wander state. The idle state works, and it switches to the wander state and back to idle, but even though I updated the walkspeed variable at the beginning of each different state (hsp = walksp in the creation code) if won't move. It only changes the sprite index but not the walksp variable. :S

Here's the update, my thinking behind this was FIRST update the variables I want to change (walksp, sprite_index), SECOND I check for collisions and move with the newly updated variables. I'm at a loss now.

if (state == STATES.IDLE){
#region Idle
// Behaviour
walksp = 0;
counter++;

// Transition Triggers
if (counter >= room_speed * 3){
var change = choose(0,1);
switch (change){
case 0: state = STATES.WANDER;
case 1: counter = 0; break;
}
}

//if (collision_circle(x,y, 200, obj_player, false, false)){
//state = STATES.ALERT;
//}

// Sprite
sprite_index = spr_idle;
#endregion
} else if (state == STATES.WANDER){
#region Wander
// Behaviour
walksp = 1;
counter++;

// Transition Triggers
if (counter >= room_speed * 3){
var change = choose(0,1);
switch (change){
case 0: state = STATES.IDLE;
case 1: counter = 0; break;
}
}

//if (collision_circle(x,y, 200, obj_player, false, false)){
//state = STATES.ALERT;
//}

// Sprite
sprite_index = spr_wander;
#endregion
}

EnemyCollisionsAndMove();
EnemyInAir();

if(hsp != 0) image_xscale = sign(hsp) * size;
image_yscale = size;


So for every step it will perform the state that it is in THEN check collisions and move outside of those if statements.
 

Nidoking

Member
(hsp = walksp in the creation code)
This doesn't set hsp to always have the value of walksp. This sets hsp to have a fixed value, which is whatever the value of walksp is at the time. hsp will never change again unless you set it to something else. You're not setting hsp in this state machine. That's why it's not moving.

And honestly, the "counter++; if counter >=" thing is exactly what a timeline would handle for you. As long as the room_speed doesn't change from room to room, you create a timeline that does whatever check at moment room_speed * 3 (whatever actual value that is, since I don't think you can use variables in moment indices) and either restart the timeline or set it to a new state. It also allows you to do things along the way, like that creating a projectile object you were talking about earlier. You can use timelines within the state machine, and you'd just have to check the timeline_position and timeline_running variables in the state machine to make sure you only change timelines when the one you want isn't already in progress. But you don't have to do that.
 
UPDATE!

I have FINALLY got it working! Here's the code if you're interested!

if (state == STATES.IDLE){
#region Idle
// Behaviour
moveY = moveY + grv;

//Vertical Collision
if(place_meeting(x,y+moveY,obj_wall)){
while(!place_meeting(x,y+sign(moveY),obj_wall)){
y = y+sign(moveY);
}
moveY = 0;
}
y += moveY;
counter += 1;

// Transition Triggers
if(counter >= room_speed * 3){
var change = choose(0,1);
switch (change){
case 0: state = STATES.WANDER;
case 1: counter = 0; break;
}
}
if(collision_circle(x,y, 250, obj_player, false,false)){
state = STATES.ALERT;
}

// Sprite
if(!place_meeting(x,y+1,obj_wall)){
grounded = false;
sprite_index = spr_jump;
image_speed = 0;
if(sign(moveY) > 0) image_index = 1; else image_index = 0;
} else {
grounded = true;
image_speed = 1;
sprite_index = spr_idle;
}

#endregion
} else if (state == STATES.WANDER){
#region Wander
//Behaviour
moveY = moveY + grv;
moveX = lengthdir_x(walksp,direction)

counter += 1;

//Horizontal Collision
if(place_meeting(x+moveX,y,obj_wall)){
while(!place_meeting(x+sign(moveX),y,obj_wall)){
x = x+sign(moveX);
}
moveX = -moveX;
}
x += moveX;

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

// Transition Triggers
if (counter >= room_speed * 3){
var change = choose(0,1);
switch (change){
case 0: state = STATES.IDLE;
case 1: direction = choose(0,180);
moveX = lengthdir_x(walksp,direction);
moveY = lengthdir_y(walksp,direction);
counter = 0;
}
if(collision_circle(x,y, 400, obj_player, false,false)){
state = STATES.ALERT;
}
}
// Sprite
if(!place_meeting(x,y+1,obj_wall)){
grounded = false;
sprite_index = spr_jump;
image_speed = 0;
if(sign(moveY) > 0) image_index = 1; else image_index = 0;
} else {
grounded = true;
image_speed = 1;
sprite_index = spr_wander;
image_xscale = sign(moveX);
}
#endregion
} else if (state == STATES.ALERT){
#region Alert
// Behaviour

if (instance_exists(obj_player)){
my_dir = sign(obj_player.x - x);
}
moveX = walksp*my_dir;
moveY = moveY + grv;

//Horizontal Collision
if(place_meeting(x+moveX,y,obj_wall)){
while(!place_meeting(x+sign(moveX),y,obj_wall)){
x = x+sign(moveX);
}
moveX = -moveX;
}
x += moveX;

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

// Transition Triggers
if (!collision_circle(x,y, 400, obj_player, false,false)){
state = STATES.IDLE;
}
if (collision_circle(x,y, 300, obj_player, false,false)){
state = STATES.ATTACK;
}

// Sprite
if(!place_meeting(x,y+1,obj_wall)){
grounded = false;
sprite_index = spr_jump;
image_speed = 0;
if(sign(moveY) > 0) image_index = 1; else image_index = 0;
} else {
grounded = true;
image_speed = 1;
sprite_index = spr_run;
image_xscale = sign(moveX);
}
#endregion
} else if (state == STATES.ATTACK){
#region Attack
// Behaviour
moveY = moveY + grv;

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

if (mygun == noone){
mygun = instance_create_layer(x,y,"Gun",obj_invisible_gun)
mygun.owner = id;
mygun.image_xscale = image_xscale;
image_index = 0;
}
// Transition Triggers
if (image_index > image_number-1) and (sprite_index == spr_attack){
state = STATES.ALERT;
}

// Sprite
if(!place_meeting(x,y+1,obj_wall)){
grounded = false;
sprite_index = spr_jump;
image_speed = 0;
if(sign(moveY) > 0) image_index = 1; else image_index = 0;
} else if (instance_exists(obj_player)) and (!collision_line(x,y,obj_player.x,obj_player.y,obj_wall,false,false)){
grounded = true;
image_speed = 1;
sprite_index = spr_attack;
image_xscale = sign(obj_player.x - x);
} else {
grounded = true;
image_speed = 1;
sprite_index = spr_idle
}
#endregion
}
 

Nidoking

Member
I will add that you don't need to use switch with a boolean (true or false, 0 or 1) variable. You can just do
Code:
var change = choose(true, false);
if (change)
{
  state = whatever;
}
else
{
  state stuff
}
 
Top