Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Equation not working when center not Vector3.zero

Discussion in 'Scripting' started by danield, Oct 14, 2016.

  1. danield

    danield

    Joined:
    Sep 30, 2014
    Posts:
    40
    This equation is meant to move a character around a point in a circular path, and toward and away from the point in a straight path. This works fine when the center is Vector3.zero but when I have it at, for example, (2, 0, 0) the character moves in a strange way. Since I had help with the math, I'm not sure if it's a problem with the math. As far as I can tell, there shouldn't be a problem, but I do have trouble understanding line 69 and 70.
    Why xMove uses " - " and zMove uses " + " specifically. Any help would be very much appreciated.

    Code (CSharp):
    1.     public static Vector3 center = Vector3.zero;
    2.     public static float distanceA = 4f;
    3.     public static float distanceB = 8f;
    4.     public static float distanceC = 12f;
    5.  
    6.     private Transform playerRot;
    7.  
    8.     private float targetDist;
    9.     private Vector2 targetAngle;
    10.     private Transform targetEnemy = null;
    11.     public float fwdSpeed = 5.0f;
    12.     public float backSpeed = 2.5f;
    13.     public float sideSpeed = 2.0f;
    14.     //[HideInInspector]
    15.     public Vector2 Moving = Vector2.zero;
    16.  
    17.     void Start()
    18.     {
    19.         playerRot = transform.GetChild(0);
    20.         targetDist = Vector3.Distance(transform.position, center);
    21.         targetAngle = new Vector2(0f, -1f);  
    22.     }
    23.  
    24.     void Update()
    25.     {
    26.         Vector3 targetPos = (targetEnemy) ? targetEnemy.position : center;
    27.         Vector2 playerPos = new Vector2((transform.position.x - targetPos.x), (transform.position.z - targetPos.z));
    28.         float curDist = playerPos.magnitude;
    29.         float moveDist = curDist;
    30.         float moveSide = 0f;
    31.         float radians = 0f;
    32.         // moving toward or away from center point unti at target distance
    33.         if(Mathf.Approximately(curDist, targetDist) == false)
    34.         {
    35.             if(Moving.y > 0f)
    36.             {
    37.                 moveDist = Mathf.Max(curDist - ((Moving.y * fwdSpeed) * Time.deltaTime), targetDist);
    38.             }
    39.             if(Moving.y < 0f)
    40.             {
    41.                 moveDist = Mathf.Min(curDist - ((Moving.y * backSpeed) * Time.deltaTime), targetDist);
    42.             }
    43.         }
    44.         else if(Moving.y != 0f)
    45.         {
    46.             Moving.y = 0f;
    47.         }
    48.         // moving around center point until at target angle
    49.         if(Mathf.Approximately(Vector2.Dot(playerPos.normalized, targetAngle), 1f) == false)
    50.         {
    51.             moveSide = (Moving.x * sideSpeed * Time.deltaTime) / (2f * Mathf.PI * moveDist);
    52.             if(Moving.x > 0f)
    53.             {
    54.                 radians = Mathf.Min((moveSide * 360f), Vector2.Angle(playerPos.normalized, targetAngle)) * Mathf.Deg2Rad;
    55.                 playerRot.localRotation = Quaternion.Euler(0f, 90f, 0f);
    56.             }
    57.             if(Moving.x < 0f)
    58.             {
    59.                 radians = Mathf.Max((moveSide * 360f), -Vector2.Angle(playerPos.normalized, targetAngle)) * Mathf.Deg2Rad;
    60.                 playerRot.localRotation = Quaternion.Euler(0f, -90f, 0f);
    61.             }
    62.         }
    63.         else if (Moving.x != 0f)
    64.         {
    65.             Moving.x = 0f;
    66.             playerRot.localRotation = Quaternion.Euler(Vector3.zero);
    67.         }
    68.         // rotate vector
    69.         float xMove = (playerPos.x * Mathf.Cos(radians)) - (playerPos.y * Mathf.Sin(radians));
    70.         float zMove = (playerPos.y * Mathf.Cos(radians)) + (playerPos.x * Mathf.Sin(radians));
    71.         // create vector
    72.         Vector3 movePos = new Vector3(xMove + targetPos.x, 0f, zMove + targetPos.z);
    73.         // set vector distance
    74.         movePos = movePos.normalized * moveDist;
    75.         movePos.y = transform.position.y;
    76.         Debug.Log("Dist: " + moveDist.ToString() + " Side: " + moveSide.ToString());
    77.         Debug.DrawRay(targetPos, new Vector3(targetAngle.x, 0f, targetAngle.y) * targetDist, Color.blue, 0.0f, false);
    78.         Debug.DrawLine(transform.position, movePos, Color.black, 10f, false);
    79.  
    80.         transform.position = movePos;
    81.         transform.LookAt(targetPos, Vector3.up);
    82.     }
    83.  
    84.     public void MoveFwd() // call to move player closer to point
    85.     {
    86.         if(Moving.sqrMagnitude == 0f)
    87.         {
    88.             if(Vector3.Distance(transform.position, center) > distanceC) // if player is further than dist C
    89.             {
    90.                 targetDist = distanceC;
    91.                 Moving.y = 1;
    92.             }
    93.             if(Mathf.Approximately(Vector3.Distance(transform.position, center), distanceC))
    94.             {
    95.                 targetDist = distanceB;
    96.                 Moving.y = 1f;
    97.             }
    98.             if(Mathf.Approximately(Vector3.Distance(transform.position, center), distanceB))
    99.             {
    100.                 targetDist = distanceA;
    101.                 Moving.y = 1f;
    102.             }
    103.         }
    104.     }
    105.  
    106.     public void MoveBack() // call to move player further from point
    107.     {
    108.         if(Moving.sqrMagnitude == 0f)
    109.         {
    110.             if(Vector3.Distance(transform.position, center) < distanceA) // if player is closer than dist A
    111.             {
    112.                 targetDist = distanceA;
    113.                 Moving.y = -1f;
    114.             }
    115.             if(Mathf.Approximately(Vector3.Distance(transform.position, center), distanceA))
    116.             {
    117.                 targetDist = distanceB;
    118.                 Moving.y = -1f;
    119.             }
    120.             if(Mathf.Approximately(Vector3.Distance(transform.position, center), distanceB))
    121.             {
    122.                 targetDist = distanceC;
    123.                 Moving.y = -1f;
    124.             }
    125.         }
    126.     }
    127.  
    128.     public void MoveSide(float direction) // call to move player right/left around point
    129.     {
    130.         if(Moving.sqrMagnitude == 0f)
    131.         {
    132.             if(direction != 0f)
    133.             {
    134.                 targetAngle = Quaternion.Euler(0f, 0f, 90f * direction) * targetAngle;
    135.                 Moving.x = direction;
    136.             }
    137.         }
    138.     }
    PS: I have an external input class that calls MoveFwd() or MoveBack() when the up or down arrow key is pressed with Input.GetButtonDown && Input.GetAxisRaw. Same for MoveSide(direction) and the left and right arrow keys.
     
  2. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,840
    I don't understand all that either. It seems to be working much harder than you really need to.

    If I understand correctly what you want, you should only have basically three fields in your class: a center (Vector2), an angle (float — not a Vector2!), and a radius (also a float). Your various Move methods should simply adjust angle and radius, at whatever speed you think these should be adjusted. And then update the position like this:

    Code (CSharp):
    1. float radians = angle * Mathf.Deg2Rad;
    2. float x = center.x + radius * Cos(angle * radians);
    3. float y = center.y + radius * Sin(angle * radians);
    4. transform.position = new Vector3(x, y, transform.position.z);
    And that's pretty much it. You can put the above code in Update, and then no matter where you adjust angle, radius, and center, your object will move appropriately.

    Now, if you want to also make your sprite face in the direction it's moving or something, that will require a little more... for example, you might just do it in MoveForward, MoveBack, etc. You already know which way you're moving at those points.
     
  3. danield

    danield

    Joined:
    Sep 30, 2014
    Posts:
    40
    I don't believe that will work for my purpose. With the code I provided, the character maintains a constant speed when traveling around the circle. The further away from the center, the longer it takes to move to angle X. With this equation, it would not matter how far from the center the character is. Here is the original thread.

    Here is a compact code you can attach to an object to demonstrate what I'm trying to achieve.
    Set distance element 0 to 20.0 and the last element to 2.0 and fill with values in between if desired. Move the object to (0, 0, 20)
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class MoveTest : MonoBehaviour {
    5.     public static Vector3 center = Vector3.zero;
    6.  
    7.  
    8.     public float fwdSpeed = 5.0f;
    9.     public float backSpeed = 2.5f;
    10.     public float sideSpeed = 2.0f;
    11.     public float[] distances;
    12.     private int distID = 0;
    13.     private float targetDist = 2f;
    14.     private Vector2 targetAngle = new Vector2(0f, 1f);
    15.     private Transform targetEnemy = null;
    16.     //[HideInInspector]
    17.     public Vector2 Moving = Vector2.zero;
    18.  
    19.     // Use this for initialization
    20.     void Start () {
    21.    
    22.     }
    23.    
    24.     // Update is called once per frame
    25.     void Update()
    26.     {
    27.         Vector3 targetPos = (targetEnemy) ? targetEnemy.position : center;
    28.         Vector2 playerPos = new Vector2((transform.position.x - targetPos.x), (transform.position.z - targetPos.z));
    29.         float curDist = playerPos.magnitude;
    30.         float moveDist = curDist;
    31.         float moveSide = 0f;
    32.         float radians = 0f;
    33.         // moving toward or away from center point unti at target distance
    34.         if(Mathf.Approximately(curDist, targetDist) == false)
    35.         {
    36.             if(Moving.y > 0f)
    37.             {
    38.                 moveDist = Mathf.Max(curDist - ((Moving.y * fwdSpeed) * Time.deltaTime), targetDist);
    39.             }
    40.             if(Moving.y < 0f)
    41.             {
    42.                 moveDist = Mathf.Min(curDist - ((Moving.y * backSpeed) * Time.deltaTime), targetDist);
    43.             }
    44.         }
    45.         else if(Moving.y != 0f)
    46.         {
    47.             Moving.y = 0f;
    48.         }
    49.         // moving around center point until at target angle
    50.         if(Mathf.Approximately(Vector2.Dot(playerPos.normalized, targetAngle), 1f) == false)
    51.         {
    52.             moveSide = (Moving.x * sideSpeed * Time.deltaTime) / (2f * Mathf.PI * moveDist);
    53.             if(Moving.x > 0f)
    54.             {
    55.                 radians = Mathf.Min((moveSide * 360f), Vector2.Angle(playerPos.normalized, targetAngle)) * Mathf.Deg2Rad;
    56.             }
    57.             if(Moving.x < 0f)
    58.             {
    59.                 radians = Mathf.Max((moveSide * 360f), -Vector2.Angle(playerPos.normalized, targetAngle)) * Mathf.Deg2Rad;
    60.             }
    61.         }
    62.         else if (Moving.x != 0f)
    63.         {
    64.             Moving.x = 0f;
    65.         }
    66.         // rotate vector
    67.         float xMove = (playerPos.x * Mathf.Cos(radians)) - (playerPos.y * Mathf.Sin(radians));
    68.         float zMove = (playerPos.y * Mathf.Cos(radians)) + (playerPos.x * Mathf.Sin(radians));
    69.         // create vector
    70.         Vector3 movePos = new Vector3(xMove + targetPos.x, 0f, zMove + targetPos.z);
    71.         // set vector distance
    72.         movePos = movePos.normalized * moveDist;
    73.         movePos.y = transform.position.y;
    74.         Debug.Log("Dist: " + moveDist.ToString() + " Side: " + moveSide.ToString());
    75.         Debug.DrawRay(targetPos, new Vector3(targetAngle.x, 0f, targetAngle.y) * targetDist, Color.blue, 0.0f, false);
    76.         Debug.DrawLine(transform.position, movePos, Color.black, 10f, false);
    77.  
    78.         transform.position = movePos;
    79.         transform.LookAt(targetPos, Vector3.up);
    80.  
    81.         // input
    82.         if(Input.GetAxisRaw("Vertical") != 0f && Input.GetButtonDown("Vertical"))
    83.         {
    84.             if(Moving.sqrMagnitude == 0f)
    85.             {
    86.                 distID = (distances.Length > 0) ? (int) Mathf.Max(Mathf.Min(distID + Input.GetAxisRaw("Vertical"), distances.Length - 1), 0f) : 0;
    87.                 targetDist = (distances.Length > 0) ? distances[distID] : 2f;
    88.                 Moving.y = Input.GetAxisRaw("Vertical");
    89.             }
    90.         }
    91.  
    92.         if(Input.GetAxisRaw("Horizontal") != 0f && Input.GetButtonDown("Horizontal"))
    93.         {
    94.             if(Moving.sqrMagnitude == 0f)
    95.             {
    96.                 targetAngle = Quaternion.Euler(0f, 0f, 90f * Input.GetAxisRaw("Horizontal")) * targetAngle;
    97.                 Moving.x = Input.GetAxisRaw("Horizontal");
    98.             }
    99.         }
    100.     }
    101. }
     
    Last edited: Oct 16, 2016
  4. takatok

    takatok

    Joined:
    Aug 18, 2016
    Posts:
    1,496
    @JoeStrout : I believe the Vector2 Moving was just a way to combine two different input checks. Moving.X was holding if the left/right was pushed and Moving.y was holding if the up/down was pushed. Its seems a little obfuscated.

    So just so I understand what you want I"m going to spell out it:
    (All my co-ordinates are in the XZ plane. I'm ignoring Y
    • You have a target enemy that you rotate around. If the enemy is null you rotate around Origin(0,0)
    • You want to move at a constant speed. You move at a constant velocity regardless of size of the circle. So it will twice as long to go around a circle of radius 2 versus a circle of radius 1
    • You rotate around your target at a fixed distance
    • You can be assigned an new target by some outside Object.
    • The Up/Down keys can increase or decrease the distance you rotate around your Object
    • The Left/Right keys can increase or decrease your speed you move around the Object
    The code below will accomplish what I listed above: The idea is is accomplished by using @JoeStrout formulas above, but we will adjust our angle/per time using arcLength formula. We keep arcLength constant, and figure out how much of an angle that takes up.

    Some Notes:
    • I renamed a lot of variables to be more descriptive of what they are
    • I refactored the Input, so an outside class doesn't have to tell us the Input we just check it ourselves and act accordingly. You can easily refactor my CheckUserInput code into various MoveFwd, MoveBack functions if you want
    • I use object's rotation to keep track of where we are in the circle. This lets us always face the enemy, but we do have to do a few tricks to make the circle correctly. Its commented in the code
    • Finally the code may seem long, but I stuck in a lot of comments to explain it. If you rip those out, its half the size :)
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class RotateHandler : MonoBehaviour {
    5.  
    6.     // this is in units/second.  If you set it = 1f
    7.     // it will take 2PI seconds to go around a size 1 circle
    8.     public float circlingSpeed = 5f;
    9.     // how far from the target should we be
    10.     public float targetEnemyDistance=5f;
    11.     //How fast should we change speeds if they hold down the keys
    12.     public float circlingChangeSpeed=3f;
    13.     // How fast should we move in and out if they hold down the keys
    14.     public float distanceChangeSpeed = 3f;
    15.  
    16.  
    17.  
    18.     private Transform targetEnemyTransform = null;
    19.     private float anglePerSecond;
    20.     private Vector3 targetEnemyPosition;
    21.     bool isApproaching;
    22.  
    23.     enum VariableAdjust
    24.     {
    25.         decreasing,
    26.         increasing,
    27.         constant
    28.     }
    29.  
    30.     // Variables to control increasing/decreasing
    31.     // speed and distance from target based on User Input
    32.     VariableAdjust distanceState;
    33.     VariableAdjust speedState;
    34.  
    35.     void Awake()
    36.     {
    37.         // initialize all our variables
    38.         targetEnemyPosition = Vector3.zero;
    39.         anglePerSecond = GetAngleFromArcLength();
    40.         distanceState = VariableAdjust.constant;
    41.         isApproaching = false;
    42.  
    43.         // our InitialState may not be correct
    44.         // Make sure we move towards our current target
    45.         StartCoroutine(ApproachEnemy());
    46.     }
    47.  
    48.     public void SetEnemy(Transform transform)
    49.     {
    50.         targetEnemyTransform = transform;
    51.         StartCoroutine(ApproachEnemy());
    52.     }
    53.  
    54.     void Update()
    55.     {
    56.         // if we are approaching a new Enemy skip updating
    57.         // till we are actually circling it
    58.         if (isApproaching)
    59.             return;
    60.  
    61.         CheckUserInput();
    62.  
    63.         // Now adust the speed and distance based on Input
    64.         if (speedState != VariableAdjust.constant)
    65.             AdjustSpeed();
    66.         if (distanceState != VariableAdjust.constant)
    67.             AdjustDistance();
    68.  
    69.          // Now circle our target
    70.         float angleAdjust = anglePerSecond * Time.deltaTime;
    71.         Vector3 eulerAngles = transform.rotation.eulerAngles;
    72.         eulerAngles.y += angleAdjust;
    73.         transform.position = GetPositionFromAngle(eulerAngles.y);
    74.         transform.rotation = Quaternion.Euler(eulerAngles);
    75.  
    76.     }
    77.  
    78.     // You can refactor this back into MoveFwd, MoveBack, etc
    79.     // But I think this object should handle its own input
    80.     void CheckUserInput()
    81.     {
    82.         // Order matters below.  Whichever key is checked first
    83.         // is the only one we process if you press up/down at the same time
    84.  
    85.         // Pushing up means move closer
    86.         if (Input.GetKey(KeyCode.UpArrow))
    87.             distanceState = VariableAdjust.decreasing;
    88.         else if (Input.GetKey(KeyCode.DownArrow))
    89.             distanceState = VariableAdjust.increasing;
    90.         else
    91.             distanceState = VariableAdjust.constant;
    92.  
    93.  
    94.         // Right arrow means speed up
    95.         if (Input.GetKey(KeyCode.RightArrow))
    96.             speedState = VariableAdjust.increasing;
    97.         else if (Input.GetKey(KeyCode.LeftArrow))
    98.             speedState = VariableAdjust.decreasing;
    99.         else
    100.             speedState = VariableAdjust.constant;
    101.     }
    102.  
    103.     void AdjustSpeed()
    104.     {
    105.         if (speedState == VariableAdjust.increasing)
    106.             circlingSpeed += circlingChangeSpeed * Time.deltaTime;
    107.         else if (speedState == VariableAdjust.decreasing)
    108.             circlingSpeed -= circlingChangeSpeed * Time.deltaTime;
    109.  
    110.         anglePerSecond = GetAngleFromArcLength();
    111.     }
    112.  
    113.     void AdjustDistance()
    114.     {
    115.         //assume we are getting closer
    116.         float maxDelta = distanceChangeSpeed * Time.deltaTime;
    117.         // flip it if we are not
    118.         if (distanceState == VariableAdjust.increasing)
    119.             maxDelta = -maxDelta;
    120.  
    121.         transform.position = Vector3.MoveTowards(transform.position, targetEnemyPosition, maxDelta);
    122.         targetEnemyDistance = Vector3.Distance(transform.position, targetEnemyPosition);
    123.         anglePerSecond = GetAngleFromArcLength();
    124.     }
    125.  
    126.     Vector3 GetPositionFromAngle(float angle, float yPos = 0f)
    127.     {
    128.         // We need  subtract 90 degrees so our starting 0 position
    129.         // is at the "bottom" of the circle not on the far right (like in normal math).
    130.         angle -= 90f;
    131.  
    132.         if (angle < 0f)
    133.             angle += 360;
    134.  
    135.         //convert to radians
    136.         angle = Mathf.Deg2Rad * angle;
    137.  
    138.         // We just pretend the Enemy is at 0,0
    139.         // Find our correct x,z Spot using sin and cos
    140.         // Then add our enemies position to translate us to the right spot
    141.  
    142.         // We need to flip the X-Axis because Unity rotates clockwise
    143.         // Standard Sin/Cos rotate counterclockwise
    144.         float xPos = -targetEnemyDistance * Mathf.Cos(angle) + targetEnemyPosition.x;
    145.         float zPos = targetEnemyDistance * Mathf.Sin(angle) + targetEnemyPosition.z;
    146.         return new Vector3(xPos, yPos, zPos);
    147.     }
    148.  
    149.     // This code should be called when a new Enemy is Set
    150.     // it Checks the distance starts us Moving Toward him.
    151.     // I use circling speed for the approach speed
    152.     // You can create a new variable for it if you want it to be different
    153.     IEnumerator ApproachEnemy()
    154.     {
    155.         isApproaching = true;
    156.         if (targetEnemyTransform != null)
    157.             targetEnemyPosition = targetEnemyTransform.position;
    158.      
    159.  
    160.         // Lets look at our target
    161.         transform.LookAt(targetEnemyPosition);
    162.         // Now we get a Vector that points from target to us
    163.         // Normalize it to magnitude 1 and multiply it by targetEnemyDistance.
    164.         // That is how far from the target we need to be on the line between him and us
    165.         Vector3 enemyToUs = transform.position - targetEnemyPosition;
    166.         Vector3 targetPosition = targetEnemyPosition + enemyToUs.normalized * targetEnemyDistance;
    167.         Vector3 startPosition = transform.position;
    168.  
    169.         // I think its easier to use time to get to a position than check the positions
    170.         // Mathf.Approximately will still give you jitters unless you get it really precise
    171.         // This gives us an easy way to exactly set the new position.
    172.         float currentTime = 0f;
    173.         float endTime = Vector3.Distance(transform.position, targetPosition) / circlingSpeed;
    174.         while (currentTime < endTime)
    175.         {
    176.             yield return null;
    177.             currentTime += Time.deltaTime;
    178.             transform.position = Vector3.Lerp(startPosition, targetPosition, currentTime/endTime);
    179.         }
    180.         isApproaching = false;
    181.     }
    182.  
    183.  
    184.     float GetAngleFromArcLength()
    185.     {
    186.         // this is the arcLength formula where
    187.         // circlingSpeed = arclength
    188.         // targetDistance = radius
    189.         // We are solving for angle and returning it
    190.         return (circlingSpeed * 360f) / (2f * targetEnemyDistance * Mathf.PI);
    191.     }
    192.  
    193.  
    194. }
    195.  
    Some things to do:
    • Put a minimum circle distance in and check for it. If you ever get to 0 distance things go bonkers
    • FIgure out a way to delay switching targets while your approaching one. Otherwise you have 2 Coroutines going on at the same time
    • When you start to approach a new Target Lerp the rotation from its current to the new LookAt rotation instead of just instantly facing it. If you want to get fancy you can have it slowly rotate towards the new target while its moving there, and end up exactly facing it when it arrives.
    One final note: Don't be afraid to make lots of little methods. If you look at my Update method versus the original one you posted, I'm willing to bet mine is much easier to read and understand. Breaking up your logic into smaller pieces really helps with writing it,and reading/understanding it later. You can look at my Update and see its adjusting the speed with AdjustSpeed(). You don't have to fully understand whats going on with that function to read the rest of the Update. Later on you can go back in and look at AdjustSpeed if you need to figure it out.
     
    Last edited: Oct 15, 2016
  5. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,840
    Looks pretty good! I especially like how you're using Lerp correctly. :)

    I have a suggestion on this one. Don't use Coroutines!

    You already have an Update method firing every frame; throwing Coroutines into the mix just makes the code more confusing, I feel.