GML enemies "seeing" through tiles

Pfap

Member
[SOLVED]Ok, so I have a really nice mp_grid system where I blocked off cells based on whether or not a wall tile was in them. I didn't really plan on what an issue enemies being able to see through walls would be though.

The collision_line() function only works with objects.

I'm trying to find a way to check each step whether there is a wall tile between the player and the enemy.

The below solution doesn't work and I feel doesn't come close. I'm way off track here...


Code:
var i,j;
for(i = 0; i < 512;){
 
 for(j = 0; j < 288; j += 32;)
if tilemap_get_at_pixel(path_collision,i,j) != 0{//if it doesn't == 0 it means theres a tile there

//wall_block = true;

if point_in_rectangle(i, j, x -256, y - 10, x + 10, y + 10) == true{//if true then there is a wall there and we can't //see
 //this checks to see if the given point is in the rectangle!!!!
 
 
//run checking code
show_debug_message("walls");


}

//set the below to varibales that change sign based off direction!!!!!!!!!!!!!
if point_in_rectangle(i, j, x -10, y - 10, x + 10, y + 10) == false{//if true then there is a wall there and we can't //see
 //this checks to see if the given point is in the rectangle!!!!
 
//run checking code
show_debug_message("no walls");

}
}
i += 32;
//j += 32;
}


Does anybody know of a solution or can point me in the direction of a tutorial or work around.

I think this may be a possible case where an "intermediate value theorem" type solution would work, but really don't want to have to bust out the calculus books. I'm not even sure how you would iterate through that... I'm thinking get the players coordinates and the enemies coordinates and check every position between for a tile. Or since I know the tiles cell position just get it's x and y and then get the players coordinates and enemies coordinates, and if the tiles coordinates are inbetween then I know theres a wall there....





TL;DR

Is there a way to get some collision_line() like functionality with tiles???
 
Last edited:

JackTurbo

Member
Run a point dir between the ai unit and the player then check the tiles every n number of pixels down that line using length dir x/y and tile get functions.

It's not ideal and won't be cheap but you shouldn't need to do it every frame
 

Pfap

Member
Run a point dir between the ai unit and the player then check the tiles every n number of pixels down that line using length dir x/y and tile get functions.

It's not ideal and won't be cheap but you shouldn't need to do it every frame
I did some playing around and that should work, but seems less feasible because then I would need to go through and only run my code if out of all the length direction checks none of them produced a tile with a wall.... if I'm thinking correctly. Any case where there is multiple walls between ai and player will result in a line that has multiple cases where there are wall tiles in between.

I could try reading the results into an array and only running the code where the enemy spots the player if that array doesn't contain any cases of tiles being found.
I think it may be too slow especially if I add more than one enemy.

Code:
var my_distance = point_distance(x,y,player_test1.x,player_test1.y);//this is the vector line
var my_direction = point_direction(x, y,player_test1.x,player_test1.y);//this is the vector angle
var i,j;
for(i = 30; i < my_distance;){
 for(j = 30; j < my_distance; j += 30)
//var xget = abs(floor(lengthdir_x(i,my_direction)));//the direction towards the player
//var yget =  abs(floor(lengthdir_y(j,my_direction)));
var xget = x +  lengthdir_x(i,my_direction);//the direction towards the player
var yget = y +  lengthdir_y(j,my_direction);

 //i += 1;
//show_debug_message(my_distance);
//show_debug_message(my_direction);
show_debug_message(xget);
show_debug_message(yget);

 //tilemap_get_at_pixel()
 
 
 if tilemap_get_at_pixel(path_collision,xget,yget) == 0{ //0 for empty tile
 
 show_debug_message("no wall");
 
 }
 i += 30;
}

I may have to rethink how to do this because I'm having enemies patrol an area and do need to perform the check near every frame.
 
Last edited:
There was a thread where someone wanted to know if a large sprite of a room (a snapshot of all the objects) could be used for path finding. Interestingly - it is possible. It also works with collision_line, though I can't say whether its more intensive than just having the objects present and checking them all.

Basically you'd have to loop through all the wall tiles and draw them onto a surface that is the size of the screen. Create a sprite from the surface, and set it to precise checking. Have an object with this sprite and place it in the room.

