Here's a simple, scalable solution. There are several prerequisites to be clarified:
- For the oblique throwing motion, it can be decomposed into the uniform linear motion of the x-axis and the vertical upward throwing motion of the y-axis.
- The shadow is moving in a straight line with uniform speed.
- The object is directly above the shadow, and the height conforms to the quadratic function, and the height is 0 at the start and end.
So we can draw the shadow first, and then draw the object through the position of the shadow, which is simpler.
private const float MOVE_DURATION = 2f; private float moveTimeLeft = 0f; private Vector3 from; private Vector3 to; private void Update() { ... if(moveTimeLeft<=0) return; moveTimeLeft -= Time.deltaTime; var moveRate = 1 - moveTimeLeft/MOVE_DURATION; var shadowPosition = Vector3.Lerp(from, to, moveRate); _shadow.transform.position = shadowPosition; } public void StartMove(Vector3 fromSet, Vector3 toSet) { from = fromSet; to = toSet; moveTimeLeft = MOVE_DURATION; }
Here we assume that the moving time is fixed, and the shadow moves to the end at a constant speed according to this time.
Then let's deal with the height of the object, here we introduce a "normalized" quadratic function: $$ y=-\left(2x-1\right)^{2}+1 $$
It looks like this:

When x represents the rate of movement, we can use it to calculate the height of the object.
private const float MOVE_DURATION = 2f; private const float HIGHEST_HEIGHT= 2f; private float moveTimeLeft = 0f; private Vector3 from; private Vector3 to; private void Update() { ... if(moveTimeLeft<=0) return; moveTimeLeft -= Time.deltaTime; var moveRate = 1 - moveTimeLeft/MOVE_DURATION; var shadowPosition = Vector3.Lerp(from, to, moveRate); _shadow.transform.position = shadowPosition; var height = − Mathf.Pow(2*moveRate−1,2) + 1; _flare.transform.position = shadowPosition + Vector3.up * height * HIGHEST_HEIGHT; } public void StartMove(Vector3 fromSet, Vector3 toSet) { from = fromSet; to = toSet; moveTimeLeft = MOVE_DURATION; }
Because the game is not the real world, you can draw the trajectory according to the effect you want. Here are some examples:
- The shadow has a constant speed:
private const float shadowSpeed = 2f; private float moveTimeMax = 0f; private float moveTimeLeft = 0f; public void StartMove(Vector3 fromSet, Vector3 toSet) { from = fromSet; to = toSet; var distance = Vector3.Distance(from,to); moveTimeMax = distance / shadowSpeed; moveTimeLeft = moveTimeMax; }
- The maximum height of the object varies with the launch distance:
private float highestHeight = 0f; private void Update() { ... _flare.transform.position = shadowPosition + Vector3.up * height * highestHeight; ... } public void StartMove(Vector3 fromSet, Vector3 toSet) { ... highestHeight = distance/2; ... }
- Objects are always launched at a 45 degree angle:
public void StartMove(Vector3 fromSet, Vector3 toSet) { ... highestHeight = distance/4; ... }
In general, if we give up the accurate calculation of physics such as gravity acceleration and velocity, things will be much simpler. We can directly give a motion curve and scale it.