- Home /
How can I make a missile home to an enemy in arc fashion? (Unity 4.3 2D)
I am building a 2D game and i've managed to make homing missiles with the following code:
Vector2 destination = player.transform.position - transform.position; lastPos = destination; float angle = Mathf.Atan2(destination.y, destination.x) * Mathf.Rad2Deg; transform.rotation = Quaternion.AngleAxis(angle, Vector3.forward);
destination.Normalize (); transform.position += new Vector3 (destination.x, destination.y, 0) speed Time.deltaTime;
I've made it so that the rocket simply travels in a straight line towards the player. Can anyone guide me in the right direction to make the rocket travel in an arc towards the player instead of a straight line.
Is this a ho$$anonymous$$g missile or do you target a fixed position? Do you want the arc to always be up or are you looking for something more organic?
The position is dynamic as the player can continue moving as the missile homes. I'm looking for something fairly simple, so even if it arced only as the enemy shot the rocket and then straightens out would be perfectly fine.
Answer by Hamdullahshah · Feb 26, 2014 at 04:34 PM
Use a bezier curve of two point
private Vector3 CalculateBezierPoint (float t, Vector3 startPosition, Vector3 endPosition, Vector3 controlPoint) {
float u = 1 - t;
float uu = u * u;
Vector3 point = uu * startPosition;
point += 2 * u * t * controlPoint;
point += t * t * endPosition;
return point;
}
Call it from update something like
this.transform.position = CalculateBezierPoint(currentDuration/duration,startPos,endPos,cp);
currentDuration += Time.deltaTime;
where "duration" is total time.
More info on wikipedia for bezier curves: http://en.wikipedia.org/wiki/File:Bezier_2_big.gif
another clear explanation of bezier curves: http://devmag.org.za/2011/04/05/bzier-curves-a-tutorial/
Answer by poncho · Feb 26, 2014 at 04:35 PM
I suggest, using iTween, where you set your waypoints, the easiest way to define this waypoints instead of using a formula, just add 4, the first, your origin, second and third, at 1 third between origin and destiny but with the y above them depending on how your desired arc is, and the fourth your destiny, itween WILL interpolate the movements, after defining that you could play a little with the type of movement, as linear, cuadratic, spring, etc, the code for i tween is something like this
public Transform[] waypoints;
public float m_speed = 1.0f;
public GameObject player1;
private int i = 0;
private bool moving = false;
public void Update() {
if (!moving && i < waypoints.Length - 1) {
i++;
moving = true;
iTween.MoveTo(player1,iTween.Hash("position",waypoints[i],"speed",m_speed,"easetype","linear", "oncompletetarget",gameObject, "oncomplete", "Done"));
}
}
public void Done() {
moving = false;
}
Answer by robertbu · Feb 26, 2014 at 05:02 PM
And another solution to add to the mix. Use the Sin() function to calculate a loft for the missile over the distance traveled. Take the script below and attach to a sphere in the scene. Click the mouse to shoot the sphere to that position:
#pragma strict
var traveling = false;
function Update() {
if (!traveling && Input.GetMouseButtonDown(0)) {
var pos = Input.mousePosition;
pos.z = 10.0;
pos = Camera.main.ScreenToWorldPoint(pos);
ArcToTarget(pos, 0.35, 1.0);
}
}
function ArcToTarget(targetPos : Vector3, arcFraction : float, time : float) {
traveling = true;
var startPos = transform.position;
var timer = 0.0;
var arcDist = (targetPos - startPos).magnitude * arcFraction;
while (timer < time) {
var pos = Vector3.Lerp(startPos, targetPos, timer / time);
pos.y += Mathf.Sin(Mathf.PI * timer / time) * arcDist;
transform.position = pos;
timer += Time.deltaTime;
yield;
}
transform.position = targetPos;
traveling = false;
}
This is giving me a close effect. I'm sure with a bit more tweaking I can get what i'm looking for.
Answer by Thorizo · Oct 03, 2018 at 09:26 PM
Let's assume we have 3 points: A (start), B (middle) and C (end). We linearly interpolate over time between A and B, as well as B and C. Meanwhile, we interpolate between the result of the two. The code presented below should work perfectly. Simply call QuadraticCurve in the Update() method. Subsequently, increment the time as we want to go from 0 to 1: time += Time.deltaTime;
private Vector3 LinearInterpolate(Vector3 start, Vector3 end, float time)
{
return start + (end - start) * time;
}
private Vector3 QuadraticCurve(Vector3 start, Vector3 middle, Vector3 end, float time)
{
Vector3 line1 = LinearInterpolate(start, middle, time);
Vector3 line2 = LinearInterpolate(middle, end, time);
return LinearInterpolate(line1, line2, time);
}
private void Update()
{
transform.position = QuadraticCurve(startPoint, middlePoint, endPoint,
duration);
durtation += Time.deltaTime;
}
While this is true it's actually more complicated / complex that way First of all reinventing the wheel for a lerp is pretty pointless. You can use Vector3.Lerp ins$$anonymous$$d of your "LinearInterpolate".
Calling lerp 3 times is rather inefficient. For a quadratic bezier you should just use a bezier implementation like "Hamdullahshah" posted in his answer. So your answer is the same as Hamdullahshah's answer just less efficient. Also you may want to rethink about your variable names. line1 and line2 do not store lines. They are points. Also "duration" is not a duration. It could be called "time", "progress" or "fraction". Duration describes a usually constant amount of time usually in seconds. The "t" / "time" value of a lerp or a bezier curve is just the interpolation value
Answer by terresquall · Feb 10, 2020 at 05:42 AM
You can use a sine curve for that. Attach the script below to your projectile, and use Projectile.Spawn()
to spawn a projectile that will home in on its target. You can set arcFactor
to adjust the height and speed
to adjust how fast it travels. For an explanation of the math involved, you can look at this article where the script was taken from: https://blog.terresquall.com/2019/11/coding-projectiles-for-your-tower-defense-game-part-2/
public class Projectile : MonoBehaviour {
public float speed = 8.5f; // Speed of projectile.
public float radius = 1f; // Collision radius.
float radiusSq; // Radius squared; optimisation.
Transform target; // Who we are homing at.
Vector3 currentPosition; // Store the current position we are at.
float distanceTravelled; // Record the distance travelled.
public float arcFactor = 0.5f; // Higher number means bigger arc.
Vector3 origin; // To store where the projectile first spawned.
void OnEnable() {
// Pre-compute the value.
radiusSq = radius * radius;
origin = currentPosition = transform.position;
}
void Update() {
// If there is no target, destroy itself and end execution.
if ( !target ) {
Destroy(gameObject);
// Write your own code to spawn an explosion / splat effect.
return; // Stops executing this function.
}
// Move ourselves towards the target at every frame.
Vector3 direction = target.position - currentPosition;
currentPosition += direction.normalized * speed * Time.deltaTime;
distanceTravelled += speed * Time.deltaTime; // Record the distance we are travelling.
// Set our position to <currentPosition>, and add a height offset to it.
float totalDistance = Vector3.Distance(origin, target.position);
float heightOffset = arcFactor * totalDistance * Mathf.Sin( distanceTravelled * Mathf.PI / totalDistance );
transform.position = currentPosition + new Vector3( 0, 0, heightOffset );
// Destroy the projectile if it is close to the target.
if ( direction.sqrMagnitude < radiusSq ) {
Destroy(gameObject);
// Write your own code to spawn an explosion / splat effect.
// Write your own code to deal damage to the .
}
}
// So that other scripts can use Projectile.Spawn to spawn a projectile.
public static Projectile Spawn(GameObject prefab, Vector3 position, Quaternion rotation, Transform target) {
// Spawn a GameObject based on a prefab, and returns its Projectile component.
GameObject go = Instantiate(prefab, position, rotation);
Projectile p = go.GetComponent<Projectile>();
// Rightfully, we should throw an error here instead of fixing the error for the user.
if(!p) p = go.AddComponent<Projectile>();
// Set the projectile's target, so that it can work.
p.target = target;
return p;
}
}