GMS 2 Destroy projectile at specific tile location

Carcophan

Member
Hello everyone.

I am new to GML and GMS2 but am in love with everything and the amazing community.


So, I am already able to successfully shoot a projectile object, from the players location/gun, towards the mouse click location/enemy. (In a top down 2D shooter type game).

However, I am looking for a way to destroy the bullet(s) object AT the location I clicked (the X/Y) instead of just flying off in a strait line off the screen forever.

Note that, this is NOT in the event of a collision with the enemy object, but at the defined mouse click location itself. Destroy Projectile_01 specifically when it gets to the mouse click location of X100 Y100, versus on collision with Enemy_object_01.

I have tried several things, and this latest example here 'should' work, but it isn't. The bullets just keep flying strait forever, on an enemy miss.

This is currently also in the 'End Step' of the Bullets object, as an FYI.

Code:
if place_meeting(x,y,obj_bullet)
{
    var bullet = instance_place(x, y, obj_bullet);
 
    with (bullet){
        instance_destroy();
    }
}
Anyone have any pointers or suggestions?

Thanks!
 
Last edited:
when creating the bullet do something like
Code:
a=instance_create(x,y,bullet);
a.goalx=mouse_x;
a.goaly=mouse_y;
as for destroying itself when it when it reaches the targeted location, chances are with most speeds the bullet wont land directly on the mouse so I would do something simple like in the create adding
Code:
a.check=point direction(a.x,a.y,mouse_x,mouse_y);
then in the step of the bullet by knowing the angle will change once it passes the goal you could do
Code:
if point_direction(a.x,a.y,goalx,goaly)!=check
instance_destroy();
 

Carcophan

Member
Thank you for your prompt reply @spoonsinbunnies .

I've tried to implement what you've posted, but am having some issues still. I am not finding anything on Instance_create, assuming it means instance_create_layer (maybe), but none of my testing is getting me further along. The only change I've been able to implement is to 'not shoot' period.


I understand finding the exact mouse coodinates with the bullet may be challenging, but would this logic also work on mouse_x and mouse_y, to indicate it has hit the 'tile' itself, versus the actual mouse x/y.
 

TheouAegis

Member
instance_create_layer() or instance_create_depth() both work.

First you say you want it to go to the mouse, then you want it to go to a tile. Which is it?

You could first find the ID of the tile under the mouse, get its coordinates, add half the tile's width to the x coordinate and half the tile's height to the y coordinate, which would put the coordinates in the dead center of the tile, then pass those coordinates to goalx and goaly in spoons' code.

His check should also thus be

a.check = point_direction(x,y,a.goalx,a.goaly);

And then his last block of code for checking when to destroy shouldn't have a in it at all. lol

if point_direction(x,y,goalx,goaly)!=check instance_destroy();
 

Carcophan

Member
Sorry for the confusion and thank you for the input.

"First you say you want it to go to the mouse, then you want it to go to a tile. Which is it? " - The bullet should go to the mouse_X/mouse_y location, at the time of the click, which is over a particular tile. So it is 'both' really.

I really like your suggestion here and will work on this next: " half the tile's width to the x coordinate and half the tile's height to the y coordinate"


A question I have then, is, if these are to go in the create and step sections of the bullet - how would the 'player' interact with these? I am currently using a player object with a mouse click:

Code:
//obj_Player
//fire projectile
if (mouse_check_button(mb_left)) && (cooldown < 1)
{
    cooldown = 10;

    a = instance_create_layer(x,y, "Bullet_Layer", obj_bullet01);

}
cooldown = cooldown - 1;
This was what was (previously) making the bullet just 'fire off in a single direction'.

So with your suggestions, I've moved the codes to the bullet object step and create events -- but how would I then trigger that same " a = instance_create_layer(x,y, "Bullet_Layer", obj_bullet01);" in two different objects?

Code:
//bullet create
a = instance_create_layer(x,y, "Bullet_Layer", obj_bullet01);
a.goalx = mouse_x;
a.goaly = mouse_y;
a.check = point_direction(x,y,a.goalx,a.goaly);
Code:
//bullet step
if point_direction(x,y, goalx, goaly)!=check instance_destroy();
 

TheouAegis

Member
Edit: wtf happened to my post?
First, the
Code:
//bullet create
a = instance_create_layer(x,y, "Bullet_Layer", obj_bullet01);
a.goalx = mouse_x;
a.goaly = mouse_y;
a.check = point_direction(x,y,a.goalx,a.goaly);
goes inside the player's code. That first line is just where you create the bullet in the player, then everything else follows it.

Code:
//obj_Player
//fire projectile
if (mouse_check_button(mb_left)) && (cooldown < 1)
{
   cooldown = 10;

   a = instance_create_layer(x,y, "Bullet_Layer", obj_bullet01);
   a.goalx = mouse_x;
   a.goaly = mouse_y;
   a.check = point_direction(x,y,a.goalx,a.goaly);
}
cooldown = cooldown - 1;
goes inside the player's code. That first line is just where you create the bullet in the player, then everything else follows it.

