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

Rigidbody MoveRotation snaps to different angle after User stops pressing diagonal vector.

Discussion in 'Scripting' started by ynm11, Jan 22, 2022.

  1. ynm11

    ynm11

    Joined:
    Jul 6, 2021
    Posts:
    57
    Trying to make rigidbody Capsule rotate towards player movement, but it keeps snapping to different angle after User input has stopped if User's last input was a diagonal vector (or 2 axes at once). What might be wrong?

    The most relevant code is in `FixedUpdate`, specifically the `r.MoveRotation` part.
    By the way, it works perfectly if the user's last input was only a single axis. Meaning that when user lets off keyboard, the rotation stops and stays exactly what it was.

    Code (CSharp):
    1.     using System.Collections;
    2.     using System.Collections.Generic;
    3.     using UnityEngine;
    4.  
    5.     public class MoveCapsule : MonoBehaviour
    6.     {
    7.         public Rigidbody r;
    8.  
    9.         //Base Movement
    10.         public Vector3 movDirection;
    11.         public float speed;
    12.         public int rotDegPerSecond = 720;
    13.  
    14.         //Ground Check
    15.         public bool isGrounded = true;
    16.         public bool shouldCheckGround = true;
    17.         public Transform groundCheckTransform;
    18.         public float groundCheckRadius = 0.25f;
    19.         public LayerMask groundLayerMask;
    20.  
    21.         void Awake()
    22.         {
    23.             r = this.GetComponent<Rigidbody>();
    24.         }
    25.  
    26.         public void PhysicsChecks()
    27.         {
    28.             // Ground Check
    29.             if (shouldCheckGround)
    30.                 GroundCheck();
    31.         }
    32.  
    33.         public void GroundCheck()
    34.         {
    35.             isGrounded = Physics.CheckSphere(groundCheckTransform.position, groundCheckRadius, groundLayerMask);
    36.         }
    37.  
    38.         public void FixedUpdate()
    39.         {
    40.             float horizontalInput = Input.GetAxis("Horizontal");
    41.             float verticalInput = Input.GetAxis("Vertical");
    42.             movDirection = new Vector3(horizontalInput, 0.0f, verticalInput).normalized;
    43.  
    44.             r.AddForce(movDirection * (speed + 18f), ForceMode.Force);
    45.      
    46.             Quaternion targetRotation = Quaternion.LookRotation(movDirection);
    47.             targetRotation = Quaternion.RotateTowards(transform.rotation, targetRotation, rotDegPerSecond * Time.deltaTime);
    48.  
    49.  
    50.  
    51.             if (movDirection != Vector3.zero)
    52.             {
    53.                 //Only rotate if player has movement input
    54.                 r.MoveRotation(targetRotation);
    55.             }
    56.  
    57.             PhysicsChecks();      
    58.         }
    59.     }
    Thank you.
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,520
    You need more slop where you inhibit setting the rotation right before you stop moving.

    Instead of this super-tight anything except zero test:

    ... perhaps make it something like:

    Code (csharp):
    1. if (movDirection.magnitude > 0.05f)
    Play with that number if not quite right.
     
    Last edited: Jan 22, 2022
    ynm11 likes this.
  3. ynm11

    ynm11

    Joined:
    Jul 6, 2021
    Posts:
    57
    For some reason that isn't doing it yet; I'll keep playing with it in case I'm doing something wrong
     
  4. GroZZleR

    GroZZleR

    Joined:
    Feb 1, 2015
    Posts:
    3,201
    Don't sample input inside FixedUpdate, it's not accurate. Cache it in Update and read it in FixedUpdate. It's probably why this is happening:

     
    ynm11 likes this.
  5. ynm11

    ynm11

    Joined:
    Jul 6, 2021
    Posts:
    57
    I've tried that now but no dice. I saw a similar thread where the problem supposedly comes from usage of `GetAxis` instead of `GetRawAxis`. Because `GetAxis` takes extra frames to go from 1 to 0, and so even if I do a check for "if moveDirection != Vector.zero", it doesn't fire exactly when User takes hands off keys.

    Even if that is the case I can't wrap my head around how to approach this still. I think @Kurt-Dekker 's approach looks like it should have solved it but I just am not finding the magic solution yet. Appreciate advice in meantime, will update if/when solved.
     
  6. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,520
    It certainly seems in this case that it should, but it could be that one of your input axes is falling to zero much faster than the other, which would leave a false sense of still pushing in only one axis direction.

    One way to fix something like that is to change your criteria for setting rotation from:

    if (magnitude > x): set rotation

    To something like:

    if (magnitude > x) OR (thisframemovement.magnitude > lastframemovement.magnitude): set rotation

    The second would let you set x up to 0.25f or even bigger.

    Again though, this is just speculation. Print out a log of X, Y, magnitude and calculated rotation, see if you can figure out mathematically what is happening.
     
    ynm11 likes this.
  7. ynm11

    ynm11

    Joined:
    Jul 6, 2021
    Posts:
    57
    Update: Solved

    2 Solution possibilities:

    1- The magnitude check as suggested above. For me it had to be a super precise number with 8 decimal points, so that's why it took a while to figure out it actually worked.

    2- I also found another approach for my project:

    Instead of
    if (movDirection != Vector3.zero)

    do
    if (movDirectionRaw != Vector3.zero)

    where movDirectionRaw is
    movDirectionRaw = new Vector3(Input.GetAxisRaw("Horizontal"), 0.0f, Input.GetAxisRaw("Vertical"));

    The main takeaway is to use GetAxisRaw to instantly stop rotation for diagonal movement, instead of GetAxis.


    I hope either of these solutions help someone in the future.
    SOLVED.
     
    Kurt-Dekker likes this.