Legacy GM Better bullet code? I feel mine is flawed.

C

Cosmic Grain

Guest
Right now this is the code that handles all of the player's bullets in my game, and I really need a new one:

Code:
if (vspeed > -bspd) speed += bspd;

if (speed > bspd) speed = bspd;

var col;
var segments = speed div sprite_get_height(obj_pbullet.sprite_index);

for(var i = 0; i <= segments; i++)
{
    var dist = segments * i
    col = instance_place(x + lengthdir_x(dist, direction), y + lengthdir_y(dist, direction), obj_enemy)
 
    if col
    {
        instance_destroy();
        if audio_is_playing(snd_ehurt)
            {
            audio_stop_sound(snd_ehurt);
            audio_play_sound(snd_ehurt, 31, false);
            }
        else
            {
            audio_play_sound(snd_ehurt, 31, false);
            }
     
     
        //Determine how much damage the enemy takes based on the type of bullet that hits it
        switch (sprite_index)
            {
            case spr_pbullet1:
            case spr_pbullet4:
            case spr_pbullet5:
            case spr_pbullet6:
            col.ehp -= 5;
            break;
            case spr_pbullet2:
            col.ehp -= 8;
            break;
            case spr_pbullet3:
            col.ehp -= 4;
            break;
            }
     
        with col
            {
            image_blend = make_colour_hsv (130,80,255);
            alarm[0] = 3;
            }
        }
        break;
    }
The bullets travel at a speed of 35, which is why I ended up trying to find some "fancier" bullet code that works with faster bullets, and this code came into being due to help from someone else. But.. This one has quite a few flaws. For one, bullets can actually still miss if an enemy is small enough. Bullets will often be destroyed before visually hitting enemies. And I can't really make it look more convincing with impact graphics because spawning the impact effect at the bullet's position when it's destroyed looks like this...
Shooting at a test object with a test impact effect
(pardon the messy gif, used gyazo gif)

I tried methods using collision line a while back, but those didn't work as it seems collision_line only works with very small bullets.

So I'm wondering if I could get some help in developing a better code for my bullets, because up until now I've sort of just been using this one while I code other things, knowing I'll need to change it eventually. Also note that the switch statement for detecting bullet type as well as the impact sound code work fine. I just need a better way to handle the bullet's movement, due to its speed. Anyway, I appreciate any assistance!
 
Last edited by a moderator:
J

jackhigh24

Guest
change the speed when it gets close enough that what i do, that way you can keep taking one away from it until it hits, you get perfect collision every time no matter what speed.
 

obscene

Member
Your not actually moving your bullet within your code once it finds a collision. If it finds a collision it should move that location THEN destroy itself, right?
 
J

jackhigh24

Guest
that wont help with the ones that over shoot though obscene
 

obscene

Member
You are checking collisions every "segment" length based on the height of the sprite. But then when a collision is detected say 3 segments ahead... you are destroying the bullet without moving it those additional 3 segments.
 

BLang

Member
If all the bullets are going straight up, you can use collision_rectangle with the bullet's bbox_left and bbox_right variables to get a precise collision no matter how large the bullet is or how small the enemy is. Try doing what you were doing with collision_line before, but substitute it for collision_rectangle.
 
C

Cosmic Grain

Guest
Some of the shots are spread out, so some bullets go diagonally. Will collision_rectangle not work in this case?


Also, I'm starting to understand why this code isn't working now, thanks to Obscene's explanation. Just not sure exactly how to implement a solution yet.
 

obscene

Member
The same way you check a collision at x+lengthdir... etc.

Set x and y to x+lengthdir_x(dist,dir) etc. the moment before you destroy it.
 
Last edited:
C

Cosmic Grain

Guest
Ah, I feel bad saying this but.. I don't know how to properly do this.

I tried adding this within "if col" right before instance_destroy
Code:
x = x + lengthdir_x(dist,direction);
    y = y + lengthdir_y(dist,direction);
