Legacy GM [SOLVED] How do I check what to collide with first? (collision_line)

Dr_Nomz

Member
I have a few moments where an object is supposed to collide with something based on it's positions from where it is now to where it will be in the next step, and then run the correct code. The first moment the bullet is created it can't collide with anything at first, so the first moment it's created it acts a little differently, relying on previousx/y and current x/y, that's why there are 4 collisions here. (2 each for Walls/Enemies)


Thing is I can't seem to make it work, because sometimes bullets will hit through walls and if you're too close to a wall, the bullet will be destroyed before it hits the enemy. How do I make it so the bullet collides with what it's supposed to collide with? (Whichever object is closer, it'll react to that one instead of the other.)
Code:
//Variables for predicting collision in the bullet's next position.
var x2 = x + lengthdir_x(speed, direction);
var y2 = y + lengthdir_y(speed, direction);

//Wall Collision.
if collision_line(xprevious,yprevious,x,y,obj_Wall,false,false) && wall_collision = 0{
  //For colliding at the start of the bullet's creation.
  instance_destroy();
}
if collision_line(x,y,x2,y2,obj_Wall,false,false) && wall_collision < 1{
  //For predicting it's next point and colliding before it draws it's position there.
  instance_destroy();
}


//Enemy collision.
if collision_line(xprevious,yprevious,x,y,par_Enemy,false,false) && wall_collision = 0{
  with(collision_line(xprevious,yprevious,x,y,par_Enemy,false,false)){
    scr_Enemy_Damage(argument0);
  }
  instance_destroy();
}
if collision_line(x,y,x2,y2,par_Enemy,false,false) && wall_collision < 1{
  with(collision_line(x,y,x2,y2,par_Enemy,false,false)){
    scr_Enemy_Damage(argument0);
  }
  instance_destroy();
}
 

Dr_Nomz

Member
Wow an entire site dedicated to GML scripts? Thanks! Yeah I forgot to put the GMS 1 tag on this lol, thanks for that, I'll take a look at it later.
 

Dr_Nomz

Member
Finally got to it, couldn't get it working.

Code:
//Close Collision:
if wall_collision == 0{

var collide_wall_close = collision_line(xprevious,yprevious,x,y,obj_Wall,false,false);
var collide_enemy_close = collision_line(xprevious,yprevious,x,y,par_Enemy,false,false);

//Wall
if instance_exists(obj_Wall){
if scr_Collision_Line_First(xprevious,yprevious,x,y,obj_Wall,false,false){
if collide_wall_close{
  instance_destroy();
  exit;
}}}

//Enemy
if instance_exists(par_Enemy){
if scr_Collision_Line_First(xprevious,yprevious,x,y,par_Enemy,false,false){
if collide_enemy_close{
  with(collide_enemy_close){
    scr_Enemy_Damage(argument0);
  }
  instance_destroy();
  exit;
}}}

}
So, what am I doing wrong and how do I get it working?

This is a LOT better than what I had (I think) but it's basically the same as the old one. It collides with walls like it's supposed to, destroying the bullet when fired at a wall at point blank, but if there's an enemy it'll destroy the bullet without damaging the enemy.
 
Last edited:
C

Catastrophe

Guest
Basically, there are three cases here:

collide with wall
collide with enemy
collide with wall and enemy.

And you only have if statements regarding the firsst two cases. for the third (and it should go above the others):

First, store the results of collision_line_first for each into a variable, this will give you the instance_id to get position values from. Then, check which one is closer, by doing a point_distance against each of those. If the wall is closest, destroy yourself, otherwise do damage.

Alternatively, you can parent the wall and enemy and do a collision_line_first on that parent (like "o_physical_object") and then check the object_index of the collided object and act appropriately. If you do it this way, you should get rid of your other two checks. But that is a terrible waste of parenting unless they both had no parent to begin with and you think that is a good parenting structure, which it might be *shrug*
 
Last edited by a moderator:

Dr_Nomz

Member
I'm sorry I can't figure out how to do the point distance check for this. Can you show me how it's supposed to be done?
 
C

Catastrophe

Guest
sure

enemy = scr_Collision_Line_First(xprevious,yprevious,x,y,par_Enemy,false,false)
wall = scr_Collision_Line_First(xprevious,yprevious,x,y,obj_Wall,false,false)


if (enemy && wall) {

distToEnemy = point_distance(x,y,enemy.x,enemy.y)
distToWall = point_distance(x,y,wall.x,wall.y)

// do code based on whichever was closer
} else if (wall) {
// do wall code
} else if (enemy) {
do enemy code
}

distance_to_object might actually be more accurate since it accounts for bounding boxes

Edit; fixed typo where the functions was checking enemies both times
 
Last edited by a moderator:

Dr_Nomz