Now to apply the tile... I have no experience with tile data retrieval in GMS2, so this will be vague. lol We'll be replacing the "a.goalx=mouse_x; a.goaly=mouse_y;" lines, but first we need to find the tile.
Code:
//obj_Player
//fire projectile
if (mouse_check_button(mb_left)) && (cooldown < 1)
{
   cooldown = 10;

   var tilex = tilemap_get_cell_x_at_pixel( tile_layer_id_thingy, mouse_x, mouse_y) + 32,
        tiley = tilemap_get_cell_y_at_pixel( tile_layer_id_thingy, mouse_x, mouse_y) + 32;    //change the 32's to your tile width and height
   a = instance_create_layer(x,y, "Bullet_Layer", obj_bullet01);
   a.goalx = tilex;
   a.goaly = tiley;
   a.check = point_direction(x,y,a.goalx,a.goaly);
}
cooldown = cooldown - 1;
goes inside the player's code. That first line is just where you create the bullet in the player, then everything else follows it.

Something like that. Like I said, I don't have GMS2 and I had no real practice using tiles back when I did have it.
 
Last edited:

Carcophan

Member
This, as is, in the player Step object, fires a projectile all of the way off screen still.
Code:
//player object, step event, fire projectile upon mouse click and cooldown not active...
if (mouse_check_button(mb_left)) && (cooldown < 1)
{
    cooldown = 10;
var lay_id = layer_get_id("Bullet_Layer");
var map_id = layer_tilemap_get_id(lay_id);
  
var tilex = tilemap_get_cell_x_at_pixel(map_id, mouse_x, mouse_y) + 32;
var tiley = tilemap_get_cell_y_at_pixel(map_id, mouse_x, mouse_y) + 32;    //change the 32's to your tile width and height
//var tileIndexMouse = tilemap_get(map_id, tilex, tiley);
  
   var tilex = tilemap_get_cell_x_at_pixel( tileIndexMouse, mouse_x, mouse_y) + 32;
   var tiley = tilemap_get_cell_y_at_pixel( tileIndexMouse, mouse_x, mouse_y) + 32;    //change the 32's to your tile width and height
  
   a = instance_create_layer(x, y, "Bullet_Layer", obj_bullet01);
  // a = instance_create_layer(x, y, map_id, obj_bullet01);
   a.goalx = tilex;
   a.goaly = tiley;
 
  //a.check = point_direction(x, y, a.tilex, a.tiley);
  a.check = point_direction(x, y, a.goalx, a.goaly);
}
cooldown = cooldown - 1;
However, when I add this to the bullet step object, nothing fires.
Code:
//bullet object step event
if point_direction(x, y, goalx, goaly)!= check instance_destroy();
I've tried variations on this as well, and get errors or non-functioning code. I am not sure what I am doing wrong :(. I left in a few commented-out samples of other examples.

I am open to alternative approaches and re-writing logic to alternatives if needed.
 

TheouAegis

Member
I'm just glad you read the manual LOL

var lay_id = layer_get_id("Bullet_Layer");
var map_id = layer_tilemap_get_id(lay_id);
var tilex = tilemap_get_cell_x_at_pixel(map_id, mouse_x, mouse_y) + 32;
var tiley = tilemap_get_cell_y_at_pixel(map_id, mouse_x, mouse_y) + 32;
This part should be right, not the three lines after it, of which you only commented out 1 line that I can see.
//var tileIndexMouse = tilemap_get(map_id, tilex, tiley);
var tilex = tilemap_get_cell_x_at_pixel( tileIndexMouse, mouse_x, mouse_y) + 32;
var tiley = tilemap_get_cell_y_at_pixel( tileIndexMouse, mouse_x, mouse_y) + 32; //change the 32's to your tile width and height
^Not right, i'm pretty sure.

Debug the values of tilex and tiley; that is the easiest way to make sure that part of the code at least works right. Just put show_debug_message(string(tilex)+", "+string(tiley));
in that code when shooting. Make sure the debug console is giving coordinates that look like the right ones.

As for why it's not firing, I think it is firing but it's destroying itself immediately because after one step the direction technically changes due to fractional pixel movement. That's my guess. I didn't write that part of the code. LOL the method I use is to check the distance, not the direction. Your bullet uses the speed variable, doesn't it? If so, you can do

if point_distance(x,y,goalx,goaly) < speed instance_destroy();
 

Carcophan

Member
This is awesome, thank you Theou (and Spoon) - thanks for sticking it out with me! I was able to sort it out most of the way, I think I should be able to get it the rest of the way now.

The 'bullet Speed Variable' and it destroying immediately, were a key suggestion.


The bullet now destroys itself when it interacts with the mouse pointer location. Now I just need to simply change it to where the mouse 'was at when it was clicked' versus where the 'mouse is at right now' - and then I am all done :)

Thanks again! You folks rock.
 
Last edited:

TheouAegis