And the result is basically nothing different than before.

Forgive my noobishness, but I'm lost.
 

obscene

Member
You're doing it right... however realize the bullet is being destroyed before it's drawn again so you don't actually see the result. However if you spawn impact effects at that location like you mentioned before you'll see it in hopefully the right place.

If you want to get that effect at a very precise position you might want to (after you find there is a collision within the sprite's size) increment the position by a few pixels at a time until you reach a point_collision or use a script like range_finder() (Google it).
 
C

Cosmic Grain

Guest
The impact effects still spawn oddly though(exactly like before), even if I try something like this:
Code:
instance_create(x + lengthdir_x(dist,direction),y + lengthdir_y(dist,direction),obj_pbulletimpact);
 
Last edited by a moderator:

BLang

Member
Some of the shots are spread out, so some bullets go diagonally. Will collision_rectangle not work in this case?


Also, I'm starting to understand why this code isn't working now, thanks to Obscene's explanation. Just not sure exactly how to implement a solution yet.
Nope, collision_rectangle is defined by two points, so it can't check rectangles with a tilt to them.

You could use two point_in_triangle or rectangle_in_triangle checks to simulate checking with a tilted rectangle.
 
S

sparksinfinite

Guest
Performance alternative

I don't use bullets as objects but as particles currently.

The collision check happens only once before the particle is created.
The particle life is calculated by the bullet speed stat and the distance, this is also the same amount of time till the target receives damage.
Of course the direction, sprite, ... of the particle is also defined right before creating the bullet.

This is a very fast (performance) alternative but has it's limits and may only be appropriate for certain cases.
 
A

anomalous

Guest
Did you do what obscene wrote?
check if your object hit (not collided, see below) if it did then destroy it and spawn after-hit effects
check if your object collided, if so, then move it to the exact position it collides with target and set variable hit = 1
/
Why: on the step it collides, it gets moved to target
on draw event (after step), it draws in the correct position "hitting" the target
next step, its destroyed and on-hit effects dropped in, etc.


You can do something like this in your bullet object step:
if hit == 1
{
// spawn an explosion object , on-hit object and/or particles, etc.
instance_destroy();
}
// check collision below this, if collided, set "hit" to 1, and move x/y to the "hit" position which is determined using range finder (gml script), basically an iterative check to find the pixel it hits at.
You only run this on the step it collides to ensure it only has to run a few times)
 
B

bojack29

Guest
I ray cast my bullets. It allows for extremely fast or slow bullets with precise collisions.

Collision_line between the bullet position and where the bullet is expected to go. If a collision is detected, loop the bullets positiom closer to the collision that hits.
Otherwise move the bullet to the expected position. You can draw a tail on the bullet too as an added effect.
 
C

Cosmic Grain

Guest
Probably just terrible implementation on my part, but this didn't work for me, Anomalous..

Code:
if hit = 1
            {
            instance_create(x,y,obj_pbulletimpact);
            instance_destroy();
            hit = 0;
            }
  
    if col
    {
    x = range_finder(col.y,col.y,direction,col.bbox_bottom,col,true,false);
    y = range_finder(col.y,col.y,direction,col.bbox_bottom,col,true,false);
    hit = 1;
    if audio_is_playing(snd_ehurt)
            {
            audio_stop_sound(snd_ehurt);
            audio_play_sound(snd_ehurt, 31, false);
            }
            else
            {
            audio_play_sound(snd_ehurt, 31, false);
            }
            switch (sprite_index)
            {
            case spr_pbullet1:
            case spr_pbullet4:
            case spr_pbullet5:
            case spr_pbullet6:
            col.ehp -= 5;
            break;
            case spr_pbullet2:
            col.ehp -= 8;
            break;
            case spr_pbullet3:
            col.ehp -= 4;
            break;
            }
            hit = 0
            break;
    
        with col
            {
            image_blend = make_colour_hsv (130,80,255);
            alarm[0] = 3;
            }
    
        }

