SOLVED Giving the illusion of shooting down while flying in isometric 2d

Roastedzerg

Member
Hey guys, I'm having trouble trying to rack my brain on this. Basically im creating an isometric 2d top down game and am working on giving the illusion of flight by raising the sprite of the flying objects above their real location but subtracting sprite_height from their Y to bring them up and stretching their image_xscale and image_yscale by adding sprite_stretch, all on their draw event like so:
GML:
draw_sprite_ext(sprite_index,image_index,x,y-sprite_fly,image_xscale+sprite_stretch,image_yscale+sprite_stretch,image_angle,image_blend,image_alpha);
The enemy objects all work like a charm, however, right now I'm trying to make them shoot projectiles down at the player, and its not fairing so well. Basically, I'm having trouble getting them to decrease sprite_fly when the target is closer, making the projectile look like its dropping slower, and faster when the target is further away. I'm thinking this should give a good illusion of the projectile shooting down from higher up. I feel like I got close, but right now the formula I tried does the opposite and moves faster when the player is close and slower when they are far. Here's what I'm using:

GML:
//Movement
distance=point_distance(x,y,fly_shoot_x,fly_shoot_y);
if(round(distance)>5)
{
    speed=fly_projectile_speed;
    move_towards_point(fly_shoot_x,fly_shoot_y,speed);
}
else{speed=0;}
//Falling
if(start_fall)
{
    start_fall=false;
    sprite_fly_start=sprite_fly;
    fall_time=distance*(1/fly_projectile_speed);
    fall_speed=sprite_fly_start/fall_time;
}

if(sprite_fly>0){sprite_fly-=fall_speed;}else{sprite_fly=0;}

if(sprite_stretch>0){sprite_stretch-=sz;}else{sprite_stretch=0;}

if(image_alpha<1){image_alpha+=al;}else{image_alpha=1;}
I'm not so great at math so for the most part i just kind of fudged it, maybe you guys can guide me on where im going wrong here. This is hard to explain, but let me know if you need any more details.
Thank you

EDIT 1: To clearify its not so much the movement to the player that is the problem, its dropping the sprite y axis using sprite_fly that is giving me issues. specifically changing the speed of the drop to be slower when target is close and faster when the target is further away.

Edit 2: For some reason when the player is standing where the enemy shadow is (the actual location of the enemy object) the bullet doesnt shoot correctly and instead is shot from below the shadow or created directly on the player. This probably has to do with the fact that the bullet is technically being created from the object (shadow) and then the sprite is using sprite_fly to make it appear to be higher up. So when the player stands near the shadow the bullet only moves a few pixels out, possibly causing this glitch.

Edit 3: if it helps, here is the code used in the enemies shoot while flying state when creating a bullet:
GML:
my_fly_bullet=instance_create_depth(x,y-my_height,depth,fly_bullet_type)
my_fly_bullet.my_parent=self.id;my_fly_bullet.direction=aim_dir;
my_fly_bullet.fly_projectile_speed=fly_projectile_speed;
my_fly_bullet.fly_shoot_x=fly_shoot_x;my_fly_bullet.fly_shoot_y=fly_shoot_y;my_fly_bullet.fly_shoot_distance=fly_shoot_distance;
my_fly_bullet.sprite_fly=sprite_fly;my_fly_bullet.sprite_stretch=sprite_stretch;
 
Last edited:
Code:
    start_fall=false;
    sprite_fly_start=sprite_fly;
    fall_time=distance*(1/fly_projectile_speed);
    fall_speed=sprite_fly_start/fall_time;
I don't fully understand what is going on here, what is sprite_fly? Is it a sprite? If so, why are you setting sprite_fly_start to a sprite and then using it like it's a number in fall_speed=sprite_fly_start/fall_time;? If it's not a sprite, where is it getting it's value from? As an aside, if fall_speed is a percentage running from 0 to 1 (which, it kinda might be? with the division?) then you can do a simple 1-(sprite_fly_start/fall_time) to get the inverse of that percentage, which would mean if that it's movement speed should be reversed from what it currently is.
 

