GML Mask issues with image_yscale

S

Showtime

Guest
hey, so im making a shmup and i want one of the shot types to be a thick laser that stops at an enemy. ive figured out i can do this by scaling its yscale, but im having a problem where it seems it doesnt scale the mask too? im not sure what the problem is, ive run countless tests to try and find the problem, it seems to scale the mask when i scale it UP (for example image_yscale=2) but not when i scale it DOWN (like image_yscale=0.5).

furthermore when two enemy objects are at the same x possition the laser has so far (depending on what solutions ive tried to use, right now its the last mentioned) collided with both (regardless of the lasers yscale), only hit the ones in the back (again regardless of scaling) which is the opposite of what i want, or - and this is what has me most confused - the test enemies i have are arranged in a circle for optimal testing and the laser acts correctly on the RIGHT side of the circle, it hits the front enemies and works great, but on the LEFT side it only hits the ones in the back, and in the middle it seems to be really inconsistent how it behaves... it has nothing to do with coordinates as ive tried to move their spawner to different spots but its still right=right and left=wrong... ive been at this for days now and i really dont know how to fix it...?

if its worth mentioning im using game maker 8 actually

delete later dfjg.gif

heres a demonstration (the text is their "hp") (the coloring defects are because of the gif formatting). notice how ive even shrunk the laser smaller that i want it to be and so that it doesnt touch any of the objects...

please help and thank you for your time fhjdkfjghjkdfhdj

EDIT: by the way, when the laser gets long in the gif thats because ive set it so that it only scales in the collision event. however now that i think about it i havent set any event or script to scale it back, could this be something useful to know...? i mean its supposed to scale back but thinking of it idk why it does...

EDIT EDIT: i realised the reason it scales back is because the player (the red dot) is creating it every step, and every step (or every second step depending how you see it) the laser is set to destroy itself. could this maybe be why the collision is messing up? i still dont understand why it only works on the right side...
 
Last edited by a moderator:
S

Showtime

Guest
Could you show the code?
in the lasers collision with enemy event
Code:
image_yscale=point_distance(x,y,x,other.y)/480
this isnt quite the same code as ive been messing around trying to fix it and in doing so used a completely different code. i replicated t as well as i remembered though. not much is different from the image, except now the entire left side is affected, BUT the one in the middle isnt, even though the ones above and bellow it are...? it might be worth noting that the circled instances are a parent "enemy" object and the one in the middle is a child.
 

Simon Gust

Member
I see.
The collision event isn't really working out for you in this case.
1. When first setting the image yscale to a big number (the laser reaches to the top) it sometimes has more than 1 enemy in it's collision.
There are multiple collisions per frame but Game maker can only handle 1 collision a frame, so it will always hit the last created enemy.

2. point_distance will fake your result because you'll have x differences in your y differences. And again "other" in this case refers to the latest enemy.

You should do this in the step event and utilize loops

obj_player - step:
Code:
// set image_yscale to maximum possible range 
image_yscale = -100;

// get a list of enemies in front 
var enemies = scr_collision_list(x-5, y, x+5, y + image_yscale, obj_enemy);
if (enemies != noone)
{
    var size = ds_list_size(enemies);
    var closest = 1000;
    var enemy = noone;
   
    // go through the enemy list and see who's closest
    for (var i = 0; i < size; i++)
    {
        var inst = ds_list_find_value(enemies, i);
        var distance = abs(y - inst.y);
        if (distance < closest)
        {
            closest = closest;
            enemy = inst;
        }
    }
   
    // set the image_yscale to the closest distance
    image_yscale = -closest;
    with (enemy)
    {
        // do damage to the closest enemy
        health--;
    }
   
    // free memory
    ds_list_destroy(enemies);
}
The script scr_collision_list
Code:
/// scr_collision_list(x1, y1, x2, y2, obj)
var x1 = argument0;
var y1 = argument1;
var x2 = argument2;
var y2 = argument3;
var obj = argument4;
var dsid = ds_list_create();

with (obj) 
{
    if (id != other.id) 
    {
        var i = collision_rectangle(x1,y1,x2,y2,id,false,false);
        if (i != noone) ds_list_add(dsid, i);
    }
}
if (ds_list_empty(dsid)) 
{
    ds_list_destroy(dsid);
    dsid = noone;
}
return dsid;
What this does simplified is set the laser to it's maximum length to detect all enemies in a line, It makes a list of all those enemies that are in that line.
It will search the enemy that is closest, set the yscale of the laser to the distance and damages that enemy.

I haven't tested this.
 

Nocturne

Friendly Tyrant
Forum Staff
Admin
I don't think any of the above is required, tbh... :)

First make sure the sprite is 1 PIXEL THICK and has the origin in the BOTTOM CENTER. Also make sure that the collision mask settings are set to FULL IMAGE. With that done the code is simple enough:

Code:
var _inst = instance_place(x, y, OBJECT); // change OBJECT for the enemy object to check

while (_inst == _noone)
{
image_yscale += 8; // Change this to suit... 8 is probably a good resolution for the game as I see it, but test with other values if required
_inst = instance_place(x, y, OBJECT);
if image_yscale > room_height break;
}

if instance_exist(_inst)
{
image_yscale = y - _inst.y;
with (_inst)
    {
    // Do damage or whatever
    }
}

So, all we are doing is cvreating the imstance and then increasing its yscale in chunks of 8 pixels and looking for a collision. If one is found (or the laser gets longer than the room height) then the loop is broken and a check is performed on the "_inst" variable to see if it found anything. If it did then the scale is set to be the distance between the laser start and the enemy instance and then you can use the "_inst" variable to do damage to the enemy or whatever. This is not the most efficient way to do what you want but it's certainly the easiest and I don't think it will affect performance in any way at all really...

:)
 

Simon Gust

Member
I don't think any of the above is required, tbh... :)
First make sure the sprite is 1 PIXEL THICK and has the origin in the BOTTOM CENTER. Also make sure that the collision mask settings are set to FULL IMAGE. With that done the code is simple enough:
So, all we are doing is cvreating the imstance and then increasing its yscale in chunks of 8 pixels and looking for a collision. If one is found (or the laser gets longer than the room height) then the loop is broken and a check is performed on the "_inst" variable to see if it found anything. If it did then the scale is set to be the distance between the laser start and the enemy instance and then you can use the "_inst" variable to do damage to the enemy or whatever. This is not the most efficient way to do what you want but it's certainly the easiest and I don't think it will affect performance in any way at all really...
:)
But wouldn't it still target the latest instance instead the nearest one under certain conditions?
 

Nocturne

Friendly Tyrant
Forum Staff
Admin
But wouldn't it still target the latest instance instead the nearest one under certain conditions?
It'll detect the first one it meets in the collision following a straight line from the start position to the top of the room, which is exactly what the OP asked for:
i want one of the shot types to be a thick laser that stops at an enemy
:)
 

Simon Gust

Member
It'll detect the first one it meets in the collision following a straight line from the start position to the top of the room, which is exactly what the OP asked for
I kinda works but my point still stands. The laser yscale will not decrease when straved over to an enemy.
upload_2017-6-5_21-51-45.png
 
S

Showtime

Guest
I don't think any of the above is required, tbh... :)

First make sure the sprite is 1 PIXEL THICK and has the origin in the BOTTOM CENTER. Also make sure that the collision mask settings are set to FULL IMAGE. With that done the code is simple enough:

Code:
var _inst = instance_place(x, y, OBJECT); // change OBJECT for the enemy object to check

while (_inst == _noone)
{
image_yscale += 8; // Change this to suit... 8 is probably a good resolution for the game as I see it, but test with other values if required
_inst = instance_place(x, y, OBJECT);
if image_yscale > room_height break;
}

if instance_exist(_inst)
{
image_yscale = y - _inst.y;
with (_inst)
    {
    // Do damage or whatever
    }
}

So, all we are doing is cvreating the imstance and then increasing its yscale in chunks of 8 pixels and looking for a collision. If one is found (or the laser gets longer than the room height) then the loop is broken and a check is performed on the "_inst" variable to see if it found anything. If it did then the scale is set to be the distance between the laser start and the enemy instance and then you can use the "_inst" variable to do damage to the enemy or whatever. This is not the most efficient way to do what you want but it's certainly the easiest and I don't think it will affect performance in any way at all really...

:)

thanks! wont the image quality of the laser look terrible if its only one pixel being stretched out, though...? its just a blurred white bar but i worry...

one thing thats weird that might be because im using gm8 is that i cant write "var blabla = true" as such without telling me i cant do that...? additionally, instance_exist doesnt seem to work in gm8... would it be able to be written as "instance_number(_inst)!=0"?

another thing is id prefer the laser to instantanously resize and not gradually, if possible :0
 
S

Showtime

Guest
I see.
The collision event isn't really working out for you in this case.
1. When first setting the image yscale to a big number (the laser reaches to the top) it sometimes has more than 1 enemy in it's collision.
There are multiple collisions per frame but Game maker can only handle 1 collision a frame, so it will always hit the last created enemy.

2. point_distance will fake your result because you'll have x differences in your y differences. And again "other" in this case refers to the latest enemy.

You should do this in the step event and utilize loops

obj_player - step:
Code:
// set image_yscale to maximum possible range
image_yscale = -100;