I'll test out the other suggestions, but in the meantime, could you explain the ray casting method Bojack? I know nothing about it. And you mentioned collision_line. Does ray casting use it? Does that mean it wouldn't work for very large bullet sprites like mine?

Also I know I may seem a bit hopeless right now, sorry! I'm pretty new to GM and certain aspects of coding still really confuse me. And sometimes I need things to be explained as if I'm a 5 year old.
 
Last edited by a moderator:
B

bojack29

Guest
Collision_line runs a line between two locations and checks for a collision against an object.

In your case you would look ahead using lengthdir functions.

So to find the x and y location of where the bullet is supposed to go you would look at the direction of the bullet.
Code:
var t, xx, yy;

//xx and yy are the expected location
xx = x + lengthdir_x(direction, length);
yy = y + lengthdir_y(direction, length);

//Obtain the id of any obj_wall between these two points
t = collision_line(x, y, xx, yy, obj_wall, false, true);

//Was there a wall here?
if (t > -4){
     //If so, loop one pixel in my current direction until I touch the wall
     do{
          x += lengthdir_x(1, direction);
          y += lengthdir_y(1, direction);
     }
     until(place_meeting(x, y, obj_wall));
}else{//No wall between two points
     x = xx;//Go to expected location
     y = yy;
}
 
C

Cosmic Grain

Guest
Hey, thanks for replying.

The code looks like this with my changes:
Code:
var t, xx, yy;

//xx and yy are the expected location
xx = x + lengthdir_x(sprite_get_width(self),direction);
yy = y + lengthdir_y(sprite_get_height(self),direction);

//Obtain the id of any obj_wall between these two points
t = collision_line(x, y, xx, yy, obj_enemy, false, true);

//Was there a wall here?
if (t > -4){
     //If so, loop one pixel in my current direction until I touch the wall
     do{
          x += lengthdir_x(1, direction);
          y += lengthdir_y(1, direction);
     }
     until(place_meeting(x, y, obj_enemy));
     if (place_meeting(x,y,obj_enemy))
        {
        instance_destroy();
        }
}else{//No wall between two points
     x = xx;//Go to expected location
     y = yy;
}

Assuming that's correct, it still isn't working right

See this:

A hit only registers if it's the middle of the bullet interacting, I'm guessing cause of collision_line?

Plus it doesn't appear to hit the enemy object in the right place.
 
B

bojack29

Guest
Pkay a few questions. Number one, how big is the mask of the sprite? It it the whole thing? Or just a small place?

Second, did you give the object a direction of 90? Dont give it any speed.

And third, did you place that code in the step event?

Also looking at your code, make the first part

xx = x + lengthdir_x(someLength, 90);
yy = y + lengthdir_y(sameLength, 90);
 
B

bojack29

Guest
Code:
var t, xx, yy;

//xx and yy are the expected location
xx = x + lengthdir_x(20, 90);
yy = y + lengthdir_y(20, 90);

//Obtain the id of any obj_wall between these two points
t = collision_line(x, y, xx, yy, obj_enemy, false, true);

//Was there a wall here?
if (t > -4){
     //If so, loop one pixel in my current direction until I touch the wall
     do{
          x += lengthdir_x(1, 90);
          y += lengthdir_y(1, 90);
     }
     until(place_meeting(x, y, obj_enemy));
     instance_destroy();
}else{//No wall between two points
     x = xx;//Go to expected location
     y = yy;
}
Try this code exact
 
C

Cosmic Grain

Guest
To answer your questions:

The bullet sprites' masks are just rectangles that hit the whole sprite

I removed the speed as you said. I see speed isn't necessary here since the code you provided moves the bullet anyway. And the direction is 90 now. I'm hoping it's safe to change the direction to other angles though, as some bullets will go diagonally.

And yes it's in the step event.


So using this new code, it's shooting fine but still colliding with the enemy the same as it was in that gif I just posted.
 
