• Hey Guest! Ever feel like entering a Game Jam, but the time limit is always too much pressure? We get it... You lead a hectic life and dedicating 3 whole days to make a game just doesn't work for you! So, why not enter the GMC SLOW JAM? Take your time! Kick back and make your game over 4 months! Interested? Then just click here!

SOLVED Need some Help with my DOOM engine (3D Collisions)

So, I've been working on this DOOM engine. Here's a video of what I've got so far:

The idea is simple, you place tiles in the room editor, then the "obj_world" will check for those tiles and place instances on the corresponding X and Y coordinates, and the tile's layer is checked as a "Z" coordinate. All this works inside 3 "for loops".
img.png

I'm using the p3dc extension for 3D collision checking. Adding a "block collision mask" for each "block tile" was too memory expensive, since it would check for collisions 6 times per tile, one for each face of the blocks. I fixed that by adding a "floor collision mask" instead, because it isn't noticeable during gameplay, and the game runs smoother by checking collisions with only 1 face per tile. I just had to make the player's "collision block" taller, so the camera wouldn't clip trough the "ceilling" when he is jumping.
img.png

I'm now having trouble with the collisions between the projectiles and the walls / blocks. I can't use "p3dc_ray" functions, since they would "pass through" the gaps between the "floor masks", as shown in the image below:
img.png

Projectiles that move slowly, like the ones from the BFG or the Plasma Rifle are fine, because I can just manually check if their Z coordinate is higher or lower than the block's Z "range", inside a collision event between the two:
img.png

But faster projectiles, like the ones from the pistol, move at about 100 pixels per frame. Sometimes their masks doesn't even overlap.
I tried using colision_line() to get the block's id and then check for the tile layer to get it's Z coordinate. The problem with that, is that collision_line() only returns one of the blocks's id. So, if i have a pile of blocks stacked, the projectile would check for collision with only one of them.

So, since I can't use a 2D vector, nor a 3D vector, nor a direct overlap between the masks to check for collisions, what should I do? Is there a solution for this?
 
Last edited:
You can use collision_line, but you have applied it incorrectly.

In the instance checking for collision, use a WITH loop on the object to be checked. It would be the same loop through instances that collision_line performs, but allows individual recognition among those instances.

with (whatever)
{
if collision_line(point1_x, point1_y, point2_x, point2_y, id, true) // the instance is checking it's own id for collision with the line
{
//////////////////// is in collision with line

/////////////////////// if calling object handles result
with (other)
{
// do whatever - add data to value, or store the id of instances
}

//////////////////// or you can refer to "self" within the loop: the id of the current instance that has checked the line for collision
collision = true; // this would set it's own variable, or you could do further calculations etc.

//////////////// the point is: within this loop you can affect either the calling object, or those being called, and still be doing exactly what a singular use of collision_line does behind the scenes. But done this way it gives more control.
}
}
 
You can use collision_line, but you have applied it incorrectly.

In the instance checking for collision, use a WITH loop on the object to be checked. It would be the same loop through instances that collision_line performs, but allows individual recognition among those instances.

with (whatever)
{
if collision_line(point1_x, point1_y, point2_x, point2_y, id, true) // the instance is checking it's own id for collision with the line
{
//////////////////// is in collision with line

/////////////////////// if calling object handles result
with (other)
{
// do whatever - add data to value, or store the id of instances
}

//////////////////// or you can refer to "self" within the loop: the id of the current instance that has checked the line for collision
collision = true; // this would set it's own variable, or you could do further calculations etc.

//////////////// the point is: within this loop you can affect either the calling object, or those being called, and still be doing exactly what a singular use of collision_line does behind the scenes. But done this way it gives more control.
}
}
Hmm, I don't remember if I've already tried something like that, but I'll give it a go. I should mention that I also use a collision line for checking collisions with the monster instances:

GML:
target = collision_line_first(xstart, ystart, x, y, obj_monster, false, false)
if target != noone
{
    if z > target.z - 10
    && z + 16 < target.z + 32
    {
        target.hp -= 1;
        if target.hurt == false
        {    
            target.agro = true;
            DIR = point_direction(target.x, target.y, obj_player.x, obj_player.y);
            target.DIR = round(DIR / 45) * 45;
            target.direction = round(DIR / 45) * 45;
            target.hurt = true;
            target.alarm[0] = 15;
            instance_destroy();
        }
    }
}
Since the projectile is so fast, I need to check for a collision with the monsters between the bullet's starting point and it's current position. I guess there is a possibility that the bullet object clips through the walls BEFORE registering a collision with them, and so, they COULD check for a collision with the monsters before being destroyed :confused:
I'll try to use your method, and then put the monster collision script as an else statement. I'll report later if it works.
 
Last edited:

Tyg

Member
Well i made my 3D world Z_UP so the player moves along the X/Y axis (except when jet packing)
What this will allow you to do is use the stuff like point_in_circle, i use this for zones if i know its in the zone then i simply check the Z
hope that helps :)

Shot2.jpg
 
Last edited:
the_dude_abides was right, I implemented he's code and now it's working flawlessly! Thanks a bunch!

GML:
with obj_block
{
    if collision_line(other.xstart, other.ystart, other.x, other.y, id, false, false)
    {
        if other.z < z
        && other.z > z - 16
            other.destroy = true;
    }
}

if destroy
     instance_destroy();

Here's the result:
 
  • Like