Member
Thanks for the help, it seems to work fine now.

But can you show me what you meant by using distance_to_object? I tried using it but I couldn't get it to work right with that.
 
C

Catastrophe

Guest
Well, the code is very simple, and you should get used to reading the manual. Try and reason this out a bit more. You just change

distToEnemy = point_distance(x,y,enemy.x,enemy.y)
distToWall = point_distance(x,y,wall.x,wall.y)

to

distToEnemy = distance_to_object(???)
distToWall = distance_to_object(???)

"This function calculates the distance from the edge of the bounding box of the calling instance to the nearest edge of the nearest instance of the object specified. The object can be an object index or a specific instance id as well as the keyword other, and the distance is returned in pixels. Note that if either of the objects have no sprite or no mask defined, the results will be incorrect."
 

Dr_Nomz

Member
Yeah I figured that was how to use it, but it didn't work. Seems like removing the check for x_previous entirely stops it from working. Is there some way to combine the two? Or would that not work at all?
 

Dr_Nomz

Member
Here's what I've got so far:
Code:
///scr_Bullet_Collision(bullet_type)
//This script is for the bullet's draw event, so it predicts where
//it's going to be and reacts accordingly.
//argument0 = Object name.

var x2 = x + lengthdir_x(speed, direction);
var y2 = y + lengthdir_y(speed, direction);
//Close Collision:
if wall_collision == 0{

var enemy = scr_Collision_Line_First(xprevious,yprevious,x,y,par_Enemy,true,false)
var wall = scr_Collision_Line_First(xprevious,yprevious,x,y,obj_Wall,true,false)

//Enemy AND Wall
if (enemy && wall) {
  var distToEnemy = point_distance(xprevious,yprevious,enemy.x,enemy.y)
  var distToWall = point_distance(xprevious,yprevious,wall.x,wall.y)
 
  if distToEnemy < distToWall{
    with(enemy){
      scr_Enemy_Damage(argument0);
    }
    instance_create(enemy.x,enemy.y,obj_Hit_Detector_2);
    instance_destroy();
    exit;
  }else
  if distToWall < distToEnemy{
    instance_create(wall.x,wall.y,obj_Hit_Detector_1);
    instance_destroy();
    exit;
  }
}else

//Wall
if (wall){
  instance_create(wall.x,wall.y,obj_Hit_Detector_1);
  instance_destroy();
  exit;
}else

//Enemy
if (enemy){
  with(enemy){
    scr_Enemy_Damage(argument0);
  }
  instance_create(enemy.x,enemy.y,obj_Hit_Detector_2);
  instance_destroy();
  exit;
}
//distance_to_object might actually be more accurate since it accounts for bounding boxes
}


//Far Collision:
if wall_collision < 1{
var collide_wall_far = scr_Collision_Line_First(x,y,x2,y2,obj_Wall,true,false);
var collide_enemy_far = scr_Collision_Line_First(x,y,x2,y2,par_Enemy,true,false);

if (collide_enemy_far && collide_wall_far) {
  var distToEnemyFar = point_distance(x,y,collide_enemy_far.x,collide_enemy_far.y)
  var distToWallFar = point_distance(x,y,collide_wall_far.x,collide_wall_far.y)
 
  if distToEnemyFar < distToWallFar{
    with(collide_enemy_far){
      scr_Enemy_Damage(argument0);
    }
    instance_create(collide_enemy_far.x,collide_enemy_far.y,obj_Hit_Detector_2);
    instance_destroy();
    exit;
  }else
  if distToWallFar < distToEnemyFar{
    instance_create(collide_wall_far.x,collide_wall_far.y,obj_Hit_Detector_1);
    instance_destroy();
    exit;
  }
}else

if collide_wall_far{
  instance_create(collide_wall_far.x,collide_wall_far.y,obj_Hit_Detector_1);
  instance_destroy();
  exit;
}else
if collide_enemy_far{
  with(collide_enemy_far){
    scr_Enemy_Damage(argument0);
  }
  instance_create(collide_enemy_far.x,collide_enemy_far.y,obj_Hit_Detector_2);
  instance_destroy();
  exit;
}
}
It works pretty well MOST of the time, but results are extremely inconsistent. Usually a bullet won't hit an enemy through a wall, but sometimes it will anyway. And for some reason if a sprite is larger than 50 px, or even if the object is just stretched farther than 50 px, then all the bullets will hit zombies through the wall in those areas, but not so much in the "origin" or whatever.

Anyway, just to explain what's going on, the variable "wall_collision" determines if the bullet has just been fired (so you can hit enemies that are right up against you) or is traveling away from the player. (so you can hit things that are far away) In the first one I use the Current X/Y (80 pixels from the player, since the bullet's speed is 80) plus the Previous X/Y to hit enemies that are close to the player, so it'll first check the previous (or starting point) x/y of the bullet and go from there.

