SOLVED Collision of player with enemies and bullets (in code) – inside obj_player or those objects - pros and cons, optimization?

MartinK12

Member
Game with a lot of bullets and enemies, ie. platformer, bullet hell, etc. When player gets hit he has immunity for 2 sec. Where is better to put collision checks between player and enemies plus bullets? I do those checks in end step.

1. If put those checks inside enemies and bullets then each object need to perform collision check but code is very easy, light and clear. We have bunch of objects doing unnecessary collision checks but it’s enough for them to use just place_meeting (which is faster) although in this case we might even do pixel precise collision check (we only need to check speeds of one obj_player).

2. If inside player than we need to use instance_place_list to check all enemies and bullets we might collide at the same time so we have one heavy object player which constantly needs to create this list and then perform heavy calculations between those object (damage priority, immunity, send data back to each colliding object, etc.). obj_player is already heavy by plenty other things. Impossible to do pixel perfect collision check from inside player (too many objects with too many different speeds). But only one object player do collision check and with only a handful number of objects at the same time.

So which one is better? I did both but I’ve no experience so I don’t know which one will be better performance wise. Looks like maintenance is waaaaay easier inside enemies and bullets.

3. BTW performance wise – I’m curious is it worth to use place_meeting before instance_place when we need to get id of object we collide with? Example:
GML:
if place_meeting(x, y, obj_to_check) {
    var object_we_colide_with = instance_place(x, y, obj_to_check);
    //do some stuff to object_we_colide_with...
}
Thank you for all your thoughts and shared experience :)
 

MaxLos

Member
Optimization vs organization; I'm pretty sure its better to have one object (the player) check for collisions with any bullets rather than 1000 bullets checking for one player. I would said it usually doesn't matter too much, until you mentioned bullet hell that is... But like you mentioned, maintenance/organization is easier if you do the checks from the bullets.

As for your third question, I could have sworn if you tried to do something like that without the place_meeting check the game would crash, but I tried in a new project to be sure and it seems I was mistaken. It only crashes if there's no object at the instance_place your checking and your trying to pass something from that non-existent object to the original object doing the check. Trying to do something with the object like in your example is fine, even if you leave out the place_meeting check and there's not actually an object there.

But I have a question though: Why do you have to use instance_place_list for checking all the enemies and bullets the player might collide with at once? You said the player would be invincible for 2 seconds after getting hit.

And correct me if I'm wrong but it doesn't sound like your using parents for your bullets? If the bullets are fundamentally the same (have a set speed, damage, knockback, etc) then you really should be using them. You could define each of the different bullet type's properties in their Create Event, set their parent to par_bullet, and make one collision event with par_bullet in the player object, and do your calculations from there.
 

MartinK12

Member
@MaxLos Thank You for your reply

For my 3rd question I’m just curious if it’s good for optimization to limit instance_place (slower) checks by first checking place_meeting (faster).

As for instance_place_list – yes I use parent called obj_can_damage_player_parrent and all bullets, enemies, etc are children of this object. Then in player’s end_step I do only one check for collision with parent and switch the result, like this:

