• Hey Guest! Ever feel like entering a Game Jam, but the time limit is always too much pressure? We get it... You lead a hectic life and dedicating 3 whole days to make a game just doesn't work for you! So, why not enter the GMC SLOW JAM? Take your time! Kick back and make your game over 4 months! Interested? Then just click here!

Help! Enemy wont attack correctly

Powerset

Member
Good evening everyone,

For some strange reason my enemy wont work as programmed. I want the enemy to switch from moving to attack state one the player comes into distance. But some strange reason the code is totally broken. I've been looking at some GML/GMS2 tutorials for it all day but nothing seems to get it working. The only thing I can get to work is the enemy moving back and forth which is not what I wanted, as I wanted to add a attack. So basically, Player comes into range of Enemy > Enemy does Attack > Enemy continues to moving in whatever direction. The code is totally flopped and doesnt even work! Can you guys enlighten me and help share your wisdom and knowledge to solve this issue?

First, I have the enemy object create event.
Code:
enum Estates
{
    
    MOVING,
    ATTACK,
}

state = Estates.MOVING;

vsp = 0;
grv = 0.5;
walkspeed = 0.5;
flash = 0;

hsp = walkspeed;
Next, the enemy step event.

Code:
switch (state)
{
    case Estates.MOVING: {
        #region Moving
    //Movement Calculations

vsp = vsp + grv;


//Horizontal Collision

if (place_meeting(x+hsp,y,oWall))
{
    while (!place_meeting(x+sign(hsp),y,oWall))
    {
        x = x + sign(hsp);
    }
    hsp = -hsp;
}
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;
#endregion
        
    }
    
    if(instance_exists(oPlayer)) && (distance_to_object(oPlayer) > 10) {
        
        state = Estates.ATTACK;
        
    
    break;
    }
    
    case Estates.ATTACK: {
        #region Attack
        
if (sprite_index != sTestEnemyAttack)
{
    sprite_index = sTestEnemyAttack;
    image_index = 0;
}

if (animation_end())
{
    state = Estates.MOVING;
}
        
        #endregion
    }
    break;
}
Of course instead of working as intended, as soon as the game loads, the enemy pops up doing a endless attack animation which is so weird, its not even in range of the player. All I want is for the enemy to walk around, player comes into area, enemy attacks and then continue to keep moving or attack again if player is there. I've been stuck all day can someone help me out? Is something wrong with the state machine? or just the way the code is set up? Thanks again its appreciated!
 

Mk.2

Member
For some strange reason my enemy wont work as programmed.
A more accurate description of your problem, is that it works exactly as programmed.