The latter uses the current X as the starting point, with the NEXT X/Y as the ending point. (based on it's speed and direction) Both the first and second parts have about 4 collision checks, one for both Enemy and Wall collision (which has 2 checks based on which thing is closer, and whichever one it is is the one that activates) one for Walls, and one for Enemies.

So what am I doing wrong here? How do I get consistent results, and why is acting this way? I'll also post a video showing off what it's doing in a sec here.

Make sure to mute the video, I left in the sound and it's really loud and annoying.
 
Last edited:
C

Catastrophe

Guest
Ah, forget the distance_to_object anyways, looking at the video I don't think it'd be useful.

Well first of all I'd comment out the entire second section, let's focus on getting one piece of code working. For instance, I have no idea if that code is causing issues at this point or not.

point_distance may give unexpected values if the origins of the sprites are in odd places. E.g. if the origin of the walls is 0,0 and not in the sprite center, none of this code will work, and you'll need to do a bit more calculation. Probably something as simple as

wallOffsetX = wall.sprite_width/2
wallOffsetY = wall.sprite_height/2
var distToWall = point_distance(xprevious,yprevious,wall.x + wallOffsetX,wall.y + wallOffsetY)
 
Last edited by a moderator:

Dr_Nomz

Member
Hey that seems much better. :D

But it only works with 50x50 sprites, why does it only take the center origin into account? Isn't there a way to account for pixels? Because windows and doors, for example, are 100 pixels wider (or taller) than an average wall, so only one part of it will be taken into account with this check. Is there ANY way to better calculate this kind of collision?
 
C

Catastrophe

Guest
Well, this should account for pixels (it uses sprite_width and sprite_height not 50 and 50), have you tried it? Ultimately if you're using point_distance, you can only get so accurate, though, so some shapes might get glitchy.

If you want 100% accuracy no matter what, there's four ways forward:

1) copy the logic in collision_line_first so you never actually collide with both a wall and enemy and it picks the first of the two. There's only so much I'm going to help with a programming topic, though, so I'll just give a hint here that you can look at the code lady glitch posted for collision_line_first and this is doable. This is a fair amount of work, though.

2) Do what I said earlier, and parent everything you want to collide with a bullet to an object, and then do collision_line_first on that parent. Then you can eliminate the "wall and enemy" case, and just check what object_index the returned instance is and act appropriately.

3) split up walls/objects into multiple smaller invisible ones so your point_distance checks are more accurate.

4) just slow down your bullet so you don't collide with both.
 

Dr_Nomz

Member
I guess I'm going with the first option then, thanks for all the help, hopefully I can figure this one out from here.
 

Dr_Nomz

Member
I tried both things, mostly through trying to get an if check to determine if it hit an enemy or a wall, basically both were like this:

Code:
var collide_far = scr_Collision_Line_First(x,y,x2,y2,par_Bullet_Collision,true,false);

if collide_far == obj_Wall{
  instance_create(collide_far.x,collide_far.y,obj_Hit_Detector_1);
  instance_destroy();
  exit;
}else
if collide_far == par_Enemy{
  with(collide_far){
    scr_Enemy_Damage(argument0);
  }
  instance_create(collide_far.x,collide_far.y,obj_Hit_Detector_2);
  instance_destroy();
  exit;
}
Problem is it just goes through everything, as if the check is never true. I even tried just using "all" in place of the parent object and it didn't work. Any idea why?
 
C

Catastrophe

Guest
Ah, so what I meant here, is that you would take this code, and alter it so it takes some object types instead of a single object type, and gives the correct output:


Code:
/// collision_line_first(x1,y1,x2,y2,object,prec,notme)
//
//  Returns the instance id of an object colliding with a given line and
//  closest to the first point, or noone if no instance found.
//  The solution is found in log2(range) collision checks.
//
//      x1,y2       first point on collision line, real
//      x2,y2       second point on collision line, real
//      object      which objects to look for (or all), real
//      prec        if true, use precise collision checking, bool
//      notme       if true, ignore the calling instance, bool
//
/// GMLscripts.com/license
{
    var ox,oy,dx,dy,object,prec,notme,sx,sy,inst,i;
    ox = argument0;
    oy = argument1;
    dx = argument2;
    dy = argument3;
    object = argument4;
    prec = argument5;
    notme = argument6;
    sx = dx - ox;
    sy = dy - oy;
    inst = collision_line(ox,oy,dx,dy,object,prec,notme);
    if (inst != noone) {
        while ((abs(sx) >= 1) || (abs(sy) >= 1)) {
            sx /= 2;
            sy /= 2;
            i = collision_line(ox,oy,dx,dy,object,prec,notme);
            if (i) {
                dx -= sx;
                dy -= sy;
                inst = i;
            }else{
                dx += sx;
                dy += sy;
            }
        }
    }
    return inst;
}
So copy that script, and change it's specs from
collision_line_first(x1,y1,x2,y2,object,prec,notme)
to
collision_line_first_array(x1,y1,x2,y2,arrayOfObjects,prec,notme)
and you'd pass in [objWall,objEnemy] in place of arrayOfObjects

