• Hey Guest! Ever feel like entering a Game Jam, but the time limit is always too much pressure? We get it... You lead a hectic life and dedicating 3 whole days to make a game just doesn't work for you! So, why not enter the GMC SLOW JAM? Take your time! Kick back and make your game over 4 months! Interested? Then just click here!

Legacy GM SOLVED: Ball that moves in a curve

V

Vito_Curse

Guest
I believe this must be really easy and I'm actually baffled that I have no idea how to do it at this point.
So here's the deal: I have an enemy that throws balls at you, the thing is I want the ball to be thrown up and make a curve.
I know the basics like vspeed and hspeed and I gather I'd have to get the players x and y coordinates, but the curve part is really giving me a hard time.

I hope you guys understand what I'm saying. If not, feel free to ask me to be clearer.
 

Bingdom

Googledom
Put something like this in your projectile's create event.
Code:
gravity = 0.1;
And shoot the ball somewhere using motion_set
 
V

Vito_Curse

Guest
But it has to end up hitting the player... it's not shooting randomly.
The code would need to get the players x and y at some point, motion_set only uses dir and speed...
 
V

Vito_Curse

Guest
I'll be reading that, and yes, the player remains stationnary.

Edit:
Okay, I read it!

You used the formula:
var t = (argument0 - x) / hspeed;
return (gravity * 0.5 * (t + 1) + vspeed) * t + y;

I tried using it in the ball's step event like this:

var t = (obj_player.x - x) / hspeed;
return (0.3 * 0.5 * (t + 1) + vspeed) * t + y;

But it didn't work, the ball didn't move at all. I must be doing something wrong.
 
Last edited by a moderator:

jazzzar

Member
I'll be reading that, and yes, the player remains stationnary.

Edit:
Okay, I read it!

You used the formula:
var t = (argument0 - x) / hspeed;
return (gravity * 0.5 * (t + 1) + vspeed) * t + y;

I tried using it in the ball's step event like this:

var t = (obj_player.x - x) / hspeed;
return (0.3 * 0.5 * (t + 1) + vspeed) * t + y;

But it didn't work, the ball didn't move at all. I must be doing something wrong.
You do NOT use return in a step event
 

jo-thijs

Member
Whoops, I was confused before.
That link does the reverse of what you want.

So, your projectile has a constant gravity and an initial speed, right?
And your player is stationnary, right?
 
V

Vito_Curse

Guest
Yes for both of those questions.
Player's coordinates are always the same and ball has gravity and speed.
 

jo-thijs

Member
So, it is the initial direction you're searching for.

The link I posted, though it isn't the answer to your question, will still be useful.
It provides a description of the path the ball will follow, given a certain initial position, speed, gravity and direction.
The path is described in function of t (the amount of steps since the initial state) by:
X(t) = x + hspeed * t
Y(t) = y + (vspeed + gravity / 2) * t + gravity / 2 * t²

If we consider the 3D object (with axis X, Y and t) that contains every reachable point (X(t), Y(t)) of every direction together at moment t, we can describe it as:
(X - x)² + (Y - y - gravity / 2 * (t + 1) * t)² = (speed * t)²

This gives the following 4th degree equation:
(gravity² / 4) * t^4 + (gravity² / 2) * t^3 + ((gravity / 4 - (Y - y)) * gravity - speed²) * t^2 - ((Y - y) * gravity) * t + ((X - x)² + (Y - y)²) = 0

4th degree polynomial equations are just in the realm of solvable by radicals (or "equations").
You can start researching on how to do this here: http://www.sosmath.com/algebra/factor/fac12/fac12.html

If you replace X with obj_player.x and Y with obj_player.y, you can calculate 4 values for t.
Some will be real numbers (which are actual solutions) and some will be complex (which are no solutions you're interested in).
If you take every real and positive t, you will have every relevant solution.

t represents the amount of steps it will take to hit the player in a certain direction.
The smallest t of those will provide the best time in which you can hit the player.

Once you've found that vlue for t (we'll call it T), you can calculate hspeed like this:
hspeed = (obj_player.x - x) / T

This leaves 2 possible values for vspeed.
Try seeing if vspeed = sqrt(speed² - hspeed²) is a correct solution for:
Y(t) = y + (vspeed + gravity / 2) * t + gravity / 2 * t²

If so, that's the value for vspeed and you've found your direction.
Otherwise, vspeed = -sqrt(speed² - hspeed²) is the correct solution
and you've once again found your direction.

