Multifunction Button (solved)

SaraTonen

Member
I have my player so that they can either press the attack button and fire a shot or hold it and fire a shot based on a cooldown. The problem I'm having is that sometimes when trying to do the press then release action, additional shots might be fired. I assume it has to do with the variable 'button_held' and there being an in between frame or two where it's value is true for both actions (held or pressed). Any thoughts would be appreciated.

Code:
//Player End Step

//Shoot Projectile
if (player_state == 0)
{
    if (key_attack)
    {
        if (button_held > projectile_cooldown && energy >= 1) //Player Holding Button
        {
           
            var _inst = instance_create_layer(x+lengthdir_x(16, dir), y+lengthdir_y(16, dir), "Instances", obj_p_projectile);
            with (_inst) direction = other.dir;
            energy -= projectile_cost;
            button_held = 0;   
        }
        else
        {
            button_held++;
        }
    }
    else
    if (button_held > 0) //Player Pressed then Released Button
    {
        if (button_held < projectile_cooldown && energy >= 1)
        {
            var _inst = instance_create_layer(x+lengthdir_x(6, dir), y+lengthdir_y(6, dir), "Instances", obj_p_projectile);
            with (_inst) direction = other.dir;
            energy -= projectile_cost;
            button_held = 0;   
        }
    }
}
 

samspade

Member
It would be helpful to see how key_attack is set as the else if there doesn't look right. You also have button_held < projectile_cooldown instead of button_held > projectile_cooldown.

I think you could get the same, or very similar results with the following code:

