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

Jump Animation with lerp and slerp setup problems

Discussion in 'Scripting' started by decay, May 2, 2017.

  1. decay

    decay

    Joined:
    Mar 21, 2016
    Posts:
    15
    hey guys, so i have this cube as a player. i need it to be able to jump from one position to another. i wrote this script, and it works fine - unless the player moves before jumping.

    the player can move (what is another script) flipping along the edges of the cube.

    so, this code here is only working when starting the game and jumping without moving. as soon as the player moves and then jumps, he is sent to the strangest locations - but not where i need it to land after jumping.

    Code (CSharp):
    1. using System;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5.  
    6. public class AnimationJumpScript : MonoBehaviour {
    7.     public AnimationCurve moveCurve;
    8.     public AnimationCurve jumpCurve;
    9.     public AnimationCurve flipCurve;
    10.  
    11.     public MoveCube cubePositionObject;
    12.  
    13.     private Vector3 startPos;
    14.     private Vector3 endPos;
    15.     private Vector3 targetAngle;
    16.     public Vector3 currentAngle;
    17.  
    18.     private float lerpTime = 2;
    19.     private float currentLerpTime = 0;
    20.  
    21.     private bool keyHit = false;
    22.  
    23.     private void Start()
    24.     {
    25.         startPos = cubePositionObject.transform.position;
    26.         Math.Round(startPos.x, 0);
    27.         endPos = startPos + new Vector3(2, 0, 0);
    28.     }
    29.  
    30.     // Update is called once per frame
    31.     void Update () {
    32.         if (Input.GetKeyDown("space"))
    33.         {
    34.             // set the positions needed for the jump animation
    35.             SetPositions();
    36.             keyHit = true;
    37.         }
    38.  
    39.         if (keyHit == true) {
    40.             // the current angle of the cube
    41.             currentAngle = transform.eulerAngles;
    42.  
    43.             currentLerpTime += Time.deltaTime * 1.5f;
    44.             if(currentLerpTime >= lerpTime)
    45.             {
    46.                 currentLerpTime = lerpTime;
    47.             }
    48.  
    49.             GetTargetAngle();
    50.  
    51.             float perc = currentLerpTime / lerpTime;
    52.  
    53.             // the movement of the cube
    54.             transform.position = Vector3.Lerp(startPos, endPos, moveCurve.Evaluate(perc));
    55.             // the jump of the cube
    56.             transform.position = Vector3.Lerp(transform.localPosition, startPos + Vector3.up, jumpCurve.Evaluate(perc));
    57.             // the rotation of the cube
    58.             currentAngle = new Vector3(
    59.                 Mathf.LerpAngle(currentAngle.x, targetAngle.x, flipCurve.Evaluate(perc)),
    60.                 Mathf.LerpAngle(currentAngle.y, targetAngle.y, flipCurve.Evaluate(perc)),
    61.                 Mathf.LerpAngle(currentAngle.z, targetAngle.z, flipCurve.Evaluate(perc)));
    62.  
    63.             transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.Euler(currentAngle), flipCurve.Evaluate(perc));
    64.  
    65.             OnJumpFinished();
    66.         }
    67.     }
    68.  
    69.     private void SetPositions()
    70.     {
    71.         // the start position is the current position
    72.         startPos = cubePositionObject.transform.position;
    73.         // just to be sure
    74.         Math.Round(startPos.x, 0);
    75.  
    76.         // end position is 2 clicks ahead on the x axis
    77.         endPos = startPos + new Vector3(2, 0, 0);
    78.     }
    79.  
    80.     private void OnJumpFinished()
    81.     {
    82.         // check if the cube has reached its destination
    83.         if (cubePositionObject.transform.position == endPos)
    84.         {
    85.             keyHit = false;
    86.             SetTargetAngleLastTip(targetAngle);
    87.  
    88.             // resetting the attributes
    89.             currentLerpTime = 0;
    90.             currentAngle = transform.eulerAngles;
    91.             GetTargetAngle();
    92.         }
    93.     }
    94.  
    95.     private Vector3 GetTargetAngle()
    96.     {
    97.         // check the rotation the cube is at. if flipped, the target is to unflip it and vice versa.
    98.         // the angles need to be odd for the lerp to rotate in the right direction
    99.         Vector3 unflipped = new Vector3(0, 0, 0);
    100.         Vector3 flipped = new Vector3(0, 0, 180);
    101.         if (transform.eulerAngles == unflipped)
    102.         {
    103.             targetAngle = new Vector3(0, 0, -179);
    104.         }
    105.  
    106.         if (transform.eulerAngles == flipped)
    107.         {
    108.             targetAngle = new Vector3(0, 0, 1);
    109.         }
    110.  
    111.         return targetAngle;
    112.     }
    113.  
    114.     private void SetTargetAngleLastTip(Vector3 currentAngle)
    115.     {
    116.         // the odd rotation when at the end of the lerp is corrected for a perfect 18ß degree flip
    117.         if (currentAngle == new Vector3(0, 0, -179))
    118.         {
    119.             Vector3 finishedAngle;
    120.             finishedAngle = new Vector3(0, 0, -180);
    121.             transform.eulerAngles = finishedAngle;
    122.         }
    123.  
    124.         if (currentAngle == new Vector3(0, 0, 1))
    125.         {
    126.             Vector3 finishedAngle;
    127.             finishedAngle = new Vector3(0, 0, 0);
    128.             transform.eulerAngles = finishedAngle;
    129.         }
    130.     }
    131. }
    132.  
    the positions are set correctly before the jump. i can only guess that theres something going on with the lerp stuff... but i am at the end of my knowledge. any help is greatly appreciated. :)
     
  2. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,773
    Euler angles are terrible - here's why. Anytime I see rotation-related questions, the first thing I do is ctrl-f for "euler" and 95% of the time, Euler angles are the problem. A quick glance at yoru code suggests that may be the case here.
     
  3. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    Well, one thing I notice right away is that you're misusing Lerp in every case except line 54. The first parameter of Lerp (as well as Slerp, LerpAngle, etc.) is always the start position, i.e. the value at some point in the past when the movement began — never the current position.

    If you want to work from the current position towards some goal, you should be using MoveTowards (or MoveTowardsAngle, etc.) instead. (And this is usually much easier than Lerp; I don't know why so many people reach for Lerp rather than using the easier method.)
     
  4. decay

    decay

    Joined:
    Mar 21, 2016
    Posts:
    15
    thanks so far. this IS the first time i am using lerp and from what i can tell, i AM using it from the beginning of the movement. (?) where am i using it with the current position and why? or is this because its in the update method?
     
  5. decay

    decay

    Joined:
    Mar 21, 2016
    Posts:
    15
    thanks! i'll do my best to understand this article you posted.
     
  6. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    As for "where," here:
    Code (CSharp):
    1. transform.position = Vector3.Lerp(transform.localPosition, startPos + Vector3.up, jumpCurve.Evaluate(perc));
    2. // the rotation of the cube
    3. currentAngle = new Vector3(
    4.       Mathf.LerpAngle(currentAngle.x, targetAngle.x, flipCurve.Evaluate(perc)),
    5.       Mathf.LerpAngle(currentAngle.y, targetAngle.y, flipCurve.Evaluate(perc)),
    6.       Mathf.LerpAngle(currentAngle.z, targetAngle.z, flipCurve.Evaluate(perc)));
    7. transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.Euler(currentAngle), flipCurve.Evaluate(perc));
    Any time the first parameter to your Lerp call is the same thing you're assigning to, you're using it wrong.

    As for "why," I have no idea. :) Except that this is a very common mistake, and for years even the Unity documentation itself committed this error, thus spreading bad examples throughout the Unity community. :(
     
    decay likes this.
  7. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,773
    Incidentally, if you're using Quaternions instead of Vector3/Euler's to store your rotation (as you should be), you can avoid the 3 different Lerp calls there, and use Quaternion.Slerp instead. If you still Lerp at all, anyway.
     
  8. decay

    decay

    Joined:
    Mar 21, 2016
    Posts:
    15
    ok, that i understand. makes sense, thanks so much!

    how would i use .moveTowards correctly in my example? could you update my code at one point? and: can i still use the curves for .moveTowards?
     
  9. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,773
    For starters, let's use Quaternion.RotateTowards, on the assumption that you'll be replacing your Euler angles with Quaternions. RT takes "degrees" as its final parameter - e.g. "rotate this many degrees towards the final rotation" - unlike Lerp/Slerp which takes a zero-to-one percentage. So this is a somewhat different concept.

    Could you explain what the curves were intended to be doing? I strongly suspect that they weren't doing what they were planned to be doing, based on the way the parameters for those calls are set up. What was the goal in terms of look and feel?

    In addition to the Euler angles and Lerp issues, I see you're using a lot of == comparers on float-based types (float, Vector3, Quaternion are the main ones to watch for). You generally shouldn't do that, and that could easily be causing some issues.
     
    JoeStrout likes this.
  10. decay

    decay

    Joined:
    Mar 21, 2016
    Posts:
    15
    jesus, you're giving me a lot of useful links! :)

    the look and feel is easy to explain. the cube, normally only being able to flip over one edge, can move by 1 field. the jump skill makes it possible for the cube to move 2 fields. since i want that to look good, i need the cube to jump up in the air and land on the spot, whilst doing a little flip by 180°. (jay!)

    by now i replaced my lerp for the position movement and the up and down movement, it seems to work pretty fine so far.
    only the rotation still gives me problems, AND: after all the main problem still remains.

    starting the game and only hitting space makes the cube jump 2 fields forward as intended. but as soon as i move the cube 1 field away from its starting position, hitting the space bar sends him....well... way off target.
     
  11. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,773
    So, tile-based movement, basically? And you want the cube to rotate on its edge, kinda like it does for Devil Dice (at least for the 1-movement thing)?

    I would almost suggest you abandon code-base movement and go full Animator, except that you probably want your cube to "stay rotated" after it moves, which an Animator couldn't really do.

    With that in mind, the first thing I'll suggest is to use coroutines - they're great for running code across a specific duration spanning multiple frames. Since your movement is discrete - e.g. one move must complete before others can be started - then only one coroutine will be active at a time. You can enforce this with an "isMoving" boolean, which gets set to true at the start of a coroutine and false at the end; if it's true, don't even check for player input.
    Code (csharp):
    1. private bool isMoving = false;
    2. void Update() {
    3. if (!isMoving) {
    4. if (Input.GetKeyDown(KeyCode.Space) ) {
    5. StartCoroutine(JumpTo(howeverYouDetermineTargetPosition) );
    6. }
    7. //same for other movement
    8. }
    9.  
    10. IEnumerator JumpTo(Vector3 endPos) {
    11. isMoving = true;
    12. float duration = 0.5f; //seconds
    13. Vector3 startPos = transform.position;
    14. for (float t=0f; t < duration; t += Time.deltaTime) {
    15. transform.position = Vector3.Lerp(startPos, endPos, t / duration);
    16. yield return 0;
    17. }
    18. transform.position = endPos;
    19. isMoving = false;
    20. }
    That code should get you started. It doesn't handle the rotation of course (it'll just sort of slide the cube to position), but Quaternion.AngleAxis should be able to help with that - in this case, you'll probably want to use Lerp on the angle, and then use AngleAxis within the for loop.

    (Note the structure of that for loop, too - it's reeeeally handy for coroutines! "t / duration" can be inserted into any Lerp/Slerp and it'll always work as expected.)
     
  12. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,773
    Getting a cube to "tip" over its edge and look right doing it might be another tricky thing. The easiest way to go about that would be to use transform.RotateAround(lowerEdgeCenterPoint, vectorAlongThatEdge, Time.deltaTime * rotateSpeedInDegrees); getting that to end at the right spot might be a little trickier. At the end of THAT coroutine, you may want to simply "snap" it to the nearest right-angle rotation, which you can probably do (I haven't tested this, so take this with a grain of salt) by rounding each component of the quaternion (xyzw) to the nearest 0.5. And you'll similarly want to snap your position to the nearest tile as well.
     
  13. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    Another way to tackle the tip-over-edge problem would be to throw another object around the cube, and let that do it for you. Picture this:
    1. You create a new empty GameObject, positioned exactly on the line between the two grid positions.
    2. You parent the cube to this new empty object (which let's call Tipper).
    3. Now you just rotate Tipper through 90°, using a quaternion or a little Update script or whatever makes sense. Because the cube is a child in the hierarchy, this basically grabs the cube and rotates it around Tipper's position (i.e. the line between the two).
    4. At the end of that, as @StarManta suggested, snap the position to the nearest tile, and maybe rotation to the nearest 90%, so you don't accumulate errors over time. And then remove your cube from Tipper, and destroy Tipper (or keep it around to use again next time).
    This would work for the jump as well, except that you'd rotate Tipper a little higher (one-half the cube height) over the tile you're jumping over, and then rotate it through 180°. But this would just turn the cube neatly upside-down; if you wanted to do a gratuitious flip in there, you'd need to do that separately. (But you could do so pretty easily with a separate coroutine or script that modifies the cube's localRotation, which would simply add to the jump caused by Tipper.)
     
  14. Gooren

    Gooren

    Joined:
    Nov 20, 2015
    Posts:
    331
    Sorry for necro, but isn't the Quaternion.Slerp (instead of Lerp) and AnimationCurve.Evaluate combination wrong? You are adding spherical (ease in/ease out) effect with the Slerp and then on the top of it the animation curve whatever function. This will most likely lead to "unpredictable/unexpected/unwatned" result, right?
     
    Last edited: Sep 22, 2017