Roastedzerg

Member
Sprite_fly is simply a variable used to adjust the y axis of the sprite of the object to give it the illusion of flying up. By adding sprite_fly_start=sprite_fly in the sprite_fall switch i had hoped to get the starting value of sprite_fly then determine the distance and adjustment speed in one step by then switching off sprite_fall cause sprite_fly changes as the object begins to descend. I could try your code later, but if it just pops out a negative value the sprite will ascend instead. I just want the speed to be slower when close to player and faster when far.
 
I didn't say it pops out a negative number, I said it inverts it. If it's a percentage between 0 and 1 it will become a percentage between 1 and 0, going the opposite way in other words.
 

HayManMarc

Member
If I'm thinking about this right, you just need to take the distance of your shot and divide it by the height of the shooter to get the correct "fall speed". If the shooter can change heights, then this calculation needs to be done for every shot.
(I hope I'm doing the math right.)
 

Roastedzerg

Member
Thanks guys, sorry for the confusion this is complicated for me to explain. Doesnt help that my math skills are rubbish lol Ill try out your suggestions when i get to my pc. Ill definitely give the inverse thing a try @RefresherTowel

If i need any more help ill try to convert some gifs of whats happening and share them.
Side note, anyone know of a good site or program for creating gifs?
 

HeadlessOne

Member
Hi Roasted,

I think using sprite_fly instead of Z had me confused there for a min... perhaps if you could comment your code telling us what each of your variables dose/ where it comes from that would help abit easier.

So basically to decrease your z (sprite_fly) more depending on how close someone is to you you need to figure out that angle from vertical... even though your game isn't 3d you need to think in 3d a bit for this one. I'll be using descriptive variables below so you will have to replace them with ones you already have in your code to make it work for you.

1629412430495.png
ok so Im sure you know that if you want to travel say 5pixels along the XY line on my image above... you end up traveling less then 5 in the x and the y (Depending on the angle) at 45degrees you end up traveling about 3.5 in both the x and the y.
1629415264513.png
the same should happen for the Z so if our angle up was 45 degrees then if we travel 5 pixels along the line i have called xyz we end up moving 3.5 z and 3.5 on the line we call xy, so then out x & y speeds would be more like 2.47.

Typically in 2d space you can use lengthdir_x() or lengthdir_y() to get the how far we move in the x and y based on our speed and direction (speed being the length of our xy line in the triangle)
In order to use this in 3d... we fake it, i like to pretend that we are starring in 2d at the front face of that yellow triangle like this
1629415043502.png
so now we can just us all our 2dfunctions like point_distance(), point_direction(), lengthdir_x(),lengthdir_y() but instead of x for the horizontal we use xy (which is the just the point_distance along the real x&y) and for the vertical we use our Z. there is a 'real math' way to do this... but i'm worried i might get kicked off the forums by evangelicals if they see all that ... sin (math puns!!!!!). I find working this way easier to wrap my little brain around.

so... since this is probably going to be something you do alot of in this game it would be a good idea to make some functions that figure this stuff out for you.

GML:
function lengthdir_3d_x(x1,y1,z1,x2,y2,z2,dist){
    var xy = point_distance(x1,y1,x2,y2) //this gives us the length in 2d from object 1 to object 2
    var xyz = point_distance(0,z1,xy,z2) //this gives us the length in 3d from object 1 to object 2
    var xydir = point_direction(x1,y1,x2,y2) //this gives us the direction in 2d from object 1 to object 2
    var xyzdir = point_direction(0,z1,xy,z2) //this give us the vertical angle our "3d angle"
    var xydist = lengthdir_x(dist,xyzdir) //this now gives us how long the "xy" is based on the distance specified (once we take away our vertical movement)
    var zdist = lengthdir_y(dist,xyzdir)
    var xdist = lengthdir_x(xydist,xydir)
    var ydist = lengthdir_y(xydist,xydir)
    return xdist;
}
(if you know anything about structs i would recommend creating one of those at the start of the step and then referencing these variables from it then killing it at the end of the step, would save on recalculating every time it is called... but also not nessesery if your newer and haven't yet played with structs yet just stick to whats comfortable)

if you add this function to a script, and then duplicate it 2 more times and change the return at the bottom output your xdist, ydist and zdist(and the name of the function accordingly) then you can call these functions to figure out how far the bullet should travel in the x,y & z based on a fixed speed... if you want to keep using your move_towards_point(fly_shoot_x,fly_shoot_y,speed); for movement then i would also copy the function a 4th time to spit out your "xydist" which you can think of like your speed on the 2d plane so it would end up looking something like this: move_towards_point(fly_shoot_x,fly_shoot_y,lengthdir_3d_xy(x1,y1,z1,x2,y2,z2,dist));

hope that all makes sense, take a look and let me know if you have any issues or questions.

edited:20/aug/2021 to fix a small error in code (error explained in reply further down)
 
Last edited:

Roastedzerg

Member
Wow! Thank you so much @HeadlessOne for such a detailed and spot on reply! This is why i love coming here when i get stuck, you guys really give it your all. Ill study what you posted and tinker around with it when i get to my pc. Thanks a bunch!
 

Roastedzerg

Member
Ok, im working on creating a script based on what youve posted @HeadlessOne, but was confused here:
if you add this function to a script, and then duplicate it 2 more times and change the return at the bottom output your xdist, ydist and zdist(and the name of the function accordingly) then you can call these functions to figure out how far the bullet should travel in the x,y & z based on a fixed speed.
Do you mean copying the script for each ydist and zdist, changing the scrip names to lengthdir_3d_y and lengthdir_3d_z and changing the return at the bottom of each script to return ydist and return zdist? Then doing it a 4th time for xydist? This is alot to digest so i need some clarification. Thanks!
Also would i run the scripts in the the start_fall switch or somewhere else? How do i use the scripts to determine how fast to drop the z/sprite_fly?
Edit: where does xyz in the script come into play? I dont see it referenced.
 
Last edited:

HayManMarc

Member
I got to thinking about this today and I'm a little confused. Since it's not 3d, why does it need to be so complicated in the first place? Wouldn't a simple straight line from shooter to target still create the desired effect in a pseudo 3d isometric environment? The projectile originates from the shooter and ends at the target in a straight line, just as it should. I have a feeling this is a problem of over-thinking the problem, unless I'm mistaken in my understanding of it.

I could be wrong, but thought I'd throw that out there just in case.
 

Roastedzerg

Member
I got to thinking about this today and I'm a little confused. Since it's not 3d, why does it need to be so complicated in the first place? Wouldn't a simple straight line from shooter to target still create the desired effect in a pseudo 3d isometric environment? The projectile originates from the shooter and ends at the target in a straight line, just as it should. I have a feeling this is a problem of over-thinking the problem, unless I'm mistaken in my understanding of it.

I could be wrong, but thought I'd throw that out there just in case.
It does work that way, in a sence, except that im trying to give the effect that its shooting down at the player from a higher location to give it depth and a 3d like appearance. Thats where it complicates things, as drawing the sprite to change depth and height to give this effect can do some funky things if not done correctly. Stuff like dropping to the ground and slidding the rest of the way and such.
 

HeadlessOne

Member
Do you mean copying the script for each ydist and zdist, changing the scrip names to lengthdir_3d_y and lengthdir_3d_z and changing the return at the bottom of each script to return ydist and return zdist? Then doing it a 4th time for xydist? This is alot to digest so i need some clarification.
yea so you end up with 4 scripts that are pretty much exactly the same called lengthdir_3d_x(), lengthdir_3d_y(), lengthdir_3d_z() & lengthdir_3d_xy() that output xdist, ydist, zdist and xydist respectively. ( i just didn't want to fill the whole screen by sending you 4 scripts).

You put these functions into a script object and that will initialize them for you to use... then you use them like you would with any other function that returns a value so you could use them inline, in other functions or set a variable to them... perhaps look up in the manual how the standard lengthdir_x() function works... and then this is just an advanced version of that.

so each step during movement you might want to just do:

GML:
x += lengthdir_3d_x(x,y,z,target.x,target.y,target.z,sped)
y += lengthdir_3d_y(x,y,z,target.x,target.y,target.z,sped)
z += lengthdir_3d_z(x,y,z,target.x,target.y,target.z,sped)
and that would every step move the object by speed in the direction of its target... so the falling (z motion) would be included in that movement toward the target. although in re-reading your code was your intention for the bullet to travel across first and then afterwards stop and fall? or was it intended to move in a direct line (in x,y & Z) towards the target, this code would help with latter but not the former.

your right xyz dosen't actually get used... I think i thought as i was writing it i would need it further down the line but because we use the distance that is input as part of the script we dont need it... you can delete that line.
 

Roastedzerg

Member
Thank you, ill try it out when i get home. It was definitely intended to move in a straight line like youre showing, but i did find that i can a simplified version of the code i have for drop attacks. Ill let you know how it all goes when i get to testing!
 

HeadlessOne

Member
I got to thinking about this today and I'm a little confused. Since it's not 3d, why does it need to be so complicated in the first place? Wouldn't a simple straight line from shooter to target still create the desired effect in a pseudo 3d isometric environment? The projectile originates from the shooter and ends at the target in a straight line, just as it should. I have a feeling this is a problem of over-thinking the problem, unless I'm mistaken in my understanding of it.

I could be wrong, but thought I'd throw that out there just in case.
your close, that would work for a side scroller or if you were doing hitscan weapons but if you think about firing a slow moving projectile at someone who is say 1000px above you in the y direction but standing on the ground say 1000px below you in the z direction then sprite to sprite you are drawn i the same spot but the bullet should not get there instantly (with a slow moving projectile).

I really think some images would help though since Technically the description says Isometric... which would lead me to think or something more like this for x,y,z.1629442607794.png
 

Roastedzerg

Member
Ran into a problem, sprite_z (originally sprite_fly) isn't changing, down or up. Also the guy is shooting backwards, the way i fixed that was by switching x+= and y+= to x-= and y-= for the bullets movements. However, adjustments dont seem to be working for the z axis. Here's a screenshot, the perspective we're trying is a different kind of top down isometric kinda way. Its more flat on, here's some programmer art, but look at our game in my signature to get a better picture, we're trying to do a more advanced version of it.
Right, the screenshot. The white box is the player, the red (partially transparent) floating thing is the flying enemy, the yellow dots are the projectiles, the tan is open feild, the black squares are walls, and the transparent black dot below everything is the shadow. Don't mind the small red and purple boxes. The shadow is where the object is actually standing and where the projectiles are being shot from before using the sprite_z trick to make it look 3d.
2021-08-20 (4).png

The code im using:
GML:
//In the end step event
x -= scr_lengthdir_3d_x(x,y,sprite_z,fly_shoot_x,fly_shoot_y,sprite_z_target,fly_projectile_speed)
y -= scr_lengthdir_3d_y(x,y,sprite_z,fly_shoot_x,fly_shoot_y,sprite_z_target,fly_projectile_speed)
z += scr_lengthdir_3d_z(x,y,sprite_z,fly_shoot_x,fly_shoot_y,sprite_z_target,fly_projectile_speed)

if(sprite_z>0){sprite_z-=z;}else{sprite_z=0;}

//In the draw event
draw_sprite_ext(sprite_index,image_index,x,y-sprite_z,image_xscale+sprite_stretch,image_yscale+sprite_stretch,image_angle,image_blend,image_alpha);
Any suggestions?
 

HeadlessOne

Member
interesting... i didn't actually test that code(sorry) just wrote it from the top of my head... looks like i put the arguments in this line the wrong way around
GML:
// it was this
var xyzdir = point_direction(xy,z1,0,z2) //this give us the vertical angle our "3d angle"

// it should be this
var xyzdir = point_direction(0,z1,xy,z2) //this give us the vertical angle our "3d angle"
so if you change that on all of your arguments it should work fine.

for your sprite_z stuff perhaps try this
Code:
//In the end step event
x -= scr_lengthdir_3d_x(x,y,sprite_z,fly_shoot_x,fly_shoot_y,sprite_z_target,fly_projectile_speed)
y -= scr_lengthdir_3d_y(x,y,sprite_z,fly_shoot_x,fly_shoot_y,sprite_z_target,fly_projectile_speed)
sprite_z += scr_lengthdir_3d_z(x,y,sprite_z,fly_shoot_x,fly_shoot_y,sprite_z_target,fly_projectile_speed)

if sprite_z<0 {sprite_z=0};
It dosen't seem like the variable z you had was useful to you so instead just add it direct to your sprite_z, and then you can check after with the if statment that just checks if its gone under 0 and sets it to zero.

When i tested it i created this example which might help you: 2.5D Top down shooter
controls:
left mouse = move toward target
right mouse = move away from target
middle mouse = move to mouse
space= move up in z
ctrl = move down in z.
 

Roastedzerg

Member
Hm ill keep fiddling around but its still not wanting to change sprite_z. Took a look at your example and it worked, though it had some odd bugs, for example if youre holding the fly up or down button while moving towards or away from the target, try it out and see what i mean. I'll let you know if i figure anything out on my end

Edit: Oh i see now, the reason he was flying up and down when i would move forward and backwards is because you made it that way to show how to use scr_lengthdir_3d_z right?

Edit 2: also the further i fly up the slower he moves forward and backwards from the target in your example. What could be causing that?

Edit 3: how can i automatically check if sprite_z needs to add or subtract when moving towards target in the code im using? Yours was straight forward, i press left mouse he moves forward and Z adds, i press right mouse he moves backwards and Z subtracts. Maybe im just missing something... I'll keep tinkering!

Edit 4: So i found if i switch sprite_z_target to 0 it almost worked, though the projectile only made it most of the way to the target before sliding on the ground the rest of the way lol Its also not a permenant fix, i gotta figure out why sprite_z_target wont work. Here's a screenshot to show whats happening. This is after applying some of the fixes btw
2021-08-20 (5).png

Edit 5: Ok, so the sliding was happening because i didnt switch sprite_z_target to 0 for scr_lengthdir_3d_x and scr_lengthdir_3d_y. After changing them it worked. Ill work with it some more and keep you posted.

Edit 6: Right-o! I forgot to change sprite_z_target to my_target.sprite_z when creating the bullet so it was aiming at its own sprite_z lol I fixed it and it seems to be working better. I need to test more before i call this topic solved, but i think we're almost there!

Edit 7: how can i go about making sprite_stretch adjust according to our sprite_z? It gets added to the image_xscale and image_yscale so the variable sprite_stretch needs to be changed in small intervals. Right now its just defaulted to sz=0.030 and looks like this:
GML:
if(sprite_stretch>0){sprite_stretch-=sz;}else{sprite_stretch=0;}
 
Last edited:

HeadlessOne

Member
Fantastic to see you have worked your way through it all... for next time one thing that often helps me work through that kinda stuff is the debug mode Here is a great video on it

for your image strech thing... i personally wouldn't bother with another variable... we already have one that tracks z (sprite_z)...that would just be a scaled down version of it. so in your sprite functions (or whereever you are setting your image_xscale & image_yscale) instead of using sprite_strech use your sprite_z * 0.03 then it will always correspond to the objects height.
 
I do a lot of iso stuff, and a nice little trick is to draw a shadow that stays on ground level for the projectile, while shooting it with the offset of your flying thing.
If you remove small details like this, it can easily begin to look wrong. It's the kind of things you notice when it's not there, if you will.
 
Top