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

Question Issues with Lerping Quaternions.

Discussion in 'Scripting' started by ThickyVEVO, Aug 5, 2023.

  1. ThickyVEVO

    ThickyVEVO

    Joined:
    May 28, 2020
    Posts:
    16
    I'm getting a weird issue when using a lerping value as the dividing variable within a lerp function. returnSpeed has a value of ten and returnSpeedP is just a constantly decreasing value, initially set to return (Lerping between itself and zero, it can be reset to it's initial value of returnSpeed when RecoilFire() is called). When I enter returnSpeed as the variable to be multiplied by Time.deltaTime (area highlighted yellow), I get no errors within the console and it works as predicted, but when I enter the value of returnSpeedP to be multiplied by Time.deltaTime, I get errors saying the Quaternion is a null value (NaN, NaN, NaN, NaN). Not sure why this happens.
    upload_2023-8-4_20-45-20.png
    Console at runtime with highlighted area set to returnSpeed:
    upload_2023-8-4_20-50-23.png
    Console at runtime with highlighted area set to returnSpeedP:
    upload_2023-8-4_20-49-58.png
    upload_2023-8-4_20-51-14.png
     

    Attached Files:

  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,563
    I don't even know what that means so like you're not using Lerp / Slerp properly.

    It gives you a rotation from A to B, based on the third argument T, which goes from 0 to 1

    That's it.

    Also, for future reference, photographs of code are NOT A THING.

    If you post a code snippet, ALWAYS USE CODE TAGS:

    How to use code tags: https://forum.unity.com/threads/using-code-tags-properly.143875/
     
  3. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,769
    This is not a correct use of Lerp. Lerp interpolates between values depending on
    t
    , which is clamped between 0 and 1. If you want to move something from start to finish with a Lerp function, the value of
    t
    needs to transition from 0 to 1 itself.

    Time.deltaTime
    and similar are generally not correct to use in Lerp, either on its own or with another value, as the value doesn't do this transition.
    Time.deltaTime
    on it's own is just going to give the same value every frame, thus you're not lerp-ing, you're just making a tiny value every time.

    You probably want something like
    Quaternion.RotateTowards
    here instead.
     
  4. ThickyVEVO

    ThickyVEVO

    Joined:
    May 28, 2020
    Posts:
    16
    How specifically should I be using Quaternion.RotateTowards. A lot of the motion in this script uses lerping quaternions to create a smooth appearance of motion. I'd obviously have to refactor a lot of my code but I little bit of a point in the right direction would help. Although it functions how I'd like it to, it's probably not even close to an optimal setup so I'd like to find a way to use Quaternion.RotateTowards instead of Lerp if it allows me to do what I was originally setting out to do. The rest of the script for context:
    Code (CSharp):
    1. public class WeaponSway : MonoBehaviour
    2. {
    3.     [SerializeField] GunScript gs;
    4.     [SerializeField] bool aimingEnabled;
    5.     [SerializeField] bool recoilEnabled;
    6.     [Header("Recoil Attributes")]
    7.     private Quaternion targetRot;
    8.     private Quaternion currentRot;
    9.     private Vector3 targetPos;
    10.     private Vector3 currentPos;
    11.     [SerializeField] float recoilX;
    12.     [SerializeField] float recoilY;
    13.     [SerializeField] float recoilZ;
    14.     [SerializeField] float recoilRotX;
    15.     [SerializeField] float recoilRotY;
    16.     [SerializeField] float recoilRotZ;
    17.     [SerializeField] float returnSpeed;
    18.     private float returnSpeedP;
    19.     [SerializeField] float recoilSnap;
    20.     [Header("SwaySettings")]
    21.     [SerializeField] float swayMultiplierX;
    22.     [SerializeField] float swayMultiplierY;
    23.     [SerializeField] float swayMultiplierZ;
    24.     [SerializeField] float swayLagMultiplier;
    25.     [SerializeField] float smoothing;
    26.     [Header("Bob Settings")]
    27.     [SerializeField] float bobMultiplierX;
    28.     [SerializeField] float bobMultiplierY;
    29.     [SerializeField] float bobMultiplierXSpeed;
    30.     [SerializeField] float bobMultiplierYSpeed;
    31.     [SerializeField] float moveOffset;
    32.     private Vector3 initialPos;
    33.     private Vector3 startPos;
    34.     private Vector3 idlePos;
    35.     private Vector3 bobPos;
    36.     private Vector3 finalPos;
    37.     private Quaternion swayPos;
    38.     private Quaternion swayBob;
    39.     private Quaternion finalSway;
    40.  
    41.     private void Start()
    42.     {
    43.         initialPos = transform.localPosition;
    44.         startPos = initialPos;
    45.     }
    46.     private void Update()
    47.     {
    48.         IdleSway();
    49.         MoveSway();
    50.         MoveBob();
    51.         Recoil();
    52.         FinalSway();
    53.         if (GunScript.isAiming && aimingEnabled)
    54.             initialPos = gs.aimPos.localPosition;
    55.         else
    56.             initialPos = startPos;
    57.     }
    58.     void IdleSway()
    59.     {
    60.         if (!GunScript.isAiming)
    61.         {
    62.             idlePos = initialPos + new Vector3
    63.     (Mathf.Cos(Time.fixedTime) * swayMultiplierX,
    64.     Mathf.Sin(Time.fixedTime) * swayMultiplierY,
    65.     Mathf.Sin(Time.fixedTime) * swayMultiplierZ) / 20;
    66.         }
    67.         else
    68.         {
    69.             idlePos = initialPos;
    70.         }
    71.     }
    72.     void MoveSway()
    73.     {
    74.         float mouseX = Input.GetAxisRaw("Mouse X") * -swayLagMultiplier;
    75.         float mouseY = Input.GetAxisRaw("Mouse Y") * -swayLagMultiplier;
    76.         float moveDir = Input.GetAxisRaw("Horizontal");
    77.  
    78.         Quaternion rotationX = Quaternion.AngleAxis(-mouseY, Vector3.right);
    79.         Quaternion rotationY = Quaternion.AngleAxis(mouseX, Vector3.up);
    80.         Quaternion moveRot = Quaternion.AngleAxis(moveDir * -5, Vector3.forward);
    81.         Quaternion moveBobX = Quaternion.AngleAxis(Mathf.Sin(Time.fixedTime * (bobMultiplierXSpeed / 2) * moveOffset) * 3 * moveOffset, Vector3.right);
    82.         Quaternion moveBobY = Quaternion.AngleAxis(Mathf.Cos(Time.fixedTime * (bobMultiplierYSpeed / 2) * moveOffset) * 3 * moveOffset, Vector3.up);
    83.         Quaternion targetRotation = rotationX * rotationY * moveRot;
    84.         Quaternion targetBob = moveBobX * moveBobY;
    85.  
    86.         swayPos = Quaternion.Slerp(finalSway, targetRotation, smoothing * Time.deltaTime);
    87.         if(PlayerController.isMoving)
    88.             swayBob = Quaternion.Slerp(swayPos, targetBob, 10 * Time.deltaTime);
    89.         else
    90.             swayBob = swayPos;
    91.     }
    92.     void MoveBob()
    93.     {
    94.         if (PlayerController.isSprinting)
    95.             moveOffset = 1.5f;
    96.         else if (PlayerController.isCrouching)
    97.             moveOffset = 0.75f;
    98.         else
    99.             moveOffset = 1;
    100.         if (PlayerController.isMoving)
    101.         {
    102.             bobPos = idlePos + new Vector3
    103.                 (Mathf.Sin(Time.fixedTime * bobMultiplierXSpeed * moveOffset) * bobMultiplierX,
    104.                 Mathf.Sin(Time.fixedTime * bobMultiplierYSpeed * moveOffset) * bobMultiplierY * moveOffset, 0) / 10;
    105.         }
    106.         else
    107.         {
    108.             bobPos = idlePos;
    109.         }
    110.         finalPos = Vector3.Lerp(transform.localPosition, bobPos, 20 * Time.deltaTime);
    111.         finalSway = swayBob;
    112.     }
    113.     void Recoil()
    114.     {
    115.         returnSpeedP = Mathf.Lerp(returnSpeedP, 0, Time.deltaTime);
    116.         targetRot = Quaternion.Lerp(targetRot, finalSway, returnSpeedP * Time.deltaTime);
    117.         currentRot = Quaternion.Slerp(currentRot, targetRot, recoilSnap * Time.deltaTime);
    118.         print(returnSpeedP * Time.deltaTime);
    119.     }
    120.     public void RecoilFire()
    121.     {
    122.         if (recoilEnabled)
    123.         {
    124.             returnSpeedP = returnSpeed;
    125.             targetRot = Quaternion.AngleAxis(-30, Vector3.right);
    126.         }
    127.     }
    128.     void FinalSway()
    129.     {
    130.         transform.localPosition = finalPos;
    131.         if (recoilEnabled)
    132.             transform.localRotation = finalSway * currentRot;
    133.         else
    134.             transform.localRotation = finalSway;
    135.     }
    136. }
     
  5. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,769
    Honestly if this gun weapon sway and recoil... just use animations instead.
     
  6. ThickyVEVO

    ThickyVEVO

    Joined:
    May 28, 2020
    Posts:
    16
    I'd usually agree for that being the solution in this scenario but I'm trying to a make a procedurally animated weapon and recoil system as a base for projects with physically based weapons, so that's kinda the whole point of this script.
     
  7. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,769
    Well procedurally animated would involve using actual animations, and using code to blend between them, ergo, Unity's Animator component/mechanim. You're just hard coding animations.

    In any case RotateTowards is pretty simple, it just gives you a rotation that's been rotated from your current rotation, towards your target rotation no more than the max delta, without overshooting.

    Just work out your target rotation, and rotate towards it a certain amount per frame.

    Also you have a lot of x, y, z fields that could be simplified into Vector3's.
     
  8. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,563
    Here was my cheap and cheerful recoil treatment.

    Mine uses AnimationCurves but if you want, replace them with your own procedural function for the deflections. Data-wise they are just two functions (line 31 and line 32) with domain 0 to 1 and output in degrees of lateral and vertical deflection.

    You could make an equivalent one for sway and just blend the outputs:

    Code (csharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class RecoilSequencer : MonoBehaviour
    6. {
    7.     [Header( "Make the curve domains from 0 to 1.")]
    8.     [Header( "Output value is degrees of deflection.")]
    9.     [Header( "Adjust time interval separately below.")]
    10.     public AnimationCurve RecoilUp;
    11.     public AnimationCurve RecoilRight;
    12.  
    13.     [Header( "How long is entire recoil sequence?")]
    14.     public float TimeInterval = 0.25f;
    15.  
    16.     [Header( "Which object is having its .localRotation driven.")]
    17.     public Transform RecoilPivot;
    18.  
    19.     float recoiling;
    20.  
    21.     public void RecoilBegin()
    22.     {
    23.         if (recoiling == 0)
    24.         {
    25.             recoiling = Time.deltaTime;
    26.         }
    27.     }
    28.  
    29.     void DriveRecoil(float fraction)
    30.     {
    31.         float up = RecoilUp.Evaluate(fraction);
    32.         float right = RecoilRight.Evaluate(fraction);
    33.  
    34.         // special number to FORCE you to have zero output when fraction is zero,
    35.         // to keep you from making borked curves and wondering WTF.
    36.         if (fraction == 0)
    37.         {
    38.             up = 0;
    39.             right = 0;
    40.         }
    41.  
    42.         up = -up;            // convenience
    43.  
    44.         RecoilPivot.localRotation = Quaternion.Euler(up, right, 0);
    45.     }
    46.  
    47.     void Update ()
    48.     {
    49.         if (recoiling > 0)
    50.         {
    51.             float fraction = recoiling / TimeInterval;
    52.  
    53.             recoiling += Time.deltaTime;
    54.  
    55.             if (recoiling > TimeInterval)
    56.             {
    57.                 recoiling = 0;
    58.                 fraction = 0;            // return to time = 0
    59.             }
    60.  
    61.             DriveRecoil(fraction);
    62.         }
    63.     }
    64. }
    Full package attached.
     

    Attached Files:

  9. ThickyVEVO

    ThickyVEVO

    Joined:
    May 28, 2020
    Posts:
    16
    Thank you, super helpful and a lot easier to work around compared to my spaghetti code.
     
  10. wideeyenow_unity

    wideeyenow_unity

    Joined:
    Oct 7, 2020
    Posts:
    728
    As far I understood Lerp/Slerp:

    Code (CSharp):
    1. swayPos = Quaternion.Slerp(
    2.    currentRotation,
    3.    targetRotation,
    4.    percentageAmountToMoveEachFrame);
    By percentage, meaning 0.00 to 1.00 = 0-100%.
    So 0.5f would mean move 50% of the value, each frame.

    I could be wrong on this, but that was my general comprehension of it.
     
  11. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,563
    Close... but Lerp/Slerp knows absolutely nothing about time, absolutely nothing about moving, nothing about frames... it doesn't even know anything about Unity or the clock.

    ALL flavors of Lerp/Slerp do the exact same thing.

    Values A and B and T are inputs.

    T is a percent (as you note)

    The returned value is between A and B based on the amount T.

    That's it.

    Really.

    That's it.

    Nothing about moving, nothing about time, nothing about anything besides computing what fraction you are between the values A and B.
     
    spiney199 and wideeyenow_unity like this.