Code:
    if(instance_exists(oPlayer)) && (distance_to_object(oPlayer) > 10) {
    
        state = Estates.ATTACK;
Here you're telling the enemy to change to the attack state as long as the enemy's distance to the player is greater than 10, which is the opposite of your intention.
 
Last edited:

Powerset

Member
A more accurate description of your problem, is that it won't work as you intended, because it works exactly as programmed.

Code:
    if(instance_exists(oPlayer)) && (distance_to_object(oPlayer) > 10) {
   
        state = Estates.ATTACK;
Here you're telling the enemy to change to the attack state as long as the enemy's distance to the player is greater than 10, which is the opposite of your intention.
Even when I put it as less it does the same thing, Automatically loads the enemy into the attack animation and endlessly plays like that. It moves around as intended, comes to the player and stops and literally just plays the attack animation on 24/7. I just want it to move around aimlessly, when player comes into contact it attacks and continues to move.
 
Last edited:

Mk.2

Member
@Mk.2 , may have meant to change the greater than > to a less than < .
Huh? I was quoting his code, not posting the fixed version. The sentence that followed implied it should be changed to <.

@Powerset so now does the enemy begin in its move state, but then gets stuck once it switches to its attack state?
 

Powerset

Member
@Mk.2 Yup , In fact it doesn't even get stuck it begins in the attack state and disregards the move state, It just moves around infinitely attacking. When it comes close to the player it acknowledges the range and stops.
 

Powerset

Member
"Yup, but actually no."?

Where are you setting the sprite for the move state?
Wow I literally forgot to set the sprite for the move state. Let me get on that. How would I go about making him attack once and then run away from the player?
 

Niften

Member
This code was pretty much unreadable because of your indenting, so I reformatted it (I like K&R, but that is neither here nor there)
GML:
switch (state) {
    case Estates.MOVING: {
        #region Moving
        //Movement Calculations

        vsp = vsp + grv;

        //Horizontal Collision

        if (place_meeting(x+hsp,y,oWall)) {
            while (!place_meeting(x+sign(hsp),y,oWall)) {
                x = x + sign(hsp);
            }
            hsp = -hsp;
        }
        
        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;
        
        #endregion
    }
    
    if (instance_exists(oPlayer))
    && (distance_to_object(oPlayer) > 10) {
        state = Estates.ATTACK;
        break;
    }
    
    case Estates.ATTACK: {
        #region Attack
        
        if (sprite_index != sTestEnemyAttack) {
            sprite_index = sTestEnemyAttack;
            image_index = 0;
        }

        if (animation_end()) {
            state = Estates.MOVING;
        }
        
        #endregion
    }
    break;
}
Now that it's a little more readable, one issue is that the break statement for Estates.MOVING is wrapped up in that if statement. I'm not sure that this is what's causing your problem, but it definitely should be fixed to prevent any unexpected behaviors.

GML:
if (instance_exists(oPlayer))
&& (distance_to_object(oPlayer) > 10) {

    state = Estates.ATTACK;

}

break;
All cases within a switch statement need to have a corresponding break statement (unless you are utilizing default). Another note: Try to format/indent your code in such a manner that makes the logic clear to the reader - this will save time for your future self and the people who are trying to help you :)
 

Powerset

Member
This code was pretty much unreadable because of your indenting, so I reformatted it (I like K&R, but that is neither here nor there)
GML:
switch (state) {
    case Estates.MOVING: {
        #region Moving
        //Movement Calculations

        vsp = vsp + grv;

        //Horizontal Collision

        if (place_meeting(x+hsp,y,oWall)) {
            while (!place_meeting(x+sign(hsp),y,oWall)) {
                x = x + sign(hsp);
            }
            hsp = -hsp;
        }
       
        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;
       
        #endregion
    }
   
    if (instance_exists(oPlayer))
    && (distance_to_object(oPlayer) > 10) {
        state = Estates.ATTACK;
        break;
    }
   
    case Estates.ATTACK: {
        #region Attack
       
        if (sprite_index != sTestEnemyAttack) {
            sprite_index = sTestEnemyAttack;
            image_index = 0;
        }

        if (animation_end()) {
            state = Estates.MOVING;
        }
       
        #endregion
    }
    break;
}
Now that it's a little more readable, one issue is that the break statement for Estates.MOVING is wrapped up in that if statement. I'm not sure that this is what's causing your problem, but it definitely should be fixed to prevent any unexpected behaviors.

GML:
if (instance_exists(oPlayer))
&& (distance_to_object(oPlayer) > 10) {

    state = Estates.ATTACK;

}

break;
All cases within a switch statement need to have a corresponding break statement (unless you are utilizing default). Another note: Try to format/indent your code in such a manner that makes the logic clear to the reader - this will save time for your future self and the people who are trying to help you :)
Thank you so much ill keep that note in mind when dealing with future code. The break in statement being wrapped up was the issue. How would I set the enemy to hit once and then walk in the other direction once the animation ends?
 

Niften

Member
Thank you so much ill keep that note in mind when dealing with future code. The break in statement being wrapped up was the issue.
Understanding your code is key to fixing any error ;)
How would I set the enemy to hit once and then walk in the other direction once the animation ends?
I'm not going to do it for you, but I'll nudge you in the right direction. When approaching any problem in programming, focus on exactly what you want to accomplish (enemy hits once, and walks in the other direction). Now focus on what you need to do to accomplish that. Perhaps something like this:

Attack
  • Begin enemy attack animation
    • Is the enemy touching the player when the "hit" frame happens?
      • If this is true, subtract health from the player and add knockback
  • Once the animation ends, set the state to Estates.MOVING
  • To change directions, it seems like you just need to do hsp = -hsp
 

Powerset

Member
Understanding your code is key to fixing any error ;)

I'm not going to do it for you, but I'll nudge you in the right direction. When approaching any problem in programming, focus on exactly what you want to accomplish (enemy hits once, and walks in the other direction). Now focus on what you need to do to accomplish that. Perhaps something like this:

Attack
  • Begin enemy attack animation
    • Is the enemy touching the player when the "hit" frame happens?
      • If this is true, subtract health from the player and add knockback
  • Once the animation ends, set the state to Estates.MOVING
  • To change directions, it seems like you just need to do hsp = -hsp
Perfect thanks man, I appreciate the advice and the nudge forward Ill try it right now and see what happens!
 

Yal

šŸ§ *penguin noises*
GMC Elder
Your issue is caused by the enemy having no cooldown between attacks. As soon as it finishes an attack, it's still able to do another if the player is close enough. Add a "cooldown" variable which is set to like a random number between 60-180 when you first enter the attack state, and which is reduced by 1 each step in the "move around randomly" state. In the check if the player is close enough, add an "&& !cooldown", i.e., only proceed if cooldown is false (less than or equal to 0) condition.

With that said, your code is a mess, so let's look at some stuff you should fix to be able to understand it better.

You're having the break and the closing curly of an if statement in the wrong order, this makes your code harder to read and might mess with the compiler:
1590567145156.png

In C-like languages, the cases of a switch statement are basically just syntactic sugar for goto jumps, so basically, jumping into the case label makes any code above it cease to exist. In C, the compiler parses ALL of the code, so it will detect and reject stuff like this if it makes no sense, but GM interprets one line at a time so it won't notice stuff like this until it tries to run the line. This can lead to weird bugs that lie under the surface for ages before being detected (the code makes sense during one control flow, but there's an edge case that makes it stop making sense, and it leads to errors - or worse, valid code that does something completely different than what you intended).

Try to write fully hierarchical code. Instead of something akin to ( [ { } ) ], where you open and close things in different orders, have ( [ { } ] ) sequences. In your code, both the case/break pairs and the region/endregion pairs are haphazardly placed; they're opening/closing pairs just like all types of parantheses, and other opening/closing pairs shouldn't "leak out" through them. All pieces of an open/close pair should be either fully inside another code block (the stuff inside an open/close pair) or fully outside it.

To reiterate what has been said before: if you don't understand what your code is doing, you're not going to be able to fix it once it stops doing what you want to. Having proper indentation and not having code blocks "leak" through a closing token like break keywords will both go a long way towards making it easier to read and understand.
 

Powerset

Member
Your issue is caused by the enemy having no cooldown between attacks. As soon as it finishes an attack, it's still able to do another if the player is close enough. Add a "cooldown" variable which is set to like a random number between 60-180 when you first enter the attack state, and which is reduced by 1 each step in the "move around randomly" state. In the check if the player is close enough, add an "&& !cooldown", i.e., only proceed if cooldown is false (less than or equal to 0) condition.

With that said, your code is a mess, so let's look at some stuff you should fix to be able to understand it better.

You're having the break and the closing curly of an if statement in the wrong order, this makes your code harder to read and might mess with the compiler:
View attachment 31459

In C-like languages, the cases of a switch statement are basically just syntactic sugar for goto jumps, so basically, jumping into the case label makes any code above it cease to exist. In C, the compiler parses ALL of the code, so it will detect and reject stuff like this if it makes no sense, but GM interprets one line at a time so it won't notice stuff like this until it tries to run the line. This can lead to weird bugs that lie under the surface for ages before being detected (the code makes sense during one control flow, but there's an edge case that makes it stop making sense, and it leads to errors - or worse, valid code that does something completely different than what you intended).

Try to write fully hierarchical code. Instead of something akin to ( [ { } ) ], where you open and close things in different orders, have ( [ { } ] ) sequences. In your code, both the case/break pairs and the region/endregion pairs are haphazardly placed; they're opening/closing pairs just like all types of parantheses, and other opening/closing pairs shouldn't "leak out" through them. All pieces of an open/close pair should be either fully inside another code block (the stuff inside an open/close pair) or fully outside it.

To reiterate what has been said before: if you don't understand what your code is doing, you're not going to be able to fix it once it stops doing what you want to. Having proper indentation and not having code blocks "leak" through a closing token like break keywords will both go a long way towards making it easier to read and understand.
Such excellent advice thank you very much. I never even thought about implementing a cooldown between attacks for the enemy. Ill try that and see how it works. Ill also work on writing clearer more precise code instead of a jumbled mess. Learning about GML has been really fun and this right here and your breakdown of the language will help and ill take it! Ill work on improving how I write so its easier to look for errors.
 
Top