Calculating Projectile Velocity for Parabolic Arc

I’m having trouble finding the correct formula to calculate the velocity needed to propel an actor with a Projectile Movement Component from its start position to a known target.

The start angle is predetermined, and so is known.
The Target Coordinates are known.
The direction and distance can be established.

What I need to find is the Velocity.

I really need the projectile to travel in an arc towards the target.

I’ve made several attempts, and my most successful approach is below, which was adapted from a coding example in another forum.


			AShell* Proj = World->SpawnActor<AShell>(ProjectileBP, startPos, FRotator(newrot.Pitch, newrot.Yaw, newrot.Roll), SpawnParams);

			float distance = (startPos - TargetPosition).Size();
			float angleToPoint = atan2(TargetPosition.Z - startPos.Z, (TargetPosition - startPos).Size());
			float distanceFactor = 1 / 10000;
			float angleCorrection = (PI*0.18) * (distance * distanceFactor);
			float velocityX = cos(angleToPoint + angleCorrection) * 3000;
			float velocityZ = sin(angleToPoint + angleCorrection) * -1 * 3000 + 500;

			FVector Vel = FVector(velocityX, 0, velocityZ); 
			Proj->ProjectileComponent->SetVelocityInLocalSpace(Vel);

This gives a semi satisfactory result, but the problem is that this does not take the launch angle into account and almost always either falls short of the target or goes too long for distant and near targets respectively. It is not at all accurate.

I have a few cheat approaches that I may have to employ (such as a UCurve and distance to dictate the height based on the distance to the target), but I know there is a relatively simple formulaic solution.
Unfortunately, mathematics is not my strong point, so I turn to you for help.

You can separate launch speed (V) into horizontal and vertical components:
Vx = Vcos(Theta)
Vz = V
sin(Theta),
where Theta is the launch angle from the horizontal.

Then using s = ut + (1/2)at^2, with a = -g vertically and a = 0 horizontally, putting it together yields the following ugly result:

V = (Sx / cos(Theta)) * Sqrt(g / (2 * (Sx * tan(Theta) - Sz)))

where Sx and Sz are the horizontal and vertical displacements to the target (so Sz is negative if the target is below the firing point). For g, use World->GetGravityZ().

Once you have V, then the velocity vector is just Vcos(Theta), 0, Vsin(Theta) ].

Hopefully I got all that right, let me know if you have problems.
I’m working on a marketplace pack to cover all sorts of aiming calculations for trajectories, prediction, AI & aim assist, etc. Won’t be done for a while yet though.

I’m assuming you don’t mind that your projectile can launch with a variable speed?

If not that’s fine - otherwise it’s a Quadratic Equation.

It would appear a quadratic equation might well be what I need.

I’m currently reading through this:

I know this should be simple, but I’m afraid that I find it so slow to read algebraic formula not in code form.

If I have any success, then I will be sure to post it here!

Also; my previous reply doesn’t seem to have appeared. Does it need moderating?

Edit: Rewritten response to Kamrann.

So, below is my current implementation of your maths, but I appear to be missing something as the projectiles always fall short.

I’m logging the distance and height difference, and they appear to be correct.



			float Theta = 30.f; // launch angle

			FVector dir = TargetPosition - startPos;
			float Sz = dir.Z;// height difference
			dir.Z = 0;
			float Sx = dir.Size();// distance

			float V = (Sx / cos(Theta)) * FMath::Sqrt(Gravity / (2 * (Sx * tan(Theta) - Sz)));

			FVector VelocityOutput = FVector(V*cos(Theta), 0, V*sin(Theta));

			UE_LOG(LogTemp, Warning, TEXT("Distance =  %f"), Sx);
			UE_LOG(LogTemp, Warning, TEXT("Height  =  %f"), Sz);


Am I misinterpreting what you’re saying here?

Yeah I was assuming variable speed is expected, otherwise with fixed distance, launch angle and launch speed, there won’t be any solutions.

It’s possible you post is awaiting moderation since you’re new, but then I don’t see why subsequent posts would get through.

If code is simpler, this engine function is doing essentially the same thing as what I posted. Though it does more besides, so for your case you’d need to extract the relevant bits.

1 Like

I understand. The velocity of the projectile is what I want to return from this calculation, in order to apply it to the projectile, so no, the speed is not fixed. The angle however is fixed; primarily for the sake of simplicity.
I’m not sure how to adjust my above code to give me the correct velocity.

Not sure if I can embed gifs, but the following links demonstrate the results I’m getting:

Close Range:

Far Range:

Apologies for the poor quality!
The Close Range shots appear to be on target, but the Far Range ones still fall short.

In order to get this result, I’ve had to angle the projectile on spawn too. Without doing that , the projectiles were moved barely a few feet. Is this per chance a weight issue?

I’m also getting a 404 error from that link.

Thanks.

Didn’t see your edit previously.

Are you working in 2D only (X and Z)? I’d assumed so from the way you set Y to 0 in your first post, but the way you set dir.Z to 0 then take Size suggests maybe you’re in 3D?

Just to confirm, I believe your Gravity variable should be +9.8, not -9.8. And you should check that term isn’t negative before passing to Sqrt (if it is, it means no solution, though that shouldn’t happen for sensible inputs in this case).

Finally, what are the trig functions you’re using? C-runtime? In the code you pasted you’ve passed in the angle in degrees, nearly all trig functions expect input in radians (= degrees * PI / 180).

Yes, I am in 3D. I do indeed set Z to 0 in order to get the distance on the X, Y plane only.

I’m also using World()->GetGravityZ();

I’ve also just converted the launch angle to radians.


float Theta = (40 * PI / 180); // launch angle 

And below is how I’m actually spawning the projectile (note how I’m adding the pitch):


 
FRotator newrot = UKismetMathLibrary::FindLookAtRotation(startPos, TargetPosition);
AShell* Proj = World->SpawnActor<AShell>(ProjectileBP, startPos, FRotator(newrot.Pitch + 40, newrot.Yaw, newrot.Roll), SpawnParams);

Also, these are the sorts of values I’m getting from my debug logs:

So, as you can see, the projectile spawn is higher than the target position.
That shouldn’t be a problem, should it?

I’m getting a satisfactory arc now, but, for distant targets, the range seems too short to reach.

Edit:

Is it possible that the problem lies with the Movement Component (and my misunderstanding of its functionality) rather than the code?

Adjusting the speeds here yields different results, but I would have thought that the velocity would dictate the speed?

The formula assumes launch angle to be relative to the horizontal. So you shouldn’t be adding pitch to the lookat rotation. That would indeed cause the projectile to fall short if the target was below the launch position. Just use


FRotator(40, lookat.Yaw, 0)

Also, not sure what you’re doing with VelocityOutput, since the way that was coded assumed 2D. I think you need to be passing FVector(V, 0, 0) to the SetVelocityInLocalSpace method on the UProjectileMovementComponent, immediately after spawning your actor.

I hadn’t just though about the pitch I was using.
Made those two changes, but the results stills seem incorrect. The current lead seems to suggest that Initial speed dictates how far the projectiles go, and not the velocity set after spawning.

After adding the below line of code, setting velocity to 0, the projectile now simply falls to the ground, which suggest the results of the calculation was never being applied at all!
Going to have to do some investigating…



AShell* Proj = World->SpawnActor<AShell>(ProjectileBP, startPos, FRotator(40, newrot.Yaw, 0), SpawnParams);
Proj->ProjectileComponent->SetVelocityInLocalSpace(FVector(0,0,0));

Edit:
So I’m getting this from printing part of the output velocity vector

VelocityOutput shouldn’t exist anymore, at least not in the form it was when you last posted the code.

Maybe just post everything as you have it now.

Thanks for your patience btw :wink:

I’ve narrowed down where that log is coming from. Bear with me…

So, printing V produces “-nan(ind)”


 float V = (Sx / cos(Theta)) * FMath::Sqrt(Gravity / (2 * (Sx * tan(Theta) - Sz))); 

I attempted to break down and log sections of the equation and these are the results:


UE_LOG(LogTemp, Warning, TEXT("V =  %f"), (Gravity / (2 * (Sx * tan(Theta) - Sz))) ); 

This produces


UE_LOG(LogTemp, Warning, TEXT("V =  %f"), FMath::Sqrt(Gravity / (2 * (Sx * tan(Theta) - Sz))));

So, when finding the Square root, I get the -nan(ind) message.

This suggests that something is wrong with the calculation of V, or one of the variables which are fed into it.

As for the current code block, it is as follows:



			FVector TargetPosition = currentTargetRegion->GetRandomPointInVolume();

			FRotator newrot = UKismetMathLibrary::FindLookAtRotation(startPos, TargetPosition);
			AShell* Proj = World->SpawnActor<AShell>(ProjectileBP, startPos, FRotator(40, newrot.Yaw, 0), SpawnParams);
			Proj->ProjectileComponent->SetVelocityInLocalSpace(FVector(0,0,0));

			const float Gravity = World->GetGravityZ();
			float Theta = (40 * PI / 180); // launch angle

			FVector dir = TargetPosition - startPos; //direction
			float Sz = dir.Z;// height difference
			dir.Z = 0; // remove hight from direction
			float Sx = dir.Size();// distance

			float V = (Sx / cos(Theta)) * FMath::Sqrt(Gravity / (2 * (Sx * tan(Theta) - Sz)));

			FVector VelocityOutput = FVector(V*cos(Theta), 0, V*sin(Theta));

			Proj->ProjectileComponent->SetVelocityInLocalSpace(VelocityOutput);


Try this:



FVector TargetPosition = currentTargetRegion->GetRandomPointInVolume();
FRotator newrot = UKismetMathLibrary::FindLookAtRotation(startPos, TargetPosition);

const float Gravity = World->GetGravityZ();
float Theta = (40 * PI / 180); // launch angle

FVector dir = TargetPosition - startPos; //direction
float Sz = dir.Z;// height difference
dir.Z = 0; // remove hight from direction
float Sx = dir.Size();// distance

float Discriminant = Gravity / (2 * (Sx * tan(Theta) - Sz));
if(Discriminant > 0)
{
  float V = (Sx / cos(Theta)) * FMath::Sqrt(Discriminant);

  FVector LocalVelocity = FVector(V, 0, 0);

  AShell* Proj = World->SpawnActor<AShell>(ProjectileBP, startPos, FRotator(40, newrot.Yaw, 0), SpawnParams);
  Proj->ProjectileComponent->SetVelocityInLocalSpace(LocalVelocity);
}


If the discriminant term is negative, it means there are no possible solutions for the given inputs, so you just shouldn’t fire. For example, if you tried to fire at something that is at 60 degrees up with a launch angle of only 40 degrees.
If you’re finding the discriminant is negative when you think your inputs are valid, then log the value of Gravity to check it’s not negative. If it is, negate it.

You were correct about the gravity, and i had spotted that problem after rereading an earlier post of yours.

So, I only ever fire at targets that are lower than the origin’s Z axis.

I have to leave the computer for a while now, but I will take a look at your code sample when I return.

Currently, the velocity does appear to be applied (and since fixing the gravity, now prints), but seems to be far lower than it should be.

I really thought this would have been far simpler that I’m making it. =P

Edit: RESULT!

I need to run a few more checks when return later before I confirm, but it now appears to be working! For your sake, and Mine I hope this is true.

Here is my final, working code - for the benefit of anyone else whom it may help.



	FActorSpawnParameters SpawnParams;
	SpawnParams.Owner = this;
	SpawnParams.Instigator = Instigator;

	FVector TargetPosition = currentTargetRegion->GetRandomPointInVolume();

	const FRotator newrot = UKismetMathLibrary::FindLookAtRotation(startPos, TargetPosition);
	AShell* Proj = World->SpawnActor<AShell>(ProjectileBP, startPos, FRotator(0, newrot.Yaw, 0), SpawnParams);
	Proj->ProjectileComponent->SetVelocityInLocalSpace(FVector(0, 0, 0));

	//  == Fixed Angle Projectile Velocity Calculation == 

	const float Gravity = World->GetGravityZ() * -1; // Gravity. (Must be a positive value)
	//const float Gravity = 980.f; // This is the same as the line above (unless your project settings have been changed)
	const float Theta = (40 * PI / 180); // Launch angle in radians (40 being the launch angle in degrees)

	FVector dir = TargetPosition - startPos; //direction
	float Sz = dir.Z; // Height difference
	dir.Z = 0; // Remove hight from direction
	float Sx = dir.Size(); // Distance

	const float V = (Sx / cos(Theta)) * FMath::Sqrt((Gravity * 1) / (2 * (Sx * tan(Theta) - Sz)));
	FVector VelocityOutput = FVector(V*cos(Theta), 0, V*sin(Theta));
			
	Proj->ProjectileComponent->SetVelocityInLocalSpace(VelocityOutput);


So, all along it was a series of red herrings, and the original code/ algorithm was largely correct.

One thing you may notice, however, that the value of gravity in UE4 is -980. I found I had to move the decimal point as originally I was using 9.81f as gravity, and you will note that the value also must be positive.

Again, thank you for all of the help!

Hello!

It seems I’m having the same issue as Greywacke, however I’ve coded the projectile using blueprints and not C++, and am therefore wondering how I could possibly implement something like the solution above but by using Blueprint nodes instead of C++ code?
My flying arrow right now hits the target (from any distance) when I have “Projectile gravity scale” on my arrows “Projectile Movement” turned off (on 0).
Doing it that way the arrow however loses any sort of arc and travels just like a linetrace, in a straight line (which is not desirable for an arrow fired from a bow).
When turning ON the gravity (giving it a value above 0), the projectile gets its arc motion back, but falls short (or goes under the target if the target is in the air) when the player firing the arrow is too far away or/and the speed of the arrow is too low.
Just modifying the projectile speed on the “Projectile movement” doesn’t seem to solve the issue, as the higher the speed of the projectile = almost no arc motion.
This is how my flying arrow and enemy targeting BP looks right now, and not sure how to change it so that the projectile keeps its arc, but also always goes far enough to hit the target location…

Thanks for any help!

Hey, thanks a lot, I have been struggling with it recently, mostly with UGameplayStatics:SuggestProjectileVelocity() functions.