Search Unity

Question Rotation reversing for a single frame using Quaternion.LookRotation

Discussion in 'Scripting' started by Bowbowis, Jan 13, 2021.

  1. Bowbowis

    Bowbowis

    Joined:
    Jun 7, 2017
    Posts:
    6
    I'm using the following code for my player controller:
    Code (CSharp):
    1.             public class PlayerController : MonoBehaviour
    2.             {
    3.              //Components
    4.              [SerializeField] Camera gameCamera;
    5.              [SerializeField] Rigidbody playerbody;
    6.        
    7.              //Physics Constants
    8.              private float acc = 0.0703125f;
    9.              private float frc = 0.0703125f;
    10.              private float top = 9f;
    11.        
    12.        
    13.               //Inputs
    14.              public Vector3 moveInput;
    15.              public Vector3 localizedInput;
    16.            
    17.               //Coroutines
    18.              Coroutine turnCoRt;
    19.        
    20.        
    21.              //On Move is called when activity from the Move InputAction is detected.
    22.              public void OnMove(InputValue value)
    23.              {
    24.                  Vector2 rawInput = value.Get<Vector2>();
    25.                  moveInput = new Vector3(rawInput.x, 0, rawInput.y);
    26.        
    27.                  if (moveInput != Vector3.zero)
    28.                  {
    29.                      //Get direction of input relative to the camera direction.
    30.                      localizedInput = GetLocalizedInput();
    31.                      if (turnCoRt != null) StopCoroutine(turnCoRt);
    32.                      turnCoRt = StartCoroutine(Turn());
    33.                  }
    34.                  else
    35.                  {
    36.                      //Get direction of input relative to the camera direction.
    37.                      localizedInput = Vector3.zero;
    38.                      if (turnCoRt != null) StopCoroutine(turnCoRt);
    39.                  }
    40.              }
    41.        
    42.        
    43.              //Update is called once per frame.
    44.              private void Update()
    45.              {
    46.                  localizedInput = GetLocalizedInput();
    47.              }
    48.        
    49.              // FixedUpdate is called once per fixed timestep frame.
    50.              void FixedUpdate()
    51.              {
    52.                  if (moveInput != Vector3.zero)
    53.                  {
    54.                     Accelerate(acc);
    55.                  }
    56.                  else
    57.                  {
    58.                     Decelerate(frc);
    59.                  }
    60.               }
    61.        
    62.          void Accelerate(float acc)
    63.              {
    64.                  if (playerbody.velocity.magnitude < top)
    65.                  {
    66.                      if (playerbody.velocity.magnitude + acc < top)
    67.                      {
    68.                          playerbody.AddForce(acc * localizedInput, ForceMode.VelocityChange);
    69.                      }
    70.                      else
    71.                      {
    72.                          playerbody.AddForce((top - playerbody.velocity.magnitude) * localizedInput, ForceMode.VelocityChange);
    73.                      }
    74.                  }
    75.              }
    76.        
    77.              void Decelerate(float dec)
    78.              {
    79.                  if (playerbody.velocity.magnitude > 0)
    80.                  {
    81.                      if (dec < playerbody.velocity.magnitude)
    82.                      {
    83.                          playerbody.AddForce(dec * -playerbody.velocity.normalized, ForceMode.VelocityChange);
    84.                      }
    85.                      else
    86.                      {
    87.                          playerbody.velocity = Vector3.zero;
    88.                      }
    89.                  }
    90.              }
    91.        
    92.              Vector3 GetLocalizedInput ()
    93.              {
    94.                  Vector3 camVector = Vector3.ProjectOnPlane(gameCamera.transform.forward, transform.up).normalized;
    95.                
    96.                 Quaternion camRotation = Quaternion.FromToRotation(Vector3.forward, camVector);
    97.                
    98.                 Vector3 result = camRotation * moveInput;
    99.                
    100.                 return result;
    101.              }
    102.        
    103.              IEnumerator Turn ()
    104.              {
    105.                  for (float r = 0; r < 1; r += Time.deltaTime)
    106.                  {
    107.                      if (r > 1) r = 1;
    108.        
    109.                      transform.rotation = Quaternion.Slerp(Quaternion.LookRotation (transform.forward, transform.up), Quaternion.LookRotation (localizedInput, transform.up), r);
    110.  
    111.                      playerbody.velocity = transform.forward * playerbody.velocity.magnitude;
    112.                      
    113.                      yield return null;
    114.                  }
    115.        
    116.                  for (; ; )
    117.                  {
    118.                      transform.rotation = Quaternion.LookRotation(localizedInput, transform.up);
    119.  
    120.                      playerbody.velocity = transform.forward * playerbody.velocity.magnitude;
    121.  
    122.                      yield return null;
    123.                  }
    124.              }
    125.          }
    This gives the desired effect most of the time but occasionally, when holding left or right to turn, the character will be rotated 180 degrees around the Y axis for a single frame before returning to the intended rotation on the next. Can anybody figure out what's causing it?
     
  2. PraetorBlue

    PraetorBlue

    Joined:
    Dec 13, 2012
    Posts:
    7,908
    Not completely sure what's causing the issue, but there's something really strange about the way you're using coroutines here and it might be related.

    The way your logic works around line 31 and 38, your coroutine is always going to be stopped after exactly one frame. That means your coroutine will only ever run up until the first yield statement. In fact I'm confused as to how you're getting any rotation on your character at all given that this means you'll be calling Slerp only with a third parameter of 0 every time... Am I missing something there?

    Also the if statement on line 107 can never be true because the for loop conditional precludes it.
     
  3. Bowbowis

    Bowbowis

    Joined:
    Jun 7, 2017
    Posts:
    6
    I think I figured it out. The
    Quaternion.FromToRotation(Vector3.forward, camVector)
    on line 96 seems to have been causing problems when camVector happened to align with Vector3.forward. Replacing it with
    Quaternion.LookRotation(camVector, transform.up)
    seems to have resolved the issue.

    @PraetorBlue On the subject of the coroutine: I'm using the New Input System so OnMove() is only called when the status of "Move" (WASD or Left Analog Stick) changes. The way I have it set up essentially restarts the coroutine whenever you input a new direction so you don't end up with multiple instances fighting each other.

    As for line 107, my understanding is that each iteration of the for loop checks if r < 1 and, if it is, adds Time.deltaTime. Since it's possible that Time.deltaTime is greater than 1 - r it is therefore possible that the final value of r could exceed 1, in which case the if statement will set the value of r to 1. If I'm misunderstanding though please let me know.
     
    PraetorBlue likes this.
  4. PraetorBlue

    PraetorBlue

    Joined:
    Dec 13, 2012
    Posts:
    7,908
    For loops work like this:
    1. Check the condition (break if false)
    2. Run the body
    3. Increment
    4. Repeat.
    Condition checking happens first. Increment happens last. So it's not possible for r to be > 1 inside the loop.
     
    Kurt-Dekker likes this.
  5. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,674
    When you're Slerping or Lerping, it's often critical to reach exactly 1.0, and as Praetor points out above, a for() loop isn't suited to this.

    Here is how I do almost ALL my slerps and lerps:

    https://forum.unity.com/threads/lerping-reset.907091/#post-5950706

    See how the fraction will always reach (at least) 1.0 before the loop exits? This lets you be confident of the final state.