If you've found no value for T, it means the ball can't hit the player in this model and it is no use to aim the ball whatsoever.

I've not gone really in depth in every step, so if you have any questions, feel free to ask them.

NOTE: Whenever I used speed, I meant the initial speed of the ball when it launches, not the value of variable speed at that time in GameMaker.
 
Last edited:
V

Vito_Curse

Guest
Okay... it was nowhere near as simple as I thought, judging by the fact that I have no idea how to use the formulas you provided me LOL I feel like the dumbest person ever right now.

So...

X(t) = x + hspeed * t
Y(t) = y + (vspeed + gravity / 2) * t + gravity / 2 * t²

this would go under the create event of the ball? I'd have to change x and y for obj_player.x and obj_player.y?

(gravity² / 4) * t^4 + (gravity² / 2) * t^3 + ((gravity / 4 + (Y - y)) * gravity - speed²) * t^2 + ((Y - y) * gravity) * t + ((X - x)² + (Y - y)²) = 0

or is THIS the equation that I need to use to find the t value? it's kinda big and I have absolutely no idea what is happening on it lol

I feel really bad that you actually had to calculate all of that and I can't even understand half of it... I did understand the last bit about calculating the hspeed and vspeed based on the result of t, I just have no idea how to use that formula to get the t.

Sorry if I'm being too much of a beginner.
 
See if modifying this code suits your needs. Disclaimer, math isn't my strong suit, so I'm not totally sure how this works. I just translated some math equations into gml.

This is for a projectile with a uniform initial velocity, and constant gravity. The target and shooter can be at different altitudes. low_arc true (dont forget to initialize it) is trajectory with minimum time of flight, low_arc false is trajectory with maximum time of flight.

Code:
if (mouse_check_button_pressed(mb_right)) { low_arc = !low_arc; }
//--------------------------------------------------------
var _x = mouse_x - x;
var _y = y - mouse_y;
var _v = 15; //projectile speed
var _v2 = _v * _v;
var _g = 0.3;  //gravitational acceleration  (0.3 pixels per step per step)
//--------------------------------------------------------
var _a = _v2 * _v2 - _g * (_g * _x * _x + 2 * _y * _v2);
if (_a > 0) {
    //target is within range if _a > 0
    if (low_arc) { var _b = -(_v2 - sqrt(_a)) / (_g * _x); }
    else         { var _b = -(_v2 + sqrt(_a)) / (_g * _x); }
    var _c = 1 / _b;
    var _m = sign(_x * _b) * _v / sqrt(1 + _c * _c);
    var _hsp = _c * _m;
    var _vsp = _m;
    //--------------------------------------------------------
    if mouse_check_button_pressed(mb_left) {
        with (instance_create(x,y,obj_projectile)) {
            hspeed = _hsp;
            vspeed = _vsp;
            gravity = _g;
        }
    }
    //--------------------------------------------------------
    image_angle = point_direction(0,0,_hsp,_vsp);  //point gun same direction as initial projectile direction
}
 
D

DariusWolfe

Guest
If starting and ending positions are static, it seems to me that a path would be the easiest way to do this.
 
V

Vito_Curse

Guest
Okay! Your code worked! The only thing I can't seem to understand is the low_arc bit. I tried using low_arc=true, and the ball would make a very little ark, but when I used low_ark=false the ball didn't launch at all (?) Any idea why is that?

EDIT: Actually I realized what didn't work was using the speed as 5 (?) Now it works perfectly! Thanks!
 
Last edited by a moderator:

jo-thijs

Member
@Vito_Curse,
I'm glad your issue got solved!
However, the solution you're using is inaccurate.

@flyingsaucerinvasion,
You don't know what you wrote, but it is very clear though.
Yet, your code makes the same mistake I addressed in the link I provided in my first post on this thread.
You're using calculations for a continuous model, whereas GameMaker isn't continuous, but works in discrete steps.

Your code makes the assumption X and Y (the positions of the projectile) in function of time can be described by:
X(t) = x + hspeed * t
Y(t) = y + vspeed * t + gravity / 2 * t²

That's already wrong.
The error of this model will become larger as the projectile continues its path.

You then go on to assume hspeed won't be 0, which is often a safe assumption, but can be fatal sometimes.
Using that assumption, you write the time and the Y coordinate of the projectile in function of the X coordinate.
t(X) = (X - x) / hspeed
Y(X) = y + (X - x) * vspeed / hspeed + gravity / 2 * (X - x)² / hspeed²