B

bojack29

Guest
Ia the sprite facing upward in the resource? If so is the origin x = centered and y = 0?
 
C

Cosmic Grain

Guest
Actually, I have it on its side.. ^^' And the origin is x = 0 and y = centered. Maybe that's the issue. I'll change that and see what happens.

EDIT: Even with the sprite like that, the problem with only the center being able to collide with enemies still persists. Plus it still doesn't visually appear to be destroyed at the right point.
 
Last edited by a moderator:
B

bojack29

Guest
Which direction is it facing in the sprite editor? If it is facing right, make the origin x all the way right and y centered
 
C

Cosmic Grain

Guest
I've done exactly that with it facing right but nothing changed. And the mask is the entire sprite.
 
B

bojack29

Guest
I know it might be a bit much to ask, but could you provide a gif of whats hapoening?

Assuming your code is as I posted it, sprite facing the right way and origin being correct.

I think I know the problem
 
B

bojack29

Guest
And whats the code like?

And the sprite resource angle and origin?

This is quite baffling because this stuff works for me all the time.

I have an asset on the marketplace that uses this method and it works perfectly.
 
C

Cosmic Grain

Guest
Okay the code in the bullet's step event is:
Code:
var t, xx, yy;

//xx and yy are the expected location
xx = x + lengthdir_x(35, 90);
yy = y + lengthdir_y(35, 90);

//Obtain the id of any obj_wall between these two points
t = collision_line(x, y, xx, yy, obj_enemy, false, true);

//Was there a wall here?
if (t > -4){
     //If so, loop one pixel in my current direction until I touch the wall
     do{
          x += lengthdir_x(1, 90);
          y += lengthdir_y(1, 90);
     }
     until(place_meeting(x, y, obj_enemy));
     instance_destroy();
}else{//No wall between two points
     x = xx;//Go to expected location
     y = yy;
}

The sprite, which is facing to the right in the sprite editor, has the dimensions w = 28 h = 62 and the origin x = 28 y = 31
 
C

Cosmic Grain

Guest
I guess I should also note that the bullet object is a parent of all bullet types, but the child objects don't overwrite any code or anything. And the bullets are created in a script in the end step event of the object when the player shoots, and to give you an idea here's a part of the script:

Code:
audio_play_sound(snd_pshoot, 30, false);
           

