SOLVED Determine if player has successfully dodged projectile

I am making a bullet hell-style combat system. I just incorporated a mechanic where the player can "near-dodge" projectiles. What I mean by this is if the projectile just barely misses the player. I calculated this using collision_circle:
GML:
if collision_circle(x - 1,y - 1,13,obj_bulletparent,true,true) && distance_to_object(obj_bulletparent) != 0 && !recover{
    if inst == noone || (instance_nearest(x,y,obj_bulletparent) != inst){
    near_miss = true;  
    inst = collision_circle(x - 1,y - 1,13,obj_bulletparent,true,true)
    }
}
The above code checks to see if there is a collision of the bullet with a collision_circle around the player object. It also checks to make sure the projectile hasn't hit the player (isn't at 0 distance) and it checks to make sure the player isn't in "recovery state" from having been hit by a bullet previously.

If a collision is found, it sets that specific bullet to the inst variable and enables near_miss, which gives the player +1 mp for near-dodging.

This works almost completely, if the player actually manages to dodge the bullet. HOWEVER, there's an issue involved where the moment the bullet crosses that collision_circle, it's registering as a dodge even if the bullet still ends up hitting the player. For example, I dodge a bullet, and it gives me +1 mp, but the bullet still ends up hitting me, so I don't want to get that mp.

I guess the best way I could think to go about this is to save the bullet's id the moment it crosses over the collision circle and ONLY add mp if the bullet manages to exit the circle (doesn't hit the player). How would I go about that code wise? Are there any other methods you could think of?
 
Last edited:

TheouAegis

Member
The way I would do it is to have the bullets keep track of the misses. Basically, create a variable in the bullet object Create event to set the true whenever the bullet is about to hit the player. Then every step you check if the bullet is either within a certain distance of the player or if it is projected to actually hit the player, then set that variable the truth. Every step, check if that variable is true and if it is, check if the distance to the player is now larger then your circle. If it is, the player managed to dodge the bullet. Now you have a choice, you can either increase the MP right then, or wait until the bullet goes off the screen and destroys itself before increasing the MP. personally I would wait until the bullet goes off screen because otherwise a player can always just move back and forth rapidly and rack up MP that way.
 
The way I would do it is to have the bullets keep track of the misses. Basically, create a variable in the bullet object Create event to set the true whenever the bullet is about to hit the player. Then every step you check if the bullet is either within a certain distance of the player or if it is projected to actually hit the player, then set that variable the truth. Every step, check if that variable is true and if it is, check if the distance to the player is now larger then your circle. If it is, the player managed to dodge the bullet. Now you have a choice, you can either increase the MP right then, or wait until the bullet goes off the screen and destroys itself before increasing the MP. personally I would wait until the bullet goes off screen because otherwise a player can always just move back and forth rapidly and rack up MP that way.
Success! I followed your instructions almost completely and everything is working just fine.

In the parent object for the bullets, I have a create, step, and collision event with the player.

Create:
GML:
near_player = false;
snd = noone;
Step:
GML:
with(obj_player_battle){
    if collision_circle(x-1,y-1,13,other,true,false) && !recover{ //if bullet is in the circle and player is not in recovery mode
        if (inst_prev == noone || inst_prev != inst){ //basically checks if the current bullet has not already been dodged (so you can't spam it as you mentioned above)
        other.near_player = true;
        inst = other; //sets instance to the calling bullet object
        }
    }
}

if near_player{
    with(obj_player_battle){
        if !collision_circle(x-1,y-1,13,other,true,false){ //if bullet exits the circle
            inst_prev = inst; //swap the values over so that a bullet can't be dodged twice
            draw_go = true; //draw the visual cue
            other.near_player = false;
            audio_stop_sound(snd_near_miss);
            snd = audio_play_sound(snd_near_miss,5,false); //play audio cue
   
            if global.mp < 100{
                global.mp += 1; //add the mp
            }
            alarm_set(3,room_speed*.1); //set the alarm which disables the visual cue when it hits 0
        }
    }
}
Collision:
GML:
with(obj_player_battle){
    draw_go = false; //tell player object to stop drawing visual cue  
    other.near_player = false; //bullet no longer exists, therefore it can't be near the player
    audio_stop_sound(snd_near_miss); //stop any audio cues
}
And then for the player object, I have a draw event that draws the visual cue:
GML:
if draw_go && instance_exists(inst){
    draw_set_color(c_fuchsia);
    draw_set_alpha(.7);
    draw_circle(x-1,y - 1,13,true);
    draw_line(x,y,inst.x,inst.y);
    draw_set_alpha(1);
}
else{
    draw_set_color(c_yellow);
    draw_set_alpha(.7);
    draw_circle(x-1,y - 1,13,true);
    draw_set_alpha(1);
}
And the alarm event which disables the visual cue:
GML:
draw_go = false; //disable visual cue
inst_prev = inst; //swap values
inst = noone; //reset value
So basically I made it so a bullet can only be dodged once and not spammed as you mentioned. Everything is working perfectly!
 

Nocturne

Friendly Tyrant
Forum Staff
Admin
Minor but important optimisation tip:

GML:
if collision_circle(x-1,y-1,13,other,true,false) && !recover
This line should be written as:

GML:
if !recover && collision_circle(x-1,y-1,13,other,true,false)
GameMaker uses short-circuit evaluation, so you should always put your least expensive checks first, especially if there is an expensive function like a collision check in the mix. This way, if the recover variable is true, the collision check will never be performed. Normally a micro-optimisation like this isn't too important, but in a bullet hell game where there could be hundreds of instances, this is suddenly a LOT more important.

;)
 
Minor but important optimisation tip:

GML:
if collision_circle(x-1,y-1,13,other,true,false) && !recover
This line should be written as:

GML:
if !recover && collision_circle(x-1,y-1,13,other,true,false)
GameMaker uses short-circuit evaluation, so you should always put your least expensive checks first, especially if there is an expensive function like a collision check in the mix. This way, if the recover variable is true, the collision check will never be performed. Normally a micro-optimisation like this isn't too important, but in a bullet hell game where there could be hundreds of instances, this is suddenly a LOT more important.

;)
I never knew this. Good to know!
 
Top