GML:
//Shoot Projectile
if (player_state == 0)
{
    if (key_attack)
    {
        button_held++;
        if (button_held > projectile_cooldown && energy >= 1) //Player Holding Button
        {
        
            var _inst = instance_create_layer(x+lengthdir_x(16, dir), y+lengthdir_y(16, dir), "Instances", obj_p_projectile);
            with (_inst) direction = other.dir;
            energy -= projectile_cost;
            button_held = 0; 
        }
    }
}
In this case, since you're updating button_held before the check, you don't need the else if at all (depending on what you're going for).
 

FoxyOfJungle

Kazan Games
"other" used in collision Event

you should use like this:
_inst.direction = other.dir;
No. She used it correctly, "other" can be used in other ways besides collisions. I suggest reading the manual. See also about with().

The other was used there because it used "with()" to refer to the instance id. So, what was defined inside with() { } will be "inside the instance", and the other will be used to get/set the variable from the other instance, which in this case is the object that just instantiated the obj_p_projectile.

The code however could be written like this:
GML:
_inst.direction = dir;
 
Last edited:

SaraTonen

Member
It would be helpful to see how key_attack is set as the else if there doesn't look right. You also have button_held < projectile_cooldown instead of button_held > projectile_cooldown.

I think you could get the same, or very similar results with the following code:

GML:
//Shoot Projectile
if (player_state == 0)
{
    if (key_attack)
    {
        button_held++;
        if (button_held > projectile_cooldown && energy >= 1) //Player Holding Button
        {
     
            var _inst = instance_create_layer(x+lengthdir_x(16, dir), y+lengthdir_y(16, dir), "Instances", obj_p_projectile);
            with (_inst) direction = other.dir;
            energy -= projectile_cost;
            button_held = 0;
        }
    }
}
In this case, since you're updating button_held before the check, you don't need the else if at all (depending on what you're going for).
Alas, this won't do. The reason I used the if else there and then checked button_held > 0 was to see if the button is pressed for a shorter amount of time, as opposed to it being held until is reaches the cooldown limit (in this case 30 frames). Thank you for the suggestion.

Also, key_attack = gamepad_button_check(global.gp_index, gp_face3); or key_attack = mouse_button_check(mb_left) depending on control type.
 
Last edited:

SaraTonen

Member
Code:
//Shoot Projectile
if (player_state == 0)
{
    if (key_attack)
    {
        button_held++;
        if (button_held > projectile_cooldown && energy >= 1)
        {
            var _inst = instance_create_layer(x+lengthdir_x(16, dir), y+lengthdir_y(16, dir), "Instances", obj_p_projectile);
            with (_inst) direction = other.dir;
            energy -= projectile_cost;
            button_held = 0;
        }
       
    }
    else
    {
        if (button_held > 0 && button_held < 10 && energy >= 1)
        {
            var _inst = instance_create_layer(x+lengthdir_x(16, dir), y+lengthdir_y(16, dir), "Instances", obj_p_projectile);
            with (_inst) direction = other.dir;
            energy -= projectile_cost;
            button_held = 0;
        }
    }
}
After adding the else to your code, it seems to be working as I was intending. Thank you all. I'm gonna leave this open for now as I haven't had time to properly test it out yet, but will update in a little while.
 

SaraTonen

Member
And it's a no go still but that's okay. I'll keep playing around with it and hopefully cobble something together that feels how I want it to.
 

samspade

Member
Alas, this won't do. The reason I used the if else there and then checked button_held > 0 was to see if the button is pressed for a shorter amount of time, as opposed to it being held until is reaches the cooldown limit (in this case 30 frames). Thank you for the suggestion.

Also, key_attack = gamepad_button_check(global.gp_index, gp_face3); or key_attack = mouse_button_check(mb_left) depending on control type.
Maybe a better description of what is wrong would help. In the OP you said: "The problem ... is that sometimes when trying to do the press then release action, additional shots might be fired." This is caused by having that else. You are almost guaranteeing that if you were pressing the attack button, and have just released it, another shot will be fired regardless of when the last shot was fired (how close is dependent upon what value projectile_cooldown has).
 

SaraTonen

Member
Sorry I didn't explain it well. What I was trying to do is use the same button (key_attack) to handle both firing the weapon manually or firing based on a cooldown if the button is held down. I'm using a gamepad and gamepad_button_check for key_attack. The problem I'm encountering is occasionally if I'm holding the button, when I release it will fire three or four shots very rapidly.
 

samspade

Member
Sorry I didn't explain it well. What I was trying to do is use the same button (key_attack) to handle both firing the weapon manually or firing based on a cooldown if the button is held down. I'm using a gamepad and gamepad_button_check for key_attack. The problem I'm encountering is occasionally if I'm holding the button, when I release it will fire three or four shots very rapidly.
I don't see anything in the code that would allow it to fire 3-4 shots rapidly. It looks like it should be possibly firing 2 shots very rapidly though when you release for the reasons stated above.

GML:
//Shoot Projectile
if (player_state == 0)
{

    if (key_attack)
    {
        if (!prev_key_attack) && (energy >= 1) {
            with (instance_create_layer(x+lengthdir_x(16, dir), y+lengthdir_y(16, dir), "Instances", obj_p_projectile)) {
                direction = other.dir;
            }
            button_held = 0;
        }

        button_held++;
        if ((button_held > projectile_cooldown) && (energy >= 1))
        {
            with (instance_create_layer(x+lengthdir_x(16, dir), y+lengthdir_y(16, dir), "Instances", obj_p_projectile)) {
                direction = other.dir;
            }
            energy -= projectile_cost;
            button_held = 0;
        }
      
    }

}

prev_key_attack = key_attack
What about something like this?
 

SaraTonen

Member
Sorry I kinda drifted away from this. When I get stuck on something, I tend to take a break and focus on other things. But I have been messing with it again and understand why I have a problem but still am having trouble solving it.

To reiterate, I want the player to be able to fire a single shot whenever (if they have the energy). I also want them to be able to hold the shoot button to charge a shot and upon release, said shot fires. The individual components both work. I can shoot whenever, and I can charge a shot. But for one frame at the start both key_shoot and key_charge_shot are true so when I start to charge a shot, a single shot still fires.

Code I'm using:

Code:
key_shoot = gamepad_button_check_pressed(global.gp_index, gp_face3);
key_charge_shot = gamepad_button_check(global.gp_index, gp_face3);

//Shoot Projectiles
if (player_state == 0)
{
    //Single Shot
    if (key_shoot && energy >= 1 && charging_shot == false)
    {
        var _inst = instance_create_layer(x+lengthdir_x(16, dir), y+lengthdir_y(16, dir), "Instances", obj_p_projectile);
        with (_inst) direction = other.dir;
        //ADD SPRITE FOR SHOT FLASH
        energy -= projectile_cost;
        exit;
    }
    
    //Charge Shot
    if (key_charge_shot)
    {
        charge_shot_counter++;
        if (charge_shot_counter >= 30) charging_shot = true;
        if (charge_shot_counter >= 60) charge_shot_counter = 60;
    }
    else
    {
        if (charge_shot_counter >= 60 && energy >= 1)
        {
            var _inst = instance_create_layer(x+lengthdir_x(16, dir), y+lengthdir_y(16, dir), "Instances", obj_p_projectile);
            with (_inst) direction = other.dir;
            //ADD ANIMATION FOR CHARGING SHOT
            energy -= charge_shot_cost;
            charge_shot_counter = 0;
            charging_shot = false;
        }
        else
        {
            charge_shot_counter = 0;
            charging_shot = false;
        }
    }
}
I tried using charging_shot to prevent the single shot, thus the charging_shot == false in it's initial check. I also tried setting charging_shot to true halfway through to prevent the first shot from firing and having an exit in the single shot code block as well. So I don't know.
 
When I create button I only have one state value per button. It can be one of enum States {idle, hover, press, hold, release} and I cause the action for clicking it to take place on the released state. I don't use the press/release mouse button functions, I just have released state occur one frame after it is not pressed with a state value of hold, then after that frame the state goes to idle.

The one frame that it has a state value of released is the one where the action occurs.
 

SaraTonen

Member
Ah I see what you're saying. Well after a little tinkering I set up something like you said and it seems to be working now but I still need to play with it and make sure. Thank you so much for that.
 

SaraTonen

Member
It does in fact seem to be working. I'll mark the thread as solved. Here's the code I ended up with if anyone ever sees or needs this. I'm sure there's much better/efficient ways to do this but it's working and I'm learning.

Code:
//Manual Shot/Charged Shot
if (key_shoot)
{
    charge_shot_counter++;
    if (charge_shot_counter > 10 && !instance_exists(obj_charge_shot_effect)) 
        instance_create_layer(x+lengthdir_x(16, dir), y+lengthdir_y(16, dir), "Effects", obj_charge_shot_effect);
    if (charge_shot_counter >= 60) shot_charged = true;
}
else if (key_shoot_released)
        {
            if (charge_shot_counter <= 10) //fire single shot    
            {
                var _inst = instance_create_layer(x+lengthdir_x(16, dir), y+lengthdir_y(16, dir), "Instances", obj_p_projectile);
                with (_inst) direction = other.dir;
                instance_create_layer(x+lengthdir_x(16, dir), y+lengthdir_y(16, dir), "Effects", obj_shot_flash);
                energy -= projectile_cost;    
            }
            else if (charge_shot_counter >= 60) //Fire Charged Shot
                   {
                       var _inst = instance_create_layer(x+lengthdir_x(16, dir), y+lengthdir_y(16, dir), "Instances", obj_p_projectile);
                       with (_inst) direction = other.dir;
                       energy -= charge_shot_cost;
                       charge_shot_counter = 0;
                       shot_charged = false;
                       if (instance_exists(obj_charge_shot_effect)) instance_destroy(obj_charge_shot_effect);
                   }
            charge_shot_counter = 0;
            shot_charged = false;
        }
 
Top