Math and Testing: Unreal Engine 4 Arc Aim Indicator
I'm going to go over the basic process I went through for creating an aiming indicator for out UE4 project in my Game Production class. I had already created a projectile that flies in an arc (which was very easy to do, simply add a gravity acceleration vector times deltatime to its velocity and then its velocity times deltatime to its position every frame). My next task was to create an arc aim indicator that creates a path from the player, in an arc, through where the cursor is, and find the initial velocity for a projectile with a certain gravity so that it will follow that arc.
Rendering the arc was easy, just start at the player and then loop however many times, following the path of the projectile with one step every iteration and creating spline points, and then creating spline meshes between the spline points (for extra accuracy I did ten velocity/position calculations between every spline point so that it would make sure to go through the cursor without creating too many meshes). But the tricky part is finding the initial velocity so that the parabolic arc will pass through the cursor. First let me show you what I ended up with so you know what I was trying to do (apologies for the terrible cursor placeholder image, that was part of the develop branch at the time I took these screenshots):
So when I got this assignment, the project lead's advice was, to determine the arc (since you need 3 points to define a parabola and we only had 2), was to take the midpoint of the vector between the player and cursor, and set the peak of the parabola to be directly above that midpoint. The distance above the midpoint would be some percentage of the distance from the player to the cursor. Now, I'm pretty sure that algorithm would have worked, and would have been easy enough. But I had a better idea that I knew would work better, if I could figure it out: minimize the initial velocity magnitude. Find the initial velocity with the smallest magnitude that would start at the player and pass through the cursor position, given the gravitational acceleration.
The formula for such a parabola would be y = (vy/vx)*x + (g/(2*vx^2))*x^2, where vx and vy are the x and y components for the initial velocity, g is gravitational acceleration. x and y would be the x and y values of any point on the parabola, assuming the starting point is the origin, 0,0. So in order to solve this problem, I'd need to set x and y as constants, to the x and y displacement from the player to the cursor (cursor x - player x and cursor y - player y, respectively). Now, I'm going to be doing this math using Wolfram Alpha, since it's much faster and less accident-prone than doing it by hand.
First thing I need to do is change the variable names into ones Wolfram Alpha can understand. As far as I know, Wolfram doesn't support variable names with multiple letters, so from this point on, 'vx' is now 'a' and 'vy' is now 'b'. Our formula is
y = (b/a)*x - (g/(2*a^2))*x^2
So right now the variable I want to find is 'a', and the expression I want to minimize is 'sqrt(a^2 + b^2)' which is the magnitude of the initial velocity. Right off the bat we know that minimizing that is the same as minimizing 'a^2+b^2' so that is the equation we're going to use.
At this point we can either choose to try to find 'a' or 'b'. Looking at the initial equation I can see that finding b in terms of a (and the other variables, which at this point are all treated as constants) will be the easiest, since b only appears once. So we'll do that.
y = (b/a)*x - (g/(2*a^2))*x^2: solve for b
b = (a*y)/x+(g*x)/(2*a)
At this point all we need to do is find 'a'. So plug 'b' back into 'a^2+b^2' and get:
a^2 + ((a*y)/x+(g*x)/(2*a))^2
We need to minimize this expression, we need to find the a values for which the slope is 0 (to find local mins/maxes), so first step is to differentiate for 'a'. Our result:
a*((2*y^2)/x^2+2)-(g^2*x^2)/(2*a^3)
Now if we want the local min/maxes we need to take this equation and set it equal to 0.
a*((2*y^2)/x^2+2)-(g^2*x^2)/(2*a^3) = 0: solve for a
a = ±(sqrt(g)*x)/(sqrt(2)*(x^2+y^2)^(1/4))
Now we have a ± on the front, but we know that we want to shoot the projectile in the x direction of the cursor, so we know that a should be positive whenever 'x' is positive. Since the denominator of that equation always evaluates to positive, we can remove the ± from the front of the equation and know that it will choose the right direction.
So now we just use a blueprint math expression with that equation to find the initial velocity x (which, if you remember, is 'a'), and use another one with this equation
b = (a*y)/x+(g*x)/(2*a)
to find the initial velocity y ('b').
If our x offset is 0 then we just need to find the initial velocity y that peaks at y offset (assuming y is positive). Unless our Y offset is negative, then our y velocity should be 0,
b = sqrt(2*g*y) (when x distance == 0 and y offset > 0)
b = 0 (when x distance == 0 and y offset<= 0)
So that's the math done. But wait! When implemented, it doesn't work properly! What now?
First thing I do is use Desmos Graphing Calculator (https://www.desmos.com/calculator) to check the math. Plug in the formulas, and check if I get the expected results. It all checks out.
Next thing I do is plug some basic numbers in for the input to the math expression and see if I'm getting the same results as doing the same thing in Desmos. I don't. Double check the graph of that the Blueprint Math Expression is creating, and it looks right... so let's narrow it down some more.
Make another Blueprint Math Expression that's just the (sqrt(g)*x) part of the equation. OK, that generates the correct answer. Test the "(sqrt(2)*(x^2+y^2)^(1/4))" part. OK, that gives me the wrong answer. Narrowing it down bit by bit I discover the issue: the (1/4) is translating to integer division. Go back to the original math equation and change that to (1/4.0). It works now!
Alright, so we now have the arc indicator that generates the correct parabola. The next few days are spent on the code generating the spline and spline meshes, and making a separate algorithm for a linear indicator (which isn't quite as fun or interesting so I won't talk about it here.)
On the last day I have some extra time so I want to add a new feature: a minimum initial velocity. For that, all I have to do is find the initial velocity for which a^2+b^2 = u^2 (where 'a' and 'b' are the initial velocity x and y values, and 'u' is the desired velocity). This turns out to be a bigger hassle than anticipated...
So I solve for b in terms of a, this time in the second equation, and get
b = sqrt(u^2-a^2)
Plug it into our equation
y = (b/a)*x - (g/(2*a^2))*x^2
y = (sqrt(u^2-a^2)/a)*x - (g/(2*a^2))*x^2 solve for a
plug that into wolfram alpha:
Oh no.
First thought is "there's no way the solution is that complicated", second is "why are there so many variations", third is "If I choose the bottom one for simplicity, the equation never results on a negative even when x is negative so that can't be right."
But after some more testing with Desmos, it turns out that is the correct answer. But it's always positive, even when x is negative. Then I realized you could extract an x out of the main square root
OK so that problem's solved. More testing reveals that the function inside the inner square root
-g^2*x^2-2*g*u^2*y+u^4
results in a negative number when the point is outside of the "range" (so you can't reach it no matter which direction you aim the initial velocity in). So the first thing I do is check that, and if it's negative, do the minimization algorithm instead.
And after some optimizations to make sure I do it with as few calculations as possible, I'm done.