You now define a new variable d, which is the tangens of the direction the arow goes to.
Again, you assume hspeed won't be 0 here.
d = vspeed / hspeed

Using this, you rewrite the above formula as follows:
Y(X) = y + (X - x) * d + gravity / 2 * (X - x)² * (1 + d²) / speed²
= (gravity / 2 * (X - x)² / speed²) d² + (X - x) d + (y + gravity / 2 * (X - x)² / speed²)
<=> (gravity / 2 * (X - x)² / speed²) d² + (X - x) d + ((y - Y) + gravity / 2 * (X - x)² / speed²) = 0
=> (gravity / 2 * (X - x)) d² + (speed²) d + (speed² * (y - Y) / (X - x) + gravity / 2 * (X - x)) = 0

In case you don't know, the equation of pythagoras gives us:
hspeed² + vspeed² = speed²
=> hspeed² = speed² / (1 + d²)

Which is the reason the first step in rewriting the formula is valid.

You now want that when the projectile has the target's X coordinate, it will also have the target's Y coordinate.
So, you can fill this in in: (gravity / 2 * (X - x)) d² + (speed²) d + (speed² * (y - Y) / (X - x) + gravity / 2 * (X - x)) = 0
and you then get a quadratic equation in d, which you let your code solve in this part:
Code:
var _x = mouse_x - x;
var _y = y - mouse_y;
var _v = 15; //projectile speed
var _v2 = _v * _v;
var _g = 0.3;  //gravitational acceleration  (0.3 pixels per step per step)
//--------------------------------------------------------
var _a = _v2 * _v2 - _g * (_g * _x * _x + 2 * _y * _v2);
if (_a > 0) {
    //target is within range if _a > 0
    if (low_arc) { var _b = -(_v2 - sqrt(_a)) / (_g * _x); }
    else         { var _b = -(_v2 + sqrt(_a)) / (_g * _x); }
However, you clearly treat 2 cases wrongly there.
The if condition shouldn't be _a > 0, but rather _a >= 0, as the target is also in range if a = 0.
However, you need to make sure to perform this check with math_set_epsilon(0), as the default epsilon is too fuzzy and can lead to an error.
Of course, you'll also need to restore the math_set_epsilon to its previous value afterwards with the help of math_get_epsilon.

And the second case you treat wrongly, is again the case that hspeed = 0 or x = mouse_x,
which will also throw an error in your face.

Now that you have a value for d, you calculate hspeed and vspeed out of it using this formula again:
hspeed² = speed² / (1 + d²)

Here you also assume speed to be positive, so you can rewrite it to:
abs(hspeed) = speed / sqrt(1 + d²)

To decide the sign of hspeed, you just take the sign of mouse_x - x, so that the projectile will move towards the target.

Finally, you use the definition of d again to calculate the value of vspeed:
vspeed = hspeed * d

This all is what you do here:
Code:
    var _c = 1 / _b;
    var _m = sign(_x * _b) * _v / sqrt(1 + _c * _c);
    var _hsp = _c * _m;
    var _vsp = _m;
A couple of further notes:
Even though your method starts from a wrong model, if you just replace it at the start with the correct model:
X(t) = x + hspeed * t
Y(t) = y + (vspeed + gravity / 2) * t + gravity / 2 * t²

And you apply the changes in every other step in my explenation,
you will get a result that looks a lot like your code.

Also, as someone who had to here it so many times at university,
your code to solve the quadratic equation isn't always very precise with respect to the size of the mantissa of the number representation your device uses.
You can find more on how it can be done better here: https://people.csail.mit.edu/bkph/articles/Quadratics.pdf

I'm also pretty sure the way you calculate _m can be done more precise when _b nears 0.

Having said all of this, I must admit that this approach does give code that is very likely to be a bit faster and quite a bit shorter than what my approach gives.
I suggest to the OP that he uses a corrected version of your suggestion.

@Vito_Curse,
I'm guessing you've read the above as well.

If you don't know how to apply the fixes I mentioned, feel free to ask for help.

If you still want me to explain my original approach in more depth, feel free to ask about that as well.
 
Hey. I'm trying to work though the math using the step model rather than the continuous model. I've gotten this far, but I don't know what to do about this square root:

h = hspeed
v = vspeed
s = speed
a = gravity / 2
b = X - x
c = a * b²
d = v / h


t = b / h
Y = y + (v + a) * t + a * t²
Y = y + (v + a) * b / h + a * (b / h)²
Y = y + b * d + a * b / h + c /h²
Y = y + b * d + a * b * √(1 + d²)/s + c * (1 + d²) / s²
Y = y + b * d + a * b * √(1 + d²)/s + c / s² + c * d² / s²
 

jo-thijs

Member
I didn't think that one through...
Maybe it isn't as easy to convert the code from the continuous model to the discrete model.

I'm going to think about this and I'll let you know if I find something.
 
putting that into wolfram alpha and asking it to solve for d gives 4 solutions, but each is like 5 miles long.

and then I ran out of computing time just trying to refresh the page.
 

jo-thijs

Member
No matter what I try, I also always end up at a fourth degree equation using the discrete model (and I've tried 4 different approaches by now).
Either because of asymmetries or because of lack of homogenity.

Perhaps solving that fourth degree equation I originally posted is the easiest precise way.
 
W

Wraithious

Guest
you could use some fairly simple sin cos pi maths conditions to do it, maybe set it up like:
button pressed event:
Code:
global.throw=1;
start_throw=1;
global.distance_half=distance_to_object(player2)*0.5;//radius of the curve
step event:
Code:
if(global.throw=1)
{
if(start_throw=1)
    {
    global.ball_pos=180;start_throw=0;//throw ball from left to right, to reverse set global.ball_pos to 0
    }
global.xpos=(ball.x+global.distance_half)+(global.distance_half*cos(global.ball_pos/(180/pi)));
global.ypos=(ball.y+global.distance_half)+(global.distance_half*sin(global.ball_pos/(180/pi)));
x=global.xpos;
y=global.ypos;
if global.ball_pos>0 ball_pos-=1;//throw ball left to right, use ball_pos+=1 to reverse
if(global.ball_pos<=0)
    {
    global.throw=0;
    speed=0;
    }
}
You may have to play with the start/end ball_pos to get what you want, especially if one player is on a hill or something, hope this helps
 

jo-thijs

Member
@Wraithious, no.
The code you gave already uses a wrong center for a circle in order for it to do what you want it to do.
Secondly, that's not the kind of curves constant gravity makes.
 
W

Wraithious

Guest
Sorry i just implemented a draw line script i had for a clock hand and tried to convert it for you on the fly so you just need to change around some stuff, but no it does not need to use any gravity at all.
 

lolslayer

Member
Ah, this, me and my brother did some advanced math to find a formula to do this, it was great, we had both versions which worked, his was the best though
 

jo-thijs

Member
Well, quite some people have said that already, but they always start from the wrong model, the continuous model.
The continuous model often isn't so bad, but it loses accuracy over time and the inaccuracy also scales with the gravity.

I have some old gamemaker8.1 files that do this perfectly, but they don't use a single formula,
they use the first method I described here together with Brent's method for findng roots.

I'm currently working on implementing my first suggestion, but finding quartic roots is proving to be a little more difficult than I thought.

Now, if you've got a single formula that works perfectly, I'm interested in taking a look at it.

EDIT:
nvm, my EDIT was wrong.
 
Last edited:

jo-thijs

Member
Well, I'm almost done doing it the way of my first method,
but I'm losing interest because my implementation is very inaccurate.
Its time calculation only has 2 correct digits after the dot.

On top of that, there is still an entire case that needs to be dealt with when solving quartics.

I'm starting to think it is a way better idea to just use Brent's method as I did in the past.

Here's a link to the gmz file containing my last attempt at it:
https://1drv.ms/u/s!AskjG-vgvdukhUOTGMvkKX75zKiW

And here's a link to my old files where I used Brent's method:
http://www.mediafire.com/download/aiz89s61l8f70i9/perfect_shooting.zip
 
K

KetoGames

Guest
Well, I'm almost done doing it the way of my first method,
but I'm losing interest because my implementation is very inaccurate.
Its time calculation only has 2 correct digits after the dot.

On top of that, there is still an entire case that needs to be dealt with when solving quartics.

I'm starting to think it is a way better idea to just use Brent's method as I did in the past.

Here's a link to the gmz file containing my last attempt at it:
https://1drv.ms/u/s!AskjG-vgvdukhUOTGMvkKX75zKiW

And here's a link to my old files where I used Brent's method:
http://www.mediafire.com/download/aiz89s61l8f70i9/perfect_shooting.zip

I tried downloading Brent's method (I suppose that it's the solution), however, there is no script for find_root. Did I make a mistake?
 
W

Wraithious

Guest
no.
The code you gave already uses a wrong center for a circle in order for it to do what you want it to do.
Secondly, that's not the kind of curves constant gravity makes.
This is what the original question asked:
one like this


Sorry about that last rushed code, I should of checked it but ok I had time n messed w my code, it now works perfectly, 2 players can pass a ball back and forth as many times as they want, not using gravity.

step event of ball:
Code:
if(instance_exists(player1)&& instance_exists(player2))
{if(global.throw=1)
{
if(global.start_throw=1)
    {
    global.ball_pos=180;
    global.distance_half=(distance_to_object(player2)*0.5)+sprite_width;
    global.pivotx=(player1.x+((player2.x-player1.x)*0.5));
    global.pivoty=(player1.y+((player2.y-player1.y)*0.5));
    global.start_throw=0;
    }
x=global.pivotx+(global.distance_half*cos(global.ball_pos/(180/pi)));
y=global.pivoty+(global.distance_half*sin(global.ball_pos/(180/pi)));
if global.ball_pos>0 global.ball_pos+=3;
if(global.ball_pos>=360+ceil((player2.y-player1.y)/2/pi))//EDITED you should use ceil to make it a whole number
    {
    global.throw=0;
    }
}
if(global.throw=2)
{
if(global.start_throw=1)
    {
    global.ball_pos=360;
    global.distance_half=(distance_to_object(player1)*0.5)+sprite_width;
    global.pivotx=(player2.x+((player1.x-player2.x)*0.5));
    global.pivoty=(player2.y+((player1.y-player2.y)*0.5));
    global.start_throw=0;
    }
x=global.pivotx+(global.distance_half*cos(global.ball_pos/(180/pi)));
y=global.pivoty+(global.distance_half*sin(global.ball_pos/(180/pi)));
if global.ball_pos>0 global.ball_pos-=3;
if(global.ball_pos<=180-ceil((player1.y-player2.y)/2/pi))//EDITED you should use ceil to make it a whole number
    {
    global.throw=0;
    }
}
}
place meeting event of object player1 meeting ball:
Code:
if(keyboard_check_pressed(vk_shift)){global.throw=1;global.start_throw=1;}
place meeting event of object player2 meeting ball:
Code:
if(keyboard_check_pressed(vk_shift)){global.throw=2;global.start_throw=1;}
 
Last edited by a moderator:

jo-thijs

Member
@KetoGames,
That's odd, the function is there when I load the package.

Although, I just tested the code and it doesn't seem to work in GM:S (it did in GM81).

Looking through the documentation, it is also very confusing,
because of stuff like me thinking "velocity" meant "acceleration".

I'll take a look at it as soon as I can, but that might take a while.
In the meanwhile, you can use one of the solutions that are based on the continuous model.
 
K

KetoGames

Guest
@KetoGames,
That's odd, the function is there when I load the package.

Although, I just tested the code and it doesn't seem to work in GM:S (it did in GM81).

Looking through the documentation, it is also very confusing,
because of stuff like me thinking "velocity" meant "acceleration".

I'll take a look at it as soon as I can, but that might take a while.
In the meanwhile, you can use one of the solutions that are based on the continuous model.
Awesome. I'm getting help from a real physicist that could certainly help this whole situation, and I'll let you know what I find.
 
K

KetoGames

Guest
@KetoGames,
That's odd, the function is there when I load the package.

Although, I just tested the code and it doesn't seem to work in GM:S (it did in GM81).

Looking through the documentation, it is also very confusing,
because of stuff like me thinking "velocity" meant "acceleration".

I'll take a look at it as soon as I can, but that might take a while.
In the meanwhile, you can use one of the solutions that are based on the continuous model.

My physics adviser came to the rescue!

I'm not sure if it is 100% accurate. However, this is what I've got:

speed = 25;
gravity = 0.1;

var time = (objTarget.x - x) / speed;
var vi = ((objTarget.y - y) - (0.5 * gravity * power(time, 2))) / time;

var dir = radtodeg(arctan(vi / speed));

direction = 360 - dir;

However, I do need a bit of help. It works well when your projectile is left of the target, but not right of it.
 

jo-thijs

Member
And once again, the continuous time model is being used, rather than the discrete time model!
I bet you didn't tell him how GameMaker actually works with speeds.

On top of that, your solution assumes the initial hspeed (which you call speed) is given, not the actual initial speed!

The only way you'll get an accurate solution without solving my quartic equation, is by solving for a different variable than time, but that would be just as complicated.

The reason your code only "works" for when the target is to the right of the projectile is because you're using arctan (which only returns angles to the right) instead of arctan2(vi, speed).
 
Top