GML:
if place_meeting(x, y, obj_can_damage_player_parrent) {
    var object_we_colide_with = instance_place(x, y, obj_can_damage_player_parrent);

switch (object_we_colide_with.type) { //type: 1=bullet, 2=enemy, etc.
    case 1:
        //do stuff to bullet and player when we colide with bullet
        //show splash, particles, destroy bullet,
        //if (player immune_timer == 0) do damage to player
    break
    
    case 2:
        //do stuff to enemy and player when we collide with enemy
        //check direction of collision
        //switch collision with enemy type: player head stunned enemy, enemy head stunned player, etc
        //do stuff to player: knockback, damage, etc.
        //do stuff to enemy: knckback, damage, etc.
    break;
}
So it works fine until we are constantly in a collision with one of those objects, ie. enemy when enemy is stunned and we still collide with him. His id will be kept by player and no other bullet, enemy etc can collide with player. Player’s invincibility have nothing to do here because during collision we still need to destroy bullet when collide with player, even if player’s invincible. In this case I have to use instance_place_list if I want to keep all code inside player or I can deactivate bullets, enemies, etc to free collision check in player. In some cases I can’t deactivate enemy, ie. in boss fights player can collide with big body of boss.

So that’s my dilemma – code is much simpler and we can avoid those situations by doing collision checks inside each bullet, enemy, etc. but this means thousands of collision checks instead one in player. And I have no experience, and have to choose one solution to keep building further and don’t want to rebuild it when project will become much more complicated :(
 

MaxLos

Member
Well in that case instead of using instance_place or instance_place_list, it might be better to have a one parent for bullets and another for enemies. You can't really use instance_place if they both have the same parent and your colliding with both at the same time as you can only store one of their ids. and Instance_place_list would over complicate things imo.

Simply separating them into two collision events should fix the problem. Instead of using their ids, you can use the 'other' keyword
Code:
///Player Collision Event with par_bullet
with (other) //With the bullet
{
    //do stuff to bullet and player when we colide with bullet
    //show splash, particles, destroy bullet
}
if (player_immune_timer == 0) 
{
    health -= other.damage;
}
Code:
///Player Collision Event with par_enemy


//check direction of collision
//switch collision with enemy type: player head stunned enemy, enemy head stunned player, etc
//do stuff to player: knockback, damage, etc.
with (other) //With the enemy
{ 
    //do stuff to enemy: knckback, damage, etc.
}
 

TheouAegis

Member
You never need place_meeting() in Game Maker. instance_place() does the exact same thing and returns more relevant info. Using both halves the speed of your code. Same with instance_place_list(), just check the list size before trying to read through the list.

Make sure you give the enemies a parent object. You want collisions with one parent, not 1000. You could even make the bullets children of that same parent. Then the player would need just one collision check. Just check
if other.object_index==obj_bullet instance_destroy(other.id);
 

MartinK12

Member
You never need place_meeting() in Game Maker. i
Thank you for reply. Yes, I understand that it halves the speed of my code but only when collision is found. Most steps there are no collision and GMS manual says that place_meeting() is faster than instance_place() so my thinking is to use place_meeting() and only check for id of collided instance when we actually collided.

So my question A - is the speed of place_meeting() and instance_place() - is the same when there’s no collision detected?

Same with instance_place_list(), just check the list size before trying to read through the list.
Make sure you give the enemies a parent object.
Yes I do the exact thing.
I use instance_place_list in the same manner so I don’t even create list if there so no collision. My thinking is the same that when there’s no collision this is faster, right? Or I should create this list every step even when there’s no collision?

The question B – is below code the most optimal code for collision check inside player?

GML:
//player's End Step
if (place_meeting(x, y, obj_can_collide_with_player_parent)) { //one parent for all objects that can collide with player
    var _list = ds_list_create();
    var _num = instance_place_list(x, y, obj_can_collide_with_player_parent, _list, false);
   
    if (_num > 0) {
        for (var i = 0; i < _num; ++i;) {
            var object_we_collide_with = ds_list_find_value(_list, i);
           
            //each object has hit_type variable to make it faster to check what kind of object is this
           
            switch (object_we_collide_with.hit_type) { //hit_type: 1=bullet, 2=enemy, 3=explosion, 4=trap, etc.
                //cases for each object type here - to perform actions both for player and collided object
            }
        }
    }
   
    ds_list_destroy(_list);
}
 

Let's Clone

Member
If the player becomes invulnerable after a collision then I don't think you'd need to check for all potential collisions at any given frame. Couldn't you simply check for the first collision and let the invulnerability ignore any further collisions?
 

TheouAegis

Member
Even if there is a difference in speed between pkace_meeting() and instance_place(), the cost for running both in tandem negates any gain from running place_meeting() first.

I use instance_place_list in the same manner so I don’t even create list if there so no collision. My thinking is the same that when there’s no collision this is faster, right? Or I should create this list every step even when there’s no collision?
Make the list once and reuse the list.

If the player becomes invulnerable after a collision then I don't think you'd need to check for all potential collisions at any given frame. Couldn't you simply check for the first collision and let the invulnerability ignore any further collisions?
Or check if invulnerable and don't do any collision checks if so.
 

MartinK12

Member
Things I’ve learned from this topic so far:
1. It’s better to do one collision check inside player than other objects (bullet, enemies, etc).
2. Never use collision functions in tandem, ie. instance_place is enough.
3. My optimal code for collision check inside player using instance_place_list – list created once in player create event and then it’s just cleared.
GML:
//player create
_list = ds_list_create();

//player's End Step
var _num = instance_place_list(x, y, obj_can_collide_with_player_parent, _list, false); //one parent for all objects that can collide with player

if (_num > 0) {
    for (var i = 0; i < _num; ++i;) { //we loop for each collided object and perform actions to it and player
        var object_we_collide_with = ds_list_find_value(_list, i);
        //each object has hit_type variable to make it faster to check what kind of object is this
        
        switch (object_we_collide_with.hit_type) { //hit_type: 1=bullet, 2=enemy, 3=explosion, 4=trap, etc.
                //cases for each object type here - to perform actions both for player and collided object
        }
    }
ds_list_clear(_list);
}
4. Immunity has nothing to do with collision checks because we still need to perform actions to all colliding objects regardless if player is immune or not (destroy bullets, stun enemies, etc.). Plus we also need to perform other actions to player regardless if he’s immune or not - for example player was hit by enemy so he’s immune - but next steps there's huge explosion nearby so it looks stupid that this explosion has no impact on player, especially when it happens so fast that person can’t see if player was hit by enemy or explosion. So in this case even if player is immune we will still at least apply knockback to him.

Any other suggestions are still appreciated :)
 
Top