// get a list of enemies in front
var enemies = scr_collision_list(x-5, y, x+5, y + image_yscale, obj_enemy);
if (enemies != noone)
{
    var size = ds_list_size(enemies);
    var closest = 1000;
    var enemy = noone;
  
    // go through the enemy list and see who's closest
    for (var i = 0; i < size; i++)
    {
        var inst = ds_list_find_value(enemies, i);
        var distance = abs(y - inst.y);
        if (distance < closest)
        {
            closest = closest;
            enemy = inst;
        }
    }
  
    // set the image_yscale to the closest distance
    image_yscale = -closest;
    with (enemy)
    {
        // do damage to the closest enemy
        health--;
    }
  
    // free memory
    ds_list_destroy(enemies);
}
The script scr_collision_list
Code:
/// scr_collision_list(x1, y1, x2, y2, obj)
var x1 = argument0;
var y1 = argument1;
var x2 = argument2;
var y2 = argument3;
var obj = argument4;
var dsid = ds_list_create();

with (obj)
{
    if (id != other.id)
    {
        var i = collision_rectangle(x1,y1,x2,y2,id,false,false);
        if (i != noone) ds_list_add(dsid, i);
    }
}
if (ds_list_empty(dsid))
{
    ds_list_destroy(dsid);
    dsid = noone;
}
return dsid;
What this does simplified is set the laser to it's maximum length to detect all enemies in a line, It makes a list of all those enemies that are in that line.
It will search the enemy that is closest, set the yscale of the laser to the distance and damages that enemy.

I haven't tested this.

wow thats uh... a lot... im not used to working with the var and argument[n] stuff, and ive tried ds but it didnt seem to work out for me. or maybe i just didnt understand it, heh...

could you try and explain what everything is doing and, if possible, remove anything that isnt absoloutely necesarry? (like nicknames for functions which i know people commonly do, its useful but confusing for me...)

also wont... setting the yscale to -100 REALLY tear on the games memory / storage / etc..? my laser is already 480 in height (i forgot to mention that earlier so thats on me) so 4800 seems like it would really make the game lag a bit...scaling it to the room_height divided by its sprite_height seems like it would be less straining.

sorry, i just need clarifications on what exactly things do, i prefer to know what im implementing before i do it both so i can learn from it and so i can edit/fix it if needed ;;
 
S

Showtime

Guest
this still doesnt explain why the laser works perfectly fine on the right side but not on the left side, or why it doesnt effect the middle enemy but it does the two above and under it... to clarify, once the above and under enemies are defeated it can get damaged, or if i move above the under enemy, but otherwise it wont get hit ?
 

Simon Gust

Member
this still doesnt explain why the laser works perfectly fine on the right side but not on the left side, or why it doesnt effect the middle enemy but it does the two above and under it... to clarify, once the above and under enemies are defeated it can get damaged, or if i move above the under enemy, but otherwise it wont get hit ?
So collision functions including the collision event can only handle 1 collision at a time, so if you overlap with more than 1 enemy, it just chooses the enemy last created (highest id).

wow thats uh... a lot... im not used to working with the var and argument[n] stuff, and ive tried ds but it didnt seem to work out for me. or maybe i just didnt understand it, heh...

could you try and explain what everything is doing and, if possible, remove anything that isnt absoloutely necesarry? (like nicknames for functions which i know people commonly do, its useful but confusing for me...)

also wont... setting the yscale to -100 REALLY tear on the games memory / storage / etc..? my laser is already 480 in height (i forgot to mention that earlier so thats on me) so 4800 seems like it would really make the game lag a bit...scaling it to the room_height divided by its sprite_height seems like it would be less straining.

sorry, i just need clarifications on what exactly things do, i prefer to know what im implementing before i do it both so i can learn from it and so i can edit/fix it if needed ;;
Code:
// set image_yscale to maximum possible range
image_yscale = -10;

// get a list of enemies in front
var enemies;
enemies = scr_collision_list(x-5, y, x+5, y + image_yscale, obj_enemy);

// if the list even holds enemies -> continue
if (enemies != noone)
{
    // create a variable that holds the distance of the closest enemy
    // this is set to a high number first so that it is comparable
    var closest;
    closest = 1000;
  
    // this variable will hold the id of the the closest enemy
    // with the id in possession you can directly access it's variables
    var enemy
    enemy = noone;
  
    // just a variable for the loop
    var i;
  
    // go through the enemy list and see who's closest
    for (i = 0; i < ds_list_size(enemies); i++)
    {
        // get the i position from the list and store it in inst
        var inst;
        inst = ds_list_find_value(enemies, i);
      
        // create a variable that will hold the distance between the player and the enemy
        var distance;
        distance = abs(y - inst.y);
      
        // if that distance is less than the previous distance it will update it
        if (distance < closest)
        {
            closest = closest;
            enemy = inst;
        }
        // it will overwrite it as soon as it seen a distance that is shorter
    }
  
    // set the image_yscale to the closest distance
    image_yscale = -closest;
    with (enemy)
    {
        // do damage to the closest enemy
        health--;
    }
  
    // free memory
    ds_list_destroy(enemies);
}
Code:
/// scr_collision_list(x1, y1, x2, y2, obj)
// create empty list of enemies
var dsid;
dsid = ds_list_create();