Member
where the mouse 'was at when it was clicked' versus where the 'mouse is at right now'
What? Both our codes deal with setting the bullet's direction to where the mouse is when you click. Make sure you aren't using mouse_check_button_released().
 

Yal

🐧 *penguin noises*
GMC Elder
Make sure you don't update the bullet's direction every step, or it'll move towards the mouse continuously instead of only once when it's created.
 

Carcophan

Member
Both our codes deal with setting the bullet's direction to where the mouse is when you click. .
The behavior I currently experience when I click/fire the bullet, which is totally fine for now and is an improvement to where I was a few days ago..... is:

I click the mouse at 200/200. The projectile correctly fires fine in that direction. If I leave the mouse at 200/200 - when the bullet collides - it disappears like I wanted it too. However... if I fire... then move the mouse FROM x/y 200/200 to anywhere else on screen, the projectile will pass through that 200/200 location without being destroyed because the mouse is no longer there.



My end goal, to over simplify it, is to make a little explosion kind of. Centered on the middle of a given tile, which is derived from the mouse click location.

So if I click anywhere on tile [4,2], which could be mouse coord of x 200/ y 200 (or any x / y range on that specific tile itself), I want the bullet to fire from the player, travel, and explode centered on [4,2]. Regardless of where the mouse gets moved to during the flight of the projectile. So I have to make it remember where the mouse was during the click, and travel there.


Make sure you don't update the bullet's direction every step

It is/was in the step event, good call, so I will change it to something else.
 

TheouAegis

Member
The point of goalx and goaly is to replace the mouse coordinates. You IGNORE the mouse once the bullet has been created. Nowhere in your bullet code should you even be referencing the mouse, only in the player's code.
 

Carcophan

Member
I ended up changing things around. I read up some more on scope and variables and 'with' and 'other'.


Either I got lucky, or this is a better solution overall. Wouldn't mind your opinion on it, in general. It works, just want to make sure there is not some huge issue I am overlooking with this type of logic.

Code:
//character object - step event
goalX = mouse_x;
goalY = mouse_y;
instance_create_layer(x, y, "Bullet_Layer", obj_bullet01);

//bullet object - step event
with(obj_player01){
    if (place_meeting(goalX, goalY, obj_bullet01)) {
    instance_destroy(obj_bullet01);
    }
}
 

Yal

🐧 *penguin noises*
GMC Elder
Would be better to use
Code:
with(instance_create_layer(x, y, "Bullet_Layer", obj_bullet01)){
  goalX = mouse_x;
  goalY = mouse_y;
}
to set the coordinate in the first place, and then adapt the step even accordingly... with(){} loops can get really expensive since you potentially need to iterate through EVERY OBJECT IN THE ROOM to find what objects are affected, and once you have hundreds or thousands of them, this can get kinda messy (especially if EVERY object has a loop that goes through every other object). Won't impact your game too much now, but keep it in mind for later.


(If it wasn't clear why my suggestion is better: running the loop at the start like that means you only need to run it once, not every step.)
 

TheouAegis

Member
//bullet object - step event with(obj_player01){ if (place_meeting(goalX, goalY, obj_bullet01)) { instance_destroy(obj_bullet01); } }
That's saying, "if the player was at (goalx,goaly), would the bullet hit him?"

Assuming the bullet isn't moving too fast, you could do

if point_distance(goalX,goalY,x,y) < 32

but that's still not as accurate as

if point_distance(goalX,goalY,x,y) < speed
(Assuming you use speed)
 

Carcophan

Member
Thanks for the input Yal

I converted the code to this:
Would be better to use
with(instance_create_layer(x, y, "Bullet_Layer", obj_bullet01)){
goalX = mouse_x;
goalY = mouse_y;
}
but am getting the 'not set before reading it.' error message now, when I implement it.

(Assuming you use speed)
Yes - I :was: using speed, but have converted it to what I displayed above. Though this is also now giving me the 'not set variable' error with element "goalY"
 

Yal

🐧 *penguin noises*
GMC Elder
Code:
Would be better to use
This isn't part of the code, that might be the reason you get errors...
 

TheouAegis

Member
You are getting the error for goalX or goalY because, most likely, you are still trying to run the code via the player's focus.

THIS is the proper code:

Player Step Event
Code:
with(instance_create_layer(x, y, "Bullet_Layer", obj_bullet01)){
  goalX = mouse_x;
  goalY = mouse_y;
  direction = point_direction(x,y,goalX,goalY);
}
Bullet Create Event
Code:
speed = 8; // or whatever speed you want
Bullet Step Event
Code:
if point_distance(x,y,goalX,goalY) < speed
instance_destroy();
if you get any errors at all with that, then you need to post the error message because you shouldn't get any errors at all.
 

Carcophan

Member
THIS is the proper code:
It sure is!!

It works better than my revised code/post above does - AND it resolves another issue with the bullet destroying 'before' the mouse, versus right 'at' the mouse, so win-win!!!

Thanks a million folks.
 
Top