if global.atk = obj_pbullet1
    {
    if global.upgrade = 0
        {
        RoF = 8;
        b1 = instance_create(x,y-35,global.atk);
        b1.direction = 90;
        b1.image_angle = b1.direction;
        }
 

BLang

Member
The problem is that you're using collision_line, which doesn't use the sprite's mask. It only checks if there are any objects on a line between (x1,y1) and (x2,y2). So tweaking the sprite's mask won't do much.

I'm gonna recommend again that you do the method I suggested earlier, by combining two rectangle_in_triangle checks to simulate a collision with a rotated triangle.
 
C

Cosmic Grain

Guest
Well I took your initial advice of using collision_rectangle for bigger bullets that go straight up. And well, there's something I failed to mention before and I feel kinda silly now. All the diagonal bullets that the player shoots are actually pretty thin, so I've discovered that collision_line will work for those bullets

So right now I'm using an if statement that determines whether to use collision_rectangle or collison_line, based on the bullet's sprite index and the code looks like this:
Code:
var t, xx, yy;

//xx and yy are the expected location
xx = x + lengthdir_x(35, direction);
yy = y + lengthdir_y(35, direction);

//Obtain the id of any obj_wall between these two points
if sprite_index = spr_pbullet1 or spr_pbullet2 or spr_pbullet4 or spr_pbullet5
    {
    t = collision_rectangle(bbox_left, bbox_top, bbox_right, bbox_bottom, obj_enemy, false, true);
    }
else
    {
    t = collision_line(x,y,xx,yy,obj_enemy,false,true);
    }
  
//Was there a wall here?
if (t > -4){
     //If so, loop one pixel in my current direction until I touch the wall
     do{
          x += lengthdir_x(1, direction);
          y += lengthdir_y(1, direction);
     }
     until(place_meeting(x, y, obj_enemy));
     instance_destroy();
}else{//No wall between two points
     x = xx;//Go to expected location
     y = yy;
}


All bullets collide with the enemies now(though still don't look right visually as before) but for some reason the game freezes sometimes when I'm shooting spread out bullets at enemy objects. Here's a gif:

It doesn't seem to immediately freeze upon shooting any enemy as you can see. So I'm unsure what exactly is causing this. Any ideas?
 

obscene

Member
The only loop you have is here, so this is where the freezing is happening.

do{
x += lengthdir_x(1, direction);
y += lengthdir_y(1, direction);
}
until(place_meeting(x, y, obj_enemy));

So before you get to this point you have had either a collision line or a collison rectangle, but here you are checking by mask and so apparently this actually "misses" the enemy and continues on infinitely.
 
B

bojack29

Guest
You can remedy this by using a for loop instead of a do loop, breaking early if a target is found.
 
C

Cosmic Grain

Guest
Hey, I haven't tried Bojack's method with a for loop yet, but I did try with your method BLang. Thanks a ton for that btw.

Using the shooting from your example, I have pretty nice results, but also something I don't understand...

This shooting works perfectly on enemies with rectangle collision masks, no matter how small, but the game freezes when I shoot at enemies that have ellipse collision masks. And this seriously only happens when it's an ellipse. I spent around ten minutes shooting at enemies with rectangular masks of different sizes, and not once did it freeze. Why is this? I really want to understand this error.
 
Last edited by a moderator:

Yal

🐧 *penguin noises*
GMC Elder
How about moving the bullet, say, 5 pixels at a time 7 times and do a collision check after each? The bullet seems to be around 10 pixels tall and half its size is about the amount of precision you need to make impacts look convincing.

So, like, in the bullet's step event:
Code:
repeat(7){
  y -= 5
  with(parent_enemy){
    if(place_meeting(x,y,other)){
      event_perform(ev_collision,other.object_index)
    }
  }
}
 

Surgeon_

Symbian Curator
Here's how I make bullets:
  • I give them rectangular masks whose length is equal to the bullet's speed, that way they never tunnel through targets (I use the built-in collision event from there on).
  • Using a timer, a variable or a similar technique I make the bullets remove themselves one step after the collision, so it looks nicer (but with this you have to be careful so they don't end up dealing damage twice).
-Surgeon_
 
C

Cosmic Grain

Guest
Currently using Yal's method and even adjusted to work with diagonal bullets.

Collisions seem spot on, they never miss regardless of enemy size, now I'm just trying to figure out how to make it convincing when the side of a large bullet hits the enemy(current impact effect is a placeholder btw):
 
Last edited by a moderator:

Yal

🐧 *penguin noises*
GMC Elder
How about clamping the effect's X value between the left side and the right side of the enemy? If the enemy's sprite origin is in the center horizontal, and the enemy is responsible for creating the effect, you'd do like this:

Code:
instance_create(clamp(other.x,x-sprite_width*0.5,x+sprite_width*0.5),other.y,obj_impacteffectthingie)
Doesn't completely work when enemies are round and big, but it's a start.

Another idea would be to spawn the effect in the center of the enemy, then have it step out a few pixels in the direction of the [bullet that caused it]'s center until it doesn't collide with that particular enemy anymore (with a loop).
 
C

Cosmic Grain

Guest
Hey, the clamping works pretty close to perfect. I see what you mean about it not completely working on big round enemies. However I think I can figure out how to deal with that. I'll experiment with the other idea too but I'm pretty sure I'll stick with the clamp one.

Thanks so much for all the help! I'm glad to finally have this shooting thing figured out. This helps now, plus I can apply/build on it in future projects.
 
Top