Reactions: Tyg
Happy to help :)

Though it might be jumping the gun to assume this is sorted :)

You might want to try this with two objects lined up in your shot, as whilst this approach can give individual results, it doesn't take into account distance, and whether an instance not yet checked might be in-between.

I believe the instance order is followed by the loop, so say you had 2 instances and the first is placed furthest away, whilst the second is placed closer and in your line of fire. The first will be checked, and found to be positive as it does have a collision. Exit the loop, or set conditions without any further checks, and you would be ignoring whether there is a closer collision - which, in this scenario, there would be.


GML:
dist = 1000000 // fairly large value, so that can start setting it to the distance of instances, and we know they will most likely be smaller than one million
collision_id = noone;

with obj_block
{
if collision_line(other.xstart, other.ystart, other.x, other.y, id, false, false)
{ 
new_dist = point_distance(other.x, other.y, x, y); // or point_distance_3d: I'm not sure which you'd need
if new_dist < other.dist
{
if other.z < z && other.z > z - 16
{
other.dist = new_dist;
other.collision_id = id;  
}     
}
}

if collision_id != noone
     instance_destroy();
Something like that adds back in the distance comparison, and should still be meeting your other condition too. If its closer than the current value of 'dist' AND it then meets your other check, it will set 'dist' to be it's distance. Essentially this is what collision_line_first is doing. I don't know if that's a function these days in GMS 2, but in 1.4 it's just a script resource for looping through instances, and then storing the end result in a ds list / priority queue etc for ordering, or storing the results.

The above code is exactly the same, without needing a data structure or anything else, and can include whatever functionality you care to add in. I suggest you do check out the collisions, as it's easy to imagine scenarios where you think my original suggestion works, but it's actually an obscured instance that's responded.

In the example I used earlier: were you to stick a decal on the "hit" instance, it wouldn't be the instance in front of you but the one behind it. Such a visual cue would make the issue apparent.
 
As far as I've tested, your previous code has been working accurately enough, but I see what you mean. You're right, in the version of GMS I'm using there's no "collision_line_first" script built in, I'm using an external one I've found online, here's what it's doing:

GML:
/// 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;
}

I'll put an Imp right next to a wall to test if the projectile still damages it, if not I'll adapt my code. But for now, I'll still leave this thread as resolved, as I haven't found any problems so far.
Thank you for the help šŸ˜

EDIT: As far as collision between the monsters, It's working correctly. Only the first Imp, closest in the collision check is being hit.
 
Last edited:
This is a bit moot if you're happy with things, but it will be the last thing I add to this topic :)

Looking through that script, it seems fairly expensive. Every time collision_line is called using object, it is doing a loop through that object behind the scenes. This script then does further calls of collision_line at shorter distance intervals. This is seemingly done to find the closest instance, rather than just comparing the distance of any collided instance.

Unless there is something about this collision_line_first script that I am missing, it would be my guess that it is noticeably slower than using WITH.

Mainly because WITH only takes one loop through to make any comparisons, whereas the same process will be done multiple times throughout the script you're using (each time collision_line is called for an object, that's exactly the same loop as one use of WITH)

Although, if you're using GMS 2, then these "optimisations" might not make much difference. But for a GMS 1 user: WITH is one of the most useful things you can implement, and I would expect you to see a pretty big boost in performance if you can avoid using that script. Even if you ignore that some behind the scenes functions of GMS 1 have better performance than others, there's still the fact that it's repeating a process multiple times, when on the face of it, it appears entirely unnecessary.
 
I didn't actually know the function WITH worked that way. I assumed it was used to reference all instances of an object, but not loop through them. That's good to know. This project is something I'm doing for fun, but also to learn more about 3D in game maker, so those optmisations are indeed something I'm interested in learning. Just because it's working doesn't mean it's working properly, so I'll take that in consideration. I'll optimise my code, I guess those adjustments aren't that hard to implement.
 
Last edited:
It is a loop :)

For whatever reason it has significant advantages under-the-hood as a computing process, though I'll admit here that I'm going on more experienced users results, and seeing the difference for myself in action. I can't explain why it works so much better, although there is the obvious benefit of having to use it less, on top of it being faster in general.

Other enhancements to 3d (at least as far as I have witnessed) would be:

1) Freeze vertex buffers if you're using them. Objects can still be rotated and transformed as a fixed shape using the matrix functions, so unless you want to animate models by moving the vertexes, this will hugely boost performance

2) If an object has no texture, but is coloured (say a blender export, with a coloured material applied to it), then removing unnecessary functions from the vertex format also boost performance.

As an example: I am using .obj models exported from blender, and they have no texturing - only colouring applied to faces of the object. It looks like how Nintendo do their graphics, with clean colouring, and no detail (presumably things like surface "sheen" and how it responds to lighting are shaders layered on top)

Because there is no texturing it only requires two definitions:
define 3d point
define colour

Whereas a fully textured object requires:
define 3d point
define colour
define normals
define texture co-ordinates

Before I removed those last two, and without freezing static vertex buffers, my project was about 5 times slower.....

Freezing the buffers was by far the biggest difference in performance, but those last two also add up when objects don't need them. Whether you do need them would depend on your graphics style, as they are irrelevant if you don't use texturing. Although, having said that, I'm unsure if further graphical effects would require that data - such as shaders for specular highlights (?)

That's pretty much the limit of my understanding :) Good luck with your project!
 
Top