and then alter it so it works correctly. Every collision check in there will need to be a for loop of collision checks, and you'll need to read and understand the code.

^spoilered the non=parenting way of doing it just in case you want to get the general idea

Edit: ah, you're trying the parenting. I will take a look, one sec

Okay, so the issue here is, you'll want to understand the differences between instances, instance ids, objects and object_indexes, look up these in the manual

The short answer is, you need to do


collide_far.object_index == obj_Wall

And likewise with the enemy
 
Last edited by a moderator:

Dr_Nomz

Member
Okay I'm definitely going with the parenting thing then.

But are you sure that code you gave me is correct? It threw an error when I tried to use it:
Code:
___________________________________________
############################################################################################
FATAL ERROR in
action number 1
of Draw Event
for object obj_Bullet_Pistol:

Variable <unknown_object>.object_index(13, -2147483648) not set before reading it.
 at gml_Script_scr_Bullet_Collision (line 60) - if collide_far.object_index == obj_Wall{
############################################################################################
--------------------------------------------------------------------------------------------
stack frame is
gml_Script_scr_Bullet_Collision (line 60)
called from - gml_Object_obj_Bullet_Pistol_DrawEvent_1 (line 4) - scr_Bullet_Collision(obj_Bullet_Pistol);
 
C

Catastrophe

Guest
you'll need to wrap this:


if collide_far.object_index == obj_Wall{
instance_create(collide_far.x,collide_far.y,obj_Hit_Detector_1);
instance_destroy();
exit;
}else
if collide_far.object_index == par_Enemy{
with(collide_far){
scr_Enemy_Damage(argument0);
}
instance_create(collide_far.x,collide_far.y,obj_Hit_Detector_2);
instance_destroy();
exit;
}

in

if (instance_exists(collide_far)) {
that code
}

Because collision_line_first is going to return "noone" if there is no collision at all. And "noone" does not have an object_index

This is essentially what the "if (enemy) { stuff}" kinds of checks were in our previous code. Though actually the code I gave earlier would have been possibly buggy (though not in the missed collisions way), instance_exists is the correct way to do this
 
Last edited by a moderator:

Dr_Nomz

Member
Code:
if (instance_exists(collide_far)){

if collide_far.object_index == obj_Wall{
  instance_create(collide_far.x,collide_far.y,obj_Hit_Detector_1);
  instance_destroy();
  exit;
}else
if collide_far.object_index == par_Enemy{
  with(collide_far){
    scr_Enemy_Damage(argument0);
  }
  instance_create(collide_far.x,collide_far.y,obj_Hit_Detector_2);
  instance_destroy();
  exit;
}

}
Are you sure this is right? Because I did it exactly like you said but it's not colliding with anything.
 
C

Catastrophe

Guest
Did you make the parent of obj_Wall and par_enemy to be par_Bullet_Collision?

You should also learn to try and debug these issues. E.g. you should try


if (instance_exists(collide_far)){
show_message("I collided with something")
}

to make sure there is any collision at all. If there is not, you probably forgot to add them to the correct parent them or something.
 

Dr_Nomz

Member
show_debug_message is probably better, but yeah that was a good idea.

I figured out that collide_far IS in fact colliding with lots of things, but none of them are returning the check correctly.

if collide_far.object_index == obj_Wall

Are you sure that's correct? It won't return true and delete the bullet, so is there some other way to write it?
 
C

Catastrophe

Guest
Hmmm... ah, right, I forgot you probably have obj_wall and par_enemy as parents of other things too. You can figure out what it is colliding with by using:

show_debug_message(object_get_name(collide_far.object_index))

So what you need to do is check

collide_far.object_index == obj_Wall || object_is_ancestor(collide_far.object_index,obj_wall)

this is a common enough check that sometimes people make a function for it like

is(a,b) -> return argument0.object_index = argument1 || object_is_ancestor(argument0.object_index,argument1)
 
Last edited by a moderator:

Dr_Nomz

Member
Man, that is amazing. I don't think I'll make an actual function for that (I mean, if I end up needing more than these 4 checks maybe) but that right there actually made it work perfectly. There's no issues with collision at all. The only issue I can seem to find is if an enemy is stuck in a wall there's no way to hit them (like, if their ENTIRE sprite is in the wall) but otherwise the collision detection is perfect. Thank you Catastrophe, you're awesome. :D
 
Top