You'd have the cost of maintaining the large surface with only one collision_line check, versus having to have all the objects and doing a collision_line check for each one. This is the code I had in a create event for an object, that was placed last in the rooms instance order:
Code:
clip_w = room_width;
clip_h = room_height;
draw_clear(c_white);
clip_mask = surface_create(clip_w, clip_h);
with (obj_wall)
{surface_set_target(other.clip_mask);
draw_sprite_ext(sprite_index, image_index, x, y, 1, 1, 0,c_black,1);
surface_reset_target();
instance_destroy();}
surface_set_target(clip_mask);
sprite_index = sprite_create_from_surface(clip_mask, 0, 0, clip_w, clip_h, false, false, x, y);
surface_reset_target();
surface_free(clip_mask);
sprite_collision_mask(sprite_index, true, 0, 0, 0, 0, 0, 0, 255);
mask_index = sprite_index;
As I said - this works, but I can't say if it's
(a) the best way to do it - compared to a for loop with lengthdir_x etc going through tiles, or a with statement and doing collision_line for all wall objects
(b) very practical - for a fixed screen it would be usable as a one time only process (create the surface after all tiles have been placed), but would require more effort for anything scrolling. Having to loop through all the tiles and redraw them etc each time the view moved - things like that
(c) even coded as well as it could be - it worked, but some of it may be unnecessary
 

Pfap

Member
Run a point dir between the ai unit and the player then check the tiles every n number of pixels down that line using length dir x/y and tile get functions.

It's not ideal and won't be cheap but you shouldn't need to do it every frame
Code:
var my_distance = point_distance(x,y,player_test1.x,player_test1.y);//this is the vector line
var my_direction = point_direction(x, y,player_test1.x,player_test1.y);//this is the vector angle

if stop == false{
var i,j;
for(i = 16; i < my_distance;){
 for(j = 16; j < my_distance; j += 16)
//var xget = abs(floor(x + lengthdir_x(i,my_direction)));//the direction towards the player
//var yget =  abs(floor(y + lengthdir_y(j,my_direction)));
var xget = (x + lengthdir_x(i,my_direction));//the direction towards the player
var yget =  (y + lengthdir_y(i,my_direction));

 //i += 1;
//show_debug_message(my_distance);
//show_debug_message(my_direction);
//show_debug_message(xget);
//show_debug_message(yget);

 //tilemap_get_at_pixel()
my_tile += string(tilemap_get_at_pixel(path_collision,xget,yget));// != 0{ //0 for empty tile
show_debug_message(my_tile);
  //show_debug_message("wall");
  //behavior_motion_control.spotted = true;
 
  //stop = true;//if this happens don'tperform the next iteration!! Otherwise there is a wall
  //if the player moves or theres change in direction reset stop back to false
 //}
 
 if tilemap_get_at_pixel(path_collision,xget,yget) == 1{ //0 for empty tile
 
  show_debug_message("wall");
  stop = true;
  //stop = true;//if this happens don'tperform the next iteration!! 
  //if the player moves or theres change in direction reset stop back to false

 }
 else{
 
show_debug_message("no wall");
 
 }
 i += 16;
}
show_debug_message("end of line");
}
Every 16 pixels along the line I'm checking for a wall tile.
So, the last string of digits basically shows there are 2 walls between my player and character "000011100000111000"
and that I can probably make my counting iterator a larger value since it caught the wall 3 times.
I'm really struggling on finding a way to use this data to do anything useful, even though it's exactly what I need.

the above code show_debug_message output:
0
no wall
00
no wall
000
no wall
0000
no wall
00001
wall
000011
wall
0000111
wall
00001110
no wall
000011100
no wall
0000111000
no wall
00001110000
no wall
000011100000
no wall
0000111000001
wall
00001110000011
wall
000011100000111
wall
0000111000001110
no wall
00001110000011100
no wall
000011100000111000
no wall
end of line
 
Last edited:

Pfap

Member
There was a thread where someone wanted to know if a large sprite of a room (a snapshot of all the objects) could be used for path finding. Interestingly - it is possible. It also works with collision_line, though I can't say whether its more intensive than just having the objects present and checking them all.

Basically you'd have to loop through all the wall tiles and draw them onto a surface that is the size of the screen. Create a sprite from the surface, and set it to precise checking. Have an object with this sprite and place it in the room.