// random variable
var i;

// go into all your enemy objects
with (argument4)
{
    var inst;
    inst = collision_rectangle(argument0, argument1, argument2, argument3, other.id, false, false);
    // create a variable that will hold the id of the laser if it collides
  
    // if there is a collision with the currently "accessed" enemy
    if (inst != noone)
    {
        // add it's own id to the enemy list
        ds_list_add(dsid, id);
    }
}

// if no enemies end up in the list because the laser is nowhere near an enemy
if (ds_list_empty(dsid))
{
    // it will just delete the list again and return noone
    ds_list_destroy(dsid);
    dsid = noone;
}

// here it will return the list or noone
return dsid;
I don't know if all functions will work in gm8
 
S

Showtime

Guest
So collision functions including the collision event can only handle 1 collision at a time, so if you overlap with more than 1 enemy, it just chooses the enemy last created (highest id).
but... why does it not do that with the right side, then...? if it helps, the one in the middle is a spawner and its spawning them counter-clockwise from 270 degrees (do the bottom-most instance is the first in this circle)
 

Simon Gust

Member
but... why does it not do that with the right side, then...? if it helps, the one in the middle is a spawner and its spawning them counter-clockwise from 270 degrees (do the bottom-most instance is the first in this circle)
Because you did spawn them in a circle. If you gave every enemy a number
create event - obj_enemy
Code:
number = instance_number(obj_enemy);
And display that number
draw event - obj_enemy
Code:
draw_text(x + 10, y, string(number));
You can test it yourself, the enemy with the higher number will be hit.
 
S

Showtime

Guest
Because you did spawn them in a circle. If you gave every enemy a number
create event - obj_enemy
Code:
number = instance_number(obj_enemy);
And display that number
draw event - obj_enemy
Code:
draw_text(x + 10, y, string(number));
You can test it yourself, the enemy with the higher number will be hit.
ok but thats not at all what happens...

delete laterrrr.png

(above is their health, below is their "number"). i followed your testing instructions exactly.

by the logic youre presenting (which makes sense) wouldnt it make MORE sense that its hitting the earlier created ones...?
 
S

Showtime

Guest
Maybe. Are you using my code or some other?
im using the one i showed earlier.
Code:
//in collision even for laser
image_yscale=point_distance(x,y,x,other.y)/480 //480 is the regular heigth of the sprite
by the way, since i saw you mentioning it, there shouldnt be an x differance as both are set to the same variable (x) so theyll always be the same
 

Nocturne

Friendly Tyrant
Forum Staff
Admin
I kinda works but my point still stands. The laser yscale will not decrease when straved over to an enemy.
Yes it will if you modify the code to check in the step event. ;)

thanks! wont the image quality of the laser look terrible if its only one pixel being stretched out, though...? its just a blurred white bar but i worry...
The standard way to do a laser is to stretch the main sprite and add start/end pieces if you need them. Trust me I've done LOTS of lasers in lots of games and this is how you do them in general... But you don't need to draw it that waqy. You can draw it however you choose, the important part is the collision checking and "chunking" the collision check is the only way to get a guaranteed "first hit" and is the way that everyone does it. If you do a search of the old GMC archive and look at all the people doing laser collisions, they ALL (in one way or another) do a loop that extends from the calling instance and checks for a collision on the way... http://gmc.yoyogames.com/index.php?...search_term=laser+collision&search_app=forums

another thing is id prefer the laser to instantaneously resize and not gradually, if possible :0
My code is instantaneous and not gradual. Have you even tried it?
 
S

Showtime

Guest
Yes it will if you modify the code to check in the step event. ;)



The standard way to do a laser is to stretch the main sprite and add start/end pieces if you need them. Trust me I've done LOTS of lasers in lots of games and this is how you do them in general... But you don't need to draw it that waqy. You can draw it however you choose, the important part is the collision checking and "chunking" the collision check is the only way to get a guaranteed "first hit" and is the way that everyone does it. If you do a search of the old GMC archive and look at all the people doing laser collisions, they ALL (in one way or another) do a loop that extends from the calling instance and checks for a collision on the way... http://gmc.yoyogames.com/index.php?...search_term=laser+collision&search_app=forums



My code is instantaneous and not gradual. Have you even tried it?
! ! i had to change some things in the code to make it work in gm8, but after a while it worked perfectly!! i dont know how you did it, but thank you so much for the help! :'D
 
Top