Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

projectile object arching movement

Discussion in '2D' started by piggybank1974, Oct 21, 2019.

  1. piggybank1974

    piggybank1974

    Joined:
    Dec 15, 2015
    Posts:
    621
    What I'm trying to do is move a GameObject from start to end position in a form of arc, I currently have this working with Vector3.Lerp but unfortunately as the gameobject gets closer to the objects that it fires from it gets slower when it fired, I hope I've explained that correctly.

    Code (CSharp):
    1.   private void Update()
    2.   {
    3.    if (mIsMoving == true)
    4.      {
    5.       mIncrementor += 0.02f;
    6.       Vector3 mPosition = Vector3.Lerp(mStartPosition, mEndPosition, mIncrementor);
    7.       mPosition.y += mForceUpwards * Mathf.Sin(Mathf.Clamp01(mIncrementor) * Mathf.PI);
    8.       transform.position = mPosition;
    9.  
    10.       if (transform.position == mEndPosition)
    11.         {
    12.          mIsMoving = false;
    13.          mIncrementor = 0;
    14.  
    15.          Collider[] mColliders = Physics.OverlapSphere(this.transform.position, DamageRadius, MonsterMask);
    16.          if (mColliders != null && mColliders.Length > 0)
    17.          {
    18.           for (int i = 0; i < mColliders.Length; i++)
    19.           {
    20.            MonsterComponent mMonster = mColliders[i].GetComponent<MonsterComponent>();
    21.            if (mMonster != null)
    22.             mMonster.UpdateHealth(this.Damage * (Vector3.Distance(mMonster.Transform.position, this.transform.position) / this.Damage));
    23.           }
    24.          }
    25.  
    26.          Destroy(this.gameObject);
    27.         }
    28.      }
    29.   }
    30.  
    I tried to use MoveTowards but the arching just did not work anymore, does anybody and Idea for a solution for this problem.

    Cheers
     
  2. Ted_Wikman

    Ted_Wikman

    Unity Technologies

    Joined:
    Oct 7, 2019
    Posts:
    906
    Hello Piggybank1974,

    The reason why your projectiles gets slower and slower the closer you are to the end is because of your use of lerp. Every frame, you move your interpolation forward 0.02 units. If your starting point and end point are close, the amount you move every frame is less than if your starting point and end point are further away.

    Example:
    Code (CSharp):
    1. Vector3 result = Vector3.Lerp(new Vector3(0f, 0f, 0f), new Vector3(10f, 0f, 0f), 0.02f);
    2. // x result - 0.2f
    3.  
    4. Vector3 result = Vector3.Lerp(new Vector3(0f, 0f, 0f), new Vector3(1f, 0f, 0f), 0.02f);
    5. // x result - 0.02
    If your goal is to move the projectile at a constant speed, my advice would be to move the projectile with a constant amount every frame, without the distance being a factor in the speed.

    Good luck!
     
  3. MisterSkitz

    MisterSkitz

    Joined:
    Sep 2, 2015
    Posts:
    833
    My question is, could he adjust the mass and gravity on the rigidbody component to maintain consistent speed? I'm thinking adjusting them through code using Time.fixedDeltatime or something along them lines. Would that actually work?
     
  4. Ledesma099

    Ledesma099

    Joined:
    May 15, 2018
    Posts:
    26
    I can't help you with .Lerp () because it's a complex thing to understand and I didn't see it very well.
    But I can help with what you are trying to do. First, are you moving the GameObject through a Rigidbody? Second, is the GameObject a projectile? Correct me if I'm wrong.

    If this is a projectile. Then you can apply the physics projectile motion formula.

    The formula says this:

    Note: T is time.
    X is the distance between the two points horizontally.
    Y it's the height.
    A is the acceleration of gravity, in this case it will be -9.81. (If you look at Unity Physics, you will see this value. For more information here).
    Pow means "elevated to the power of".

    X = V0x * Cos(angle) * T

    Y = V0y * Sin(angle) - 1/2 * A * Pow(T, 2)

    After seeing this, maybe you noticed that you have X, Y and in T you can give a value. So, in this case, you need V0x and V0y for the initial momentum. But first you need to know the angle generated by the two points. Then we need to apply the trigonometric functions to get the angles.

    If you see vectors you can do this:

    Code (CSharp):
    1.  
    2. float Acos(float c) => Mathf.Acos(c) * 180 / Mathf.PI; // function to get the arc-cosine in degrees
    3. float Asin(float s) => Mathf.Asin(s) * 180 / Mathf.PI; // function to get the arc-sine in degrees
    4.  
    5. Vector2 distSide = target - origin; // Subtract target by origin to get side distance
    6. float h = distSide.magnitude; // Get the magnitude of distSide. This will be the hypotenuse.
    7.  
    8. // Angle by the Cos method
    9.  
    10. float cos = dist.x / h // adjacent over the hypotenuse
    11. float resultCos = cos >= 0 ? Mathf.Round(Acos(cos)) : Mathf.Round(Acos(-cos)); // Make a check because it will always return positive grades
    12.  
    13. // ---
    14.  
    15. // Angle by Sin method
    16.  
    17. float sin = dist.y / h // opposite over the hypotenuse
    18. float resultSin = Mathf.Round(Asin(sin));
    19.  
    20. // ---
    21.  
    22.  
    Now we need two values that we don't know. And these are V0x and V0y. Then we can make an equation and the result will be this:

    V0x = X / Cos(angle) * T

    V0y = Y / Sin(angle) * T + 1/2 * A * T

    So now we need to know these values. And if we pass this to the script, it will be something like this:

    Code (CSharp):
    1.  
    2.  
    3. // Formulas
    4. float Vx(float x, float t) => x / (Mathf.Cos(resultCos / 180 * Mathf.PI) * t);
    5. // I do a Mathf.Abs to the Mathf.Sin because sometimes this returns a negative value.
    6. // And also apply .Abs to Physics2D.gravity.y because this is negative
    7. float Vy(float y, float t) => y / (Mathf.Abs(Mathf.Sin(resultSin / 180 * Mathf.PI)) * t) + .5f * Mathf.Abs(Physics2D.gravity.y) * t;
    8. // ----
    9.  
    10. float hY = target.y - origin.y; // Get the hight
    11. float dX = target.x - origin.x; // Get the distance on the X axis
    12.        
    13. Vector2 v0 = new Vector2(dX, 0).normalized; // Assign a new normailized Vector2 in v0.
    14. v0 *= Vx(Mathf.Abs(dX)); // Multiply the Vx by the v0. Use Mathf.Abs to convert negative values to positive.
    15. v0.y = Vy(hY); // Assign in Y the Vy
    16.  
    17. // Assign v0 to rigidbody
    18.  
    19. rb2D.velocity = v0;
    20.  
    21.  
    You can even remove t from the formulas. Since this value affects how the parabola is formed at the time of the flight. But if you want to generate a normal parable. So just having the angles is enough, because they apply a normal time without needing t.

    I hope this helps.
     
    Last edited: Oct 22, 2019
  5. MisterSkitz

    MisterSkitz

    Joined:
    Sep 2, 2015
    Posts:
    833
    This is truly a magnificent work of art! Well done!
    If no one else appreciates it, I do!
    I needed to see this :)
     
    Ledesma099 likes this.
  6. Ledesma099

    Ledesma099

    Joined:
    May 15, 2018
    Posts:
    26
    Thanks!!
     
  7. Ledesma099

    Ledesma099

    Joined:
    May 15, 2018
    Posts:
    26
    I'm looking to improve these functions a bit. Then I made these changes:

    For trigonometric functions it's just this.
    Code (CSharp):
    1.  
    2. // Removed Acos and Asin functions.
    3.  
    4. Vector2 distSide = target - origin; // Subtract target by origin to get side distance
    5. float h = distSide.magnitude; // Get the magnitude of distSide. This will be the hypotenuse.
    6.  
    7. // Angle in radians by the Cos method
    8.  
    9. float cos = dist.x / h; // adjacent over the hypotenuse
    10. float resultCos = cos >= 0 ? cos : -cos; // Make a check because it will always return positive radians
    11.  
    12. // ---
    13.  
    14. // Angle in radians by Sin method
    15.  
    16. float sin = dist.y / h; // opposite over the hypotenuse
    17. float resultSin = sin;
    18.  
    19. // ---
    20.  
    So, for formulas, just change this:

    Code (CSharp):
    1.  
    2. // Formulas
    3.  
    4. float Vx(float x, float t) => x / resultCos * t; // When using the previous method. I noticed that Mathf.Cos gives the same result as dist.x / h. So replace the Mathf.Cos only with the resultCos.
    5. // I use Mathf.Abs for the resultSin because sometimes this returns negative values.
    6. // And also apply .Abs to Physics2D.gravity.y because this is negative.
    7. float Vy(float y, float t) => y / (Mathf.Abs(resultSin) * t) + .5f * Mathf.Abs(Physics2D.gravity.y) * t; // Here the same as Vx.
    8.  
    9. // ----
    10. float hY = target.y - origin.y; // Get the hight
    11. float dX = target.x - origin.x; // Get the distance on the X axis
    12.  
    13. Vector2 v0 = new Vector2(dX, 0).normalized; // Assign a new normailized Vector2 in v0.
    14. v0 *= Vx(Mathf.Abs(dX)); // Multiply the Vx by the v0. Use Mathf.Abs to convert negative values to positive.
    15. v0.y = Vy(hY); // Assign in Y the Vy
    16.  
    17. // Assign v0 to rigidbody
    18. rb2D.velocity = v0;
    19.  
    For more flexibility, you can create extension methods for this and separate the code. Like this:

    Code (CSharp):
    1.  
    2. using UnityEngine;
    3.  
    4. namespace ExtensionMethods
    5. {
    6.      public static class MathfP
    7.      {
    8.           /// <summary>
    9.           /// Returns the angle of the vector in radians. Through Cos method generated by the origin and the target.
    10.           /// </summary>
    11.           /// <param name="origin">The point in 2D space where the vector start.</param>
    12.           /// <param name="target">The point in 2D space where the vector ends.</param>
    13.           /// <returns><seealso cref="float"/> angle in radians.</returns>
    14.           public static float AngleCosRadian(Vector2 origin, Vector2 target)
    15.           {
    16.                Vector2 distSide = target - origin;
    17.                float h = distSide.magnitude;
    18.                float cos = distSide.x / h;
    19.                float result = cos >= 0 ? cos : -cos;
    20.                return result;
    21.           }
    22.      
    23.      
    24.           /// <summary>
    25.           /// Returns the angle of the vector in radians. Through Sin method generated by the origin and the target.
    26.           /// </summary>
    27.           /// <param name="origin">The point in 2D space where the vector start.</param>
    28.           /// <param name="target">The point in 2D space where the vector ends.</param>
    29.           /// <returns><seealso cref="float"/> angle in radians.</returns>
    30.           public static float AngleSinRadian(Vector2 origin, Vector2 target)
    31.           {
    32.                Vector2 distSide = target - origin;
    33.                float h = distSide.magnitude;
    34.                float sin= distSide.y / h;
    35.                return sin;
    36.           }
    37.      
    38.      
    39.           /// <summary>
    40.           /// Generates a projectile motion between origin and target.
    41.           /// </summary>
    42.           /// <param name="origin">The point in 2D space where the projectile motion start.</param>
    43.           /// <param name="target">The point in 2D space where the projectile motion ends.</param>
    44.           /// <returns><seealso cref="Vector2"/> with the initial momentum.</returns>
    45.           public static Vector2 ProjectileMotion(Vector2 origin, Vector2 target)
    46.           {
    47.                 // As I said in the previous post. You can remove t from the formulas
    48.                 float Vx(float x) => x / AngleCosRadian(origin, target);
    49.                 float Vy(float y) => y / Mathf.Abs(AngleSinRadian(origin, target)) + .5f * Mathf.Abs(Physics2D.gravity.y);
    50.        
    51.                 float hY = target.y - origin.y;
    52.                 float dX = target.x - origin.x;
    53.        
    54.                 Vector2 v0 = new Vector2(dX, 0).normalized;
    55.                 v0 *= Vx(Mathf.Abs(dX));
    56.                 v0.y = Vy(hY);
    57.  
    58.                 return v0;
    59.            }
    60.  
    61.            // In this method you can add t in the parameters
    62.            /// <summary>
    63.            /// Generates a projectile motion between origin and target.
    64.            /// </summary>
    65.            /// <param name="origin">The point in 2D space where the projectile motion start.</param>
    66.            /// <param name="target">The point in 2D space where the projectile motion ends.</param>
    67.            /// <param name="t">The time of flight of a projectile motion.</param>
    68.            /// <returns><seealso cref="Vector2"/> with the initial momentum.</returns>
    69.            public static Vector2 ProjectileMotion(this Vector2 origin, Vector2 target, float t)
    70.            {
    71.                 float Vx(float x) => x / AngleCosRadian(origin, target) * t;
    72.                 float Vy(float y) => y / (Mathf.Abs(AngleSinRadian(origin, target)) * t) + .5f * Mathf.Abs(Physics2D.gravity.y) * t;
    73.  
    74.                 float hY = target.y - origin.y;
    75.                 float dX = target.x - origin.x;
    76.  
    77.                 Vector2 v0 = new Vector2(dX, 0).normalized;
    78.                 v0 *= Vx(Mathf.Abs(dX));
    79.                 v0.y = Vy(hY);
    80.  
    81.                 return v0;
    82.            }
    83.      }
    84. }
    85.  
    And if you want to use these methods. Just do this:

    Code (CSharp):
    1.  
    2. using UnityEngine;
    3. using ExtensionMethods; // We access the namespace and the whole class that has.
    4.  
    5. public class Projectile : MonoBehaviour
    6. {
    7.      [SerializeField, Tooltip("The target position")]
    8.      private Transform target;
    9.  
    10.      private Rigidbody2D rb2D;
    11.  
    12.      void Awake()
    13.      {
    14.           rb2D = GetComponent<Rigidbody2D>();
    15.      }
    16.  
    17.      void Update()
    18.      {
    19.           rb2D.velocity = MathfP.ProjectileMotion(rb2D.position, target.position); // I call the ExtensionMethods class with its method.
    20.      }
    21. }
    22.  
    For more information about Extension methods here.
    For more information about namespaces here
     
    Last edited: Oct 23, 2019
    MisterSkitz likes this.
  8. piggybank1974

    piggybank1974

    Joined:
    Dec 15, 2015
    Posts:
    621
    @Ted_Wikman
    @MisterSkitz
    @Ledesma099

    Sorry for the delay in replying guys, My emails where down last night, I figured it was the timing on the lerp,

    I thought I tried that Ted but it does not work, and I think I know why it's because of the arcing I'm putting on the gameobject, for example the mForceUpwards is based on the angle of the turret the bomb is firing out off, if the turret is pointing forward no mForceUpwards will be applied but if it' lets say 30% it will be 3F, this makes it arc I also tried movetowards but again could not get it working correctly they just went you and down like I ping pong if I remember.

    What do you think of this solution, the bomb can travel a max distance of 9 unity units and a minimum of 2 units so we know that 0.02F is for units could a adjust that for less?

    then as the fire is less it should be the same move the same speed, I hope I've explained that correctly.

    I know I'm wanting to do a Parabola Curve, just could not work this out, I do have an example I wrote of this for the Wiki pages, but that's if both start/end are on the ground.

    Ledesma099 yes I do have a rigidbody but as you can see I'm not moving with it, the collider is set to trigger not collision.

    Ledesma099 these are not extension methods, I'm sure the first parameter should be this?

    Ledesma099 I could not see a height for the arcing, I'm I missing something

    Thanks for all your help guys, I'm sure we can figure it out, I'll create a little unity package if that helps to see it better, I don't mind :).
     
  9. piggybank1974

    piggybank1974

    Joined:
    Dec 15, 2015
    Posts:
    621
    I found an angle to reach method I wrote a while back

    Code (CSharp):
    1.   /// <summary>
    2.   ///  Calculates the angle to hit target
    3.   /// </summary>
    4.   /// <param name="distance">The distance from Source(Enemy) to Target(hero) e.g (Source-Target).magnitude</param>
    5.   /// <param name="pvelocity">The projectiles velocity e.g (Speed)</param>
    6.   /// <param name="angle">The angle that is been calculated</param>
    7.   /// <returns>True if angle can be calculated or false if it cannot, the result is in angle</returns>
    8.   private Boolean AngleOfReach(float distance, Single pvelocity, out Single angle)
    9.   {
    10.    angle = 0.5F * (Mathf.Asin((-Physics.gravity.y * distance) / (pvelocity * pvelocity)) * Mathf.Rad2Deg);
    11.    if (float.IsNaN(angle) == true)
    12.    {
    13.     angle = 0;
    14.     return false;
    15.    }
    16.  
    17.    return true;
    18.   }
    19.  
    But I'm sure this will only work if it's level?
     
  10. Ledesma099

    Ledesma099

    Joined:
    May 15, 2018
    Posts:
    26
    Yes you're right. But I create a MathfP class and call its methods as Mathf of unity.

    What specific part?
     
  11. piggybank1974

    piggybank1974

    Joined:
    Dec 15, 2015
    Posts:
    621
    @Iron-Warrior
    @Ledesma099
    @Ted_Wikman
    @MisterSkitz

    Hi all not to give up on this task of getting this thing in my head, I'm in my middle 40's so I did not tackle this sort of thing when I was younger.

    This weekend I put together a little unity package with several Turrets

    BombTower
    Is my version although it has extra things it contains, it's all where this started.

    Tower 2
    I got this idea from a YouTube video
    this does work but you have no way I can see to increase the speed.

    Tower 3
    I got this from Iron-Warrior's project he did, thanks for this, although it's end result was above the target "probably because of the Yoffsset", I did figure out a little solution that every iteration "point" it moves the next one down giving the end result of the ball hitting the ground/floor.


    Tower 4
    I started implementing Ledesma099 code but only got so far, as I wanted to release this.


    Like I said I've not done equations so if I explain what I understand maybe I can be pointed out, I get confused when they swap things.

    I know these values

    Distance/Displacement
    Acceleration/Speed

    I don't know these values

    Angle to fire the ball from the barrel.
    V1y at halfway for time?


    VO = Target position/velocity
    V1 = Source Position/velocity

    Heading = Target - Source
    Distance = Heading.Magnitude;

    if we take a Ball fired from the turret

    V0x = 0 has it has no initial velocity, position x?
    V0y = the value of the fire position from the turret

    V1x = distance it can travel.
    v1y = at the midpoint/half way then it will start falling again.
     

    Attached Files: