Do damage, but only once

M

Mikael.K

Guest
The script my weapon "hit box" execute when colliding with an enemy looks like this.

GML:
/// @param victim
/// @param xpush
/// @param ypush
/// @param damage

var collision_list = ds_list_create();
var number = instance_place_list(x, y, argument0, collision_list, false);

if number > 0
{
    for (var entry = 0; entry < number; entry += 1;)
    {
        if collision_list[| entry] != owner // owner is the instance that created the hit box. As not to damage self.
        {
            with(collision_list[| entry])
            {
                // do damage
                hp -= argument3;               
                
                // push object
                if can_be_pushed
                {
                    scr_push(argument1,argument2);
                }
            }
        }
    }
}

ds_list_destroy(collision_list);
The script makes a ds_list of all the possible "victims" that it collides with, and reduce the hp of those instances with the damage variable.
The problem I'm having is that the hp is reduced by damage for all the frames that the hit box is colliding with the victim. I want the hit to be just once per attack.

I want to avoid having a variable in the potential victims, like been_hit, and set that to true when colliding with an object that does damage, and reset to false when the object that damaged the instance is destroyed.
The problem with this is that the struck victim is invincible for as long as the object that damaged the instance exists.

Any ideas on how to add code to the script so that the hit box knows which instances has been damaged already?
 
You need another list keeping track of what's been hit but it can't be created in that script specifically (or rather, it's awkward if you try to do that). Instead, every time a new projectile/swipe/whatever is doing damage is created, give that instance a list:

Create Event for damage object
Code:
hit_list = ds_list_create();
Then in your script, which I assume is being called from within the same object that is doing the hitting, you add id's to the list when they've been hit and skip damaging them again if that id already exists in the list:
Code:
if number > 0

{

    for (var entry = 0; entry < number; entry += 1;)

    {
        var _col_inst = collision_list[| entry]; // Store the collision instance into a var to make it a bit less typing
       
        if _col_inst != owner // owner is the instance that created the hit box. As not to damage self.

        {
            if (ds_list_find_index(hit_list,_col_inst) < 0) // Search through the hit_list for the _col_inst id, if it is not found, -1 is returned
            {
               
                ds_list_add(hit_list,_col_inst); // Now add col_inst to hit_list, the next frame, it will find the id above when checking through the list and skip all of this code
               
                with(_col_inst)

                {

                    // do damage

                    hp -= argument3;              

               

                    // push object

                    if can_be_pushed

                    {

                        scr_push(argument1,argument2);

                    }

                }
               
            }

        }

    }

}
Just make sure to destroy hit_list when the damage instance is destroyed.
 
Last edited:

rytan451

Member
In the bullet, store a list of instances that have already been hit. Use a ds_map to hold which ids have been hit (put the ids in the keys, and set the values to 0). Only decrease hp if the id of the enemy being hit is not in the ds_map.

GML:
/// @param victim
/// @param xpush
/// @param ypush
/// @param damage
/// @param already_damaged

var collision_list = ds_list_create();
var number = instance_place_list(x, y, argument0, collision_list, false);
var inst; // CHANGE: Store instance id in temporary variable for performance increase and readability
if number > 0
{
    for (var i = 0; i < number; i++) /// CHANGE: remove trailing semicolon that can cause problems; change entry to i to keep with convention
    {
        inst = collision_list[| i];
        arguments4[? inst] = 0; // CHANGE: Save that the instance has been hit by the bullet
        if inst != owner && !ds_map_exists(argument4, inst) // owner is the instance that created the hit box. As not to damage self. // CHANGE: add check to avoid hitting the same instance multiple times
        {
            with(inst)
            {
                // do damage
                hp -= argument3;              
               
                // push object
                if can_be_pushed
                {
                    scr_push(argument1,argument2);
                }
            }
        }
    }
}

ds_list_destroy(collision_list);
// Remember to clear the map if you want to reuse the hitbox for another attack, and to destroy the map when the hitbox is destroyed
Why use this code instead of what RefresherTowel gave? What RefresherTowel gave was a similar concept, except instead of using a ds_map to store the ids, they used a ds_list. A ds_map is faster, because checking if a key exists in a ds_map takes constant time. It takes a constant short amount of time to check if the instance was damaged. However, using a ds_list is slower, because if the hitbox hit, say, a hundred instances over a period of a hundred frames, then the hitbox will need to check each of those hundred instances against that list (holding 100 instances) 99 times, for a total of >990,000 checks. In comparison, in the ds_map method, you're only checking the hundred instances against a map for a hundred frames, for a total of 10,000 (a speedup of 99x).

The speedup is even larger when there are more instances being damaged.
 
I mean, that is true, but which one is faster is entirely dependent on how many instances there are being hit before the damage instance is destroyed. With a smaller number of enemies, the ds_list will be faster, with a larger number, the map will be. You'd have to do some benchmarking to figure out where the ratio of enemies being hit to list checks becomes more performant when swapping to the ds_map method.
 
M

Mikael.K

Guest
RefresherTowels solution worked well.

Good to know that ds_maps are faster if involving many instances, thanks rytan451!
 
As @rytan451 has already said, ds_maps have a "static" time to retrieve data (this is because they're basically hash maps and there's a reason why that's true if you want to look it up). This "static" time is longer than the retrieval time of an entry in a list when the list is relatively small, so a list will perform better, but as the list size grows, the time needed to loop through and retrieve entries from the list also grows, whereas with a map it stays "static".

So depending on the sizes of the data structures, there's a tipping point where the "static" retrieval time of a map will become faster than the "increased" retrieval time of a large list. It doesn't matter what's stored in the list/map, simply how many entries there are. If you're experiencing slowdown and you have something that is accessing data structures a lot and the data structures can be large (for instance, a path generation algorithm could be one, depending on the resolution of the path cells), then it can often be a good idea to switch over to maps, but it's one of those things where benchmarking and testing really helps.
 

Nidoking

Member
Is there a need for the hitbox to continue doing damage after the first frame at all? Do you specifically need things that enter the hitbox during its animation to take damage? Why not run the script in the Create event of the hitbox rather than Step?
 
M

Mikael.K

Guest
If the damage script I'm using is used within a projectile that penetrates more than one target, or if i the weapon has more than one frame that does damage, then this method is useful.
 
Top