You'd have the cost of maintaining the large surface with only one collision_line check, versus having to have all the objects and doing a collision_line check for each one. This is the code I had in a create event for an object, that was placed last in the rooms instance order:
Code:
clip_w = room_width;
clip_h = room_height;
draw_clear(c_white);
clip_mask = surface_create(clip_w, clip_h);
with (obj_wall)
{surface_set_target(other.clip_mask);
draw_sprite_ext(sprite_index, image_index, x, y, 1, 1, 0,c_black,1);
surface_reset_target();
instance_destroy();}
surface_set_target(clip_mask);
sprite_index = sprite_create_from_surface(clip_mask, 0, 0, clip_w, clip_h, false, false, x, y);
surface_reset_target();
surface_free(clip_mask);
sprite_collision_mask(sprite_index, true, 0, 0, 0, 0, 0, 0, 255);
mask_index = sprite_index;
As I said - this works, but I can't say if it's
(a) the best way to do it - compared to a for loop with lengthdir_x etc going through tiles, or a with statement and doing collision_line for all wall objects
(b) very practical - for a fixed screen it would be usable as a one time only process (create the surface after all tiles have been placed), but would require more effort for anything scrolling. Having to loop through all the tiles and redraw them etc each time the view moved - things like that
(c) even coded as well as it could be - it worked, but some of it may be unnecessary

Yea, this does seem like an interesting solution and should work if I draw my tilemap to a surface.... hmmmm
 
W

Wraithious

Guest
One way you could do it is have the wall object run a point direction to the enemy twice from wall to enemy, one from enemy top to wall top, one from enemy bottom to wall bottom, then run 2 colision lines from wall top and bottom to extend using the direction you got for the point direction checks. If the top colision line is above your player and the bottom colisiin line is below your player then the enemy cannot see your player.

EDIT:
To elaborate further, I probably shouldn't have said 'top' and 'bottom' , as you will be using 2 of the 4 bbox functions for this, so depending on what 'quadrants' the enemy and player is in relitive to each other you may be using bbox_left and bbox_right on the wall and enemy point direction checks. and to keep processing to a minimum you'd only want to make your collision lines extend from wall to the appropriate player bbox (so if enemy is on right, wall in middle, colision line stops at player bbox_left), and only bother checking and making the colision lines when the wall is between the enemy and player.
 
Last edited by a moderator:
Just to add to my previous post. Having asked about the details of surfaces, and sprites created from surfaces, Nocturne confirmed to me that the resulting sprite is not volatile like surfaces are. It creates a new texture page, whereas a surface doesn't.

This adds some extra cost to things - as right now I'm not sure if you can optimize the page afterwards, like you can with the pages outside of the game. It would only be one page extra in the scheme of things, but it is a performance hit. The question is whether its better than the calculation based alternatives.
 

Pfap

Member
Run a point dir between the ai unit and the player then check the tiles every n number of pixels down that line using length dir x/y and tile get functions.

It's not ideal and won't be cheap but you shouldn't need to do it every frame

One way you could do it is have the wall object run a point direction to the enemy twice from wall to enemy, one from enemy top to wall top, one from enemy bottom to wall bottom, then run 2 colision lines from wall top and bottom to extend using the direction you got for the point direction checks. If the top colision line is above your player and the bottom colisiin line is below your player then the enemy cannot see your player.

EDIT:
To elaborate further, I probably shouldn't have said 'top' and 'bottom' , as you will be using 2 of the 4 bbox functions for this, so depending on what 'quadrants' the enemy and player is in relitive to each other you may be using bbox_left and bbox_right on the wall and enemy point direction checks. and to keep processing to a minimum you'd only want to make your collision lines extend from wall to the appropriate player bbox (so if enemy is on right, wall in middle, colision line stops at player bbox_left), and only bother checking and making the colision lines when the wall is between the enemy and player.
Just to add to my previous post. Having asked about the details of surfaces, and sprites created from surfaces, Nocturne confirmed to me that the resulting sprite is not volatile like surfaces are. It creates a new texture page, whereas a surface doesn't.

This adds some extra cost to things - as right now I'm not sure if you can optimize the page afterwards, like you can with the pages outside of the game. It would only be one page extra in the scheme of things, but it is a performance hit. The question is whether its better than the calculation based alternatives.

Right now I'm using the above code I posted except I'm using a break; statement to exit the for loop if a 1 is found and then turning the tile string into a real and if it equals 0 chasing my player. I'm going to mark this one as solved, but I think the other solutions are also pretty good and I'm particularly interested in the surface one; I think it would be easier to set up and debug in a separate object. So, I found something that worked, but will keep playing with it. Thanks everybody that posted :)
 
Top