I’m working on a game that involves throwing a disc at moving targets. You pick the targets, but the disc is supposed to autonomously adjust its course to strike them at a specific “due” time.
So we have the disc’s current position and velocity; the target position; and the target time. Note that we don’t care what velocity the disc has when it hits, as long as it reaches the target at the appointed time.
This would be an easy problem if I didn’t care how the disc moves — it could just turn directly towards the target, divide the distance by the time, and move in a straight line at the resulting speed. But that looks terrible. I want the disc to swoop around in graceful arcs.
Any ideas how to accomplish that?
Currently I’m just calculating what velocity would get me to the target at the right time, and using MoveTowards to shift my current velocity towards the needed velocity a little bit each frame. It works, but it’s more abrupt and less swoopy than I would like.
Perhaps the solution lies in Bezier curves, but I just haven’t been able to put the pieces together. Anybody have a clever idea?
Is the target moving in a deterministic manner? In other words, at the point of launching the disc (LP), the exact point of contact (CP) can immediately be determined?
If so, then one option could be to imagine that the line LP->CP lies along the ground (LG-CG). You can calculate a trajectory as if throwing a ball from LG at a given angle under a given gravity to CG.
In its simplest form, you could lerp X along LG-CG over the known time giving the Y on that path (at point XY). Then transpose XY onto the path LP-CP.
If that basically works, then play around with the angle and gravity values to see what looks best.
Perhaps combine a lerp with a quadratic curve?
A is the position the disc is launched from.
B is a position halfway between where the disc was launched from and where the target was when the disc is launched
C is the current position of the target, updated as it moves
The projectiles current position is a lerp along the curve, hits the target at t = 1;
The initial trajectory will be a straight line and as the target moves it will become a curve to track the targets current position. The further the target moves from its position at time of launch the stronger the curve will become to meet its new position.
Yes, in fact we can assume the target isn’t moving at all. We know exactly where it’s going to be at the moment we want to hit it, so I think of that as the target point in time/space.
But in that calculation, you get to set the initial velocity of the ball however you like. As noted in the OP, that makes the problem trivial, but doesn’t give me the result I want, which is a smooth change from the disc’s current velocity (whatever that might be) to a course that intercepts the target.
In that case, maybe the Bezier solution is worth a look. I would recommend this video by Sebastian Lague as a good starting point. The visuals he uses explain it quite neatly I think. As far as I can tell, the placement of point “B” in the cubic curve (the curve utilizing 4 points) should not be too difficult to calculate (just the disc’s current velocity extrapolated forward some distance) but I’m not so sure about point “C”.
EDIT : Oh well, I probably should of read that first. Nevermind the answer then.
Here’s an attempt, supposing I understood you correctly.
A) Get the midpoint between the initial projectile position and the target position.
B) Get the midpoint between the initial projectile position and where the projectile would be if it had navigated linearly instead of moving towards the target.
C) Get the midpoint between A and B
D) C - A
This will go from initial to target linearly:
position = Vector3.Lerp(initialPosition, targetPosition, t);
This SHOULD add a bit of weight based on the initial velocity in the shape of a sine wave
position += Vector3.LerpUnclamped(Vector3.zero, D, Mathf.Sin(Mathf.PI * t));
If this works, super untested, you should be able to change the weight by changing C (e.g. more weight if t = 0.75f, less if t = 0.25f).
edit : More super untested stuff! Honestly I’m not sure this is going to give the desired results, but just in case : You can pass t through this instead of Mathf.Sin(Mathf.PI * t)) for the added position.
float FUBAR(float t)
{
t = t * 2f;
if (t < 1f)
{
t = QuadraticEaseOut(t);
}
else
{
t = t - 1f;
t = QuadraticEaseIn(t);
t = 1f - t;
}
return t;
}
float QuadraticEaseIn(float t)
{
return t * t;
}
float QuadraticEaseOut(float t)
{
return -(t * (t - 2));
}