- Home /
How do I make a projectile arc and always hit a moving target?
Hi all I'm new to Unity as well as programming in C# (about 3 weeks in). I've been searching for over a week now and trying different things that I can think of to no avail. I'm making a tower defense style game and I'm stuck on getting the projectile for my catapult to work how I intent. The idea is I have a catapult positioned about 20 units up in the Y direction on top of a building and the enemy is moving straight towards the building at ground level (Y=0). The enemy is moving at a constant speed with a simple transform.translate for now. When the enemy hits the collider I have setup that means the enemy is in range and the catapult begins to fire on it. I want the projectile to make a nice arc and hit the enemy every time no matter the speed the enemy is traveling at because different enemies will move at different speeds. The best example I can give is the catapults in Plants Vs. Zombies on the roof level. At first I thought adding a force and letting unity's physics take care of it would make a nice arc for the projectile, but I found this code here on unity answers that deals with velocity and thought it would be what I was looking for... (modified for C# and the way my project is setup)
void Start()
{
rigidbody.velocity = BallisticVel(myTarget);
}
Vector3 BallisticVel(Transform target)
{
dir = target.position - transform.position; // get target direction
h = dir.y; // get height difference
dir.y = 0; // retain only the horizontal direction
dist = dir.magnitude ; // get horizontal distance
dir.y = dist; // set elevation to 45 degrees
dist += h; // correct for different heights
vel = Mathf.Sqrt(dist * Physics.gravity.magnitude);
return vel * dir.normalized; // returns Vector3 velocity
}
So what happens is the catapult will instantiate the projectile at a location of an empty game object when the enemy collides with the catapult trigger. I attached this code to my projectile and it has no errors and is super close, however given that my catapult is a higher elevation then the enemy and the enemy is moving constantly towards the building the projectile will only hit the enemy if the enemy is moving very slow (.5). Anything with a speed of 1 or higher the projectile will overshoot the enemy and land where the enemy was at the time the projectile was created and not where its going to be. I figure somehow I have to incorporate the speed the enemy is moving into my code, and I passed the speed of the enemy to the projectile but I'm not sure how to incorporate that or if that is even the best way to go about this. Any options would be greatly appreciated. Sorry the post is long I wanted to make what I was trying to accomplish as clear as possible.
Answer by Fornoreason1000 · May 02, 2013 at 07:40 AM
simple use Vector3.RotateToawrds.
http://docs.unity3d.com/Documentation/ScriptReference/Vector3.RotateTowards.html
if your target is moving the projectile will rotate towards it as it moves. to make it "Arc" you will need to make a parabolic path towards it which you have basically already done. when the project tile reaches the "Apex" use RotateToawrds to seek the opponent. try this and if it works you may not need the enemy speed.
if it turns out you do need it, just instantiate a empty game object based on the enemies dir ,speed and distance. how to do this can vary if you have bi-level projection or a horizontal projection. you basically need to calculate the time to hit where the enemy is now, then calculate where the enemy will be after that time has passed by using his direction , speed and his position this will be where the empty game object will go. from that you can calculate the projection that will hit the enemy even if he moves using that gameobject as a target predication. if your enemy is constantly changing directions, he can actually "Dodge" the projectile if he moves out of the direction. since you projectile tile will be pre-calculated. if this is the case you might need to do an calculation that updates regularly to keep up with the enemy
your basically predicting where the enemy will be after that set amount of time. pretty cool huh?. let me know if you have more questions
Thank you for the response! So I playing around with the Vector3.RotateTowards but it seemed to be acting similar to a LookAt. I added the code to my update...
public Transform myTarget;
public float speed;
void Update() {
Vector3 targetDir = myTarget.position - transform.position;
float step = speed * Time.deltaTime;
Vector3 newDir = Vector3.RotateTowards(transform.forward, targetDir, step, 0.0F);
Debug.DrawRay(transform.position, newDir, Color.red);
transform.rotation = Quaternion.LookRotation(newDir);
}
The projectile would launch when the BallisticVel function was called and I could see the little red DrawRay line pointing at myTarget. However, the projectile would still overshoot the target. So I tried adding a move towards...
transform.position = Vector3.$$anonymous$$oveTowards(transform.position, myTarget.position, step);
but that didn't change either. So the weird thing is if I have the speed/step set low like 10 or 20 then it still overshoots and then once it hits the ground starts traveling towards the enemy because of the movetowards and rotatetowards. But if I increase the speed to say 50 it loses a lot of the arch the velocity gives it and shoots its quick and straight to the target, hitting the target. However it being that fast just looks real bad even though its hitting the enemy but still overshoots when its slow. I'm at work right now I just want to go home and play with it more wondering if there is something I'm just missing.
Anyways I have a question about the second part of your response about creating the empty game object. Sorry I'm new but if i understand what your saying. I might need some sort of formula to figure out the targets speed distance and direction so I know where the enemy is GOING to be after a certain amount of time. Then once I have that create an empty game object at that location and make the empty game object myTarget ins$$anonymous$$d of the enemy itself that way when the projectile lands it will land where the empty game object WAS and the enemy currently is?
I'm not sure about a formula I could use for that but it does give me an idea that might work. If I have an empty game object as a child in the heiarchy to the enemy at a set distance in front of him and his path then make the empty object my target ins$$anonymous$$d. The enemy moves at a constant speed in one direction unless slowed so the slowing part is my concern about doing it this way. However, its a cartoony game so I'm not looking for realistic trajectories or physics. I just want the projectile to arc and always hit the target really. Thanks!
So I played around with my code and think I have something I can live with. I had to chance my code for the arc from what I had and ended up using the Vector3.RotateTowards you suggested as well as $$anonymous$$oveTowards to get it to work better. for the moving targets.
Answer by JoeStrout · Sep 01, 2016 at 07:09 PM
I also wrote a blog post on this topic. It shows code that adds an arc (using the parabolic equation) to otherwise straight motion. It also shows how to make your sprite point in the direction it's going (in case it's something like an arrow, not round like a cannonball).
http://luminaryapps.com/blog/arcing-projectiles-in-unity/
The script as shown keeps a fixed target point (targetPos). If you wanted to track a moving target, then you would just replace that Vector3 with a Transform (let's call it target), and then use target.position instead of targetPos. That should do it.
In your script, you need to define "nextPos". So: Vector3 nextPos = new Vector3(nextX, baseY + arc, transform.position.z);
nextPos is defined (it's the last line in the first block of lines in Update). But I see the link is broken — I'll edit and fix it now.
Answer by jepidoptera · Jul 16, 2017 at 01:22 AM
I wrote a function that targets a projectile using physics and recursion to predict where the target will be by the time the projectile arrives. The recursion is necessary (it seems to me) because in order to find the target's projected location, you need to know how long it will take the projectile to get there - but to know that, you need to know the projected location of your target... I solved this by doing first one calculation, then the other, using each result to refine the next, and found that after ten iterations, it's almost always close enough. If you need more accuracy, just adjust "accuracy" parameter to add more iterations to the loop. None of the calculations involved are very expensive.
This function uses plain physics: what's fired is a rock, not a rocket. The velocity is set (albeit with inhuman precision) from the moment it's launched. It doesn't "cheat" and steer towards its target in midair. Therefore, it's possible for the target to dodge if by changing course while the projectile is en route. This may or may not be what you want, but it is realistic.
float projectileSpeed = 100; // or whatever
float accuracy = 10;
Vector3 BallisticVelocity(Vector3 source, Vector3 target, Vector3 targetVelocity)
{
// use a few iterations of this recursive function to zero in on
// where the target will be, when the projectile gets there
Vector3 horiz = new Vector3(target.x - source.x, 0, target.z - source.z);
float t = horiz.magnitude / projectileSpeed;
for (int a = 0; a < accuracy; a++)
{
horiz = new Vector3(target.x + targetVelocity.x * t - source.x, 0, target.z + targetVelocity.z * t - source.z);
t = horiz.magnitude / projectileSpeed;
}
// after t seconds, the cannonball will reach the horizontal location of the target -
// so all we have to do is make sure its 'y' coordinate zeros out right there
float gravityY = (.5f * Physics.gravity * t * t).y;
// now we've calculated how much the projectile will fall during that time
// so let's add a 'y' component to the velocity that will take care of the rest
float yComponent = (target.y - source.y - gravityY) / t + targetVelocity.y;
horiz = horiz.normalized * projectileSpeed;
return new Vector3(horiz.x, yComponent, horiz.z);
}
strong text
Answer by terresquall · Apr 15, 2021 at 07:36 AM
I've wrote an article on this too, and it comes with visuals to describe the math behind it. There are 2 parts to the article, the first part describes how to make a projectile always track a target (just like in RTS games): https://blog.terresquall.com/2019/11/coding-projectiles-for-your-tower-defense-game/
The second part describes how to get a projectile to arc, using a sin curve. If you're not interested in the math, you can also just skip all of that and copy the code at the end of the article. It's a usable component with settable attributes which you can just plug and play: https://blog.terresquall.com/2019/11/coding-projectiles-for-your-tower-defense-game-part-2/
Your answer
Follow this Question
Related Questions
make a ball hit a target 0 Answers
Aim preview for projectile trajectory 1 Answer
Precise projectile launching in 3d world 0 Answers
How do I arc a rigidbody so it lands on a target? 2 Answers
Make enemy Flash when Hit 3 Answers