Search Unity

  1. Unity 6 Preview is now available. To find out what's new, have a look at our Unity 6 Preview blog post.
    Dismiss Notice
  2. Unity is excited to announce that we will be collaborating with TheXPlace for a summer game jam from June 13 - June 19. Learn more.
    Dismiss Notice

Making an object rotate smoothly to a certain point and stop? (kinda solved thanks :) )

Discussion in 'Scripting' started by Thaao, Jul 7, 2014.

  1. Thaao

    Thaao

    Joined:
    Jun 9, 2014
    Posts:
    54
    Please check post #8 for an updated code and new problem. Well, I think the problem was happening all along but now the other problems have been ironed out.

    So, I've been trying to use Quaternion.Lerp or Quaternion.Slerp to make my player character rotate 45 degrees at a time (it's a first-person dungeon crawler on a grid, so I want the movement to 'snap' to only ever 45 degrees).

    But I think there's something about Lerp/Slerp that I don't understand. I am using Euler Angles to set the angles and everything, and the actual turning motion seems to be working properly, but the problem I'm having is really strange.

    Here is my code:

    Code (CSharp):
    1.     void Update ()
    2.     {
    3.         if(myTurn && !isInAction)
    4.         {
    5.             if(Input.GetAxis ("Horizontal") > 0)
    6.             {
    7.                 Debug.Log ("rot before " + rot);
    8.                 rot = (rot == 315) ? 0 : (rot + 45);
    9.                 Debug.Log ("rot after " + rot);
    10.                 isTurning = true;
    11.                 isInAction = true;
    12.             }
    13.             if(Input.GetAxis ("Horizontal") < 0)
    14.             {
    15.                 Debug.Log ("rot before " + rot);
    16.                 rot = (rot == 0) ? 315 : (rot - 45);
    17.                 Debug.Log ("rot after " + rot);
    18.                 isTurning = true;
    19.                 isInAction = true;
    20.             }
    21.         }
    22.  
    23.         if(isTurning)
    24.         {
    25.             if(transform.rotation.eulerAngles.y == rot)
    26.             {
    27.                 Debug.Log ("Turning complete!");
    28.                 isTurning = false;
    29.                 isInAction = false;
    30.             }
    31.             else
    32.             {
    33.                 Debug.Log ("Still Turning...");
    34.                 transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.Euler (new Vector3 (0, rot, 0)), Time.deltaTime * turnSpeed);
    35.             }
    36.         }
    37.     }
    Nothing is messing with myTurn, so it's always set to true.

    rot is set to transform.rotation.eulerAngles.y in Start() and then seems to update properly from there on out.

    Here are my problems:

    1. Whenever I move left (horizontal negative), the object/character does indeed turn! But the if(transform.rotation.eulerAngles.y == rot) is never entered. Rot is set properly, and the y value is matching in the inspector... I pause the game and look at the player character. transform.rotation.eulerAngles.y will be 315, and rot will be 315, but I never get the log "Turning complete!" (though I do get "Still Turning..." once) and the bools never set to false. So the player control is completely frozen. It doesn't matter what rotation/rot value I am at when I try to move left; moving left always causes this to occur. (I did have it work shortly after the following problem, but normally it just freezes like this):

    2. I can rotate to the right more successfully. I will press right (horizontal positive) and the rotation will happen, then there will be a super laggy pause before it ever logs "Turning complete!" and allows movement again. I can continue to rotate to the right in the same way, and it takes a lot time before control is restored each time. Only the very first time I press the button, I get "Still Turning..." and "Turning Complete!" even though I should be entering the code every time (I mean, it's doing the rotation, so it's obviously going there!). But then once I get to 315 and try to go right, rot is set to 0, but the rotation seems to jump around in negative numbers for a LONG time before control is ever returned.



    Here's a video of what it looks like. Most of the time I'm just mashing or holding vertical positive. Now and then I press vertical negative. SORRY, I FORGOT PROCASTER WAS GOING TO RECORD MY SPEAKERS' AUDIO, so there is music in the video. Just mute it if you don't want to hear it.

    I've been working at this for days now, most of that condensed into yesterday, and I just can't figure it out.
     
    Last edited: Jul 7, 2014
  2. Ereous

    Ereous

    Joined:
    Aug 29, 2012
    Posts:
    163
    Simple way is to just to make it not equal it.. Just have it close enough. As Slerp gets closer to that angle you want.. it starts going really really slow towards. Its like an infinite line that is going towards 0.. but never gets there. (That math terminology that I've long forgotten)

    So this line:
    if(transform.rotation.eulerAngles.y== rot)

    Instead of checking exactness.. Check a threshold (Closeness) 0.1 difference or even 0.01 depends on how close you want it.
     
    Thaao likes this.
  3. Thaao

    Thaao

    Joined:
    Jun 9, 2014
    Posts:
    54
    Thanks for your response. I will try this. What about in cases like going from 315 to 0 where it will go to weird values like -6 and the value will jump around far from 0?
     
  4. JamesLeeNZ

    JamesLeeNZ

    Joined:
    Nov 15, 2011
    Posts:
    5,616
    == will never work when comparing float values and should be avoided. You can use Mathf.Approximately if you want, however the best solution would be to not rely on rotation tests anyway...

    Something like this should work.

    Code (csharp):
    1.  
    2.  
    3. float rotationY = 0;
    4.  
    5. void Update()
    6. {
    7.   if(Input.GetKey(KeyCode.D))
    8.     rotationY += 45;
    9.   if(Input.GetKey(KeyCode.A))
    10.     rotationY -= 45;
    11.  
    12.   if(rotationY > 360)
    13.      rotationY -= 360;
    14.   if(rotationY < 0)
    15.      rotationY += 360;
    16.  
    17.   transform.RotateTowards(transform.rotation, Quaternion.Euler(0,rotationY,0), 5 * Time.deltaTime);
    18.  
    19. }
    20.  
    21.  
     
  5. BFGames

    BFGames

    Joined:
    Oct 2, 2012
    Posts:
    1,543
    Edit: can see people already answered the floating point problem.. :)

    However you should note how Quaternion.Euler works:

    "Returns a rotation that rotates z degrees around the z axis, x degrees around the x axis, and y degrees around the y axis (in that order)."

    So as you move towards rot, you wont keep moving towards the same Y value, but towards your current rotation rotated A amount around Y. So it will move towards a different rotation in each iteration, and will as such never "reach" it.
     
    Last edited: Jul 7, 2014
    Thaao likes this.
  6. BFGames

    BFGames

    Joined:
    Oct 2, 2012
    Posts:
    1,543
    So just to clarify.

    Set a Quaternion you will move towards in you Input.Axis checks, and then move towards that Quaternion in Quaternion.Slerp.
     
    Thaao likes this.
  7. Thaao

    Thaao

    Joined:
    Jun 9, 2014
    Posts:
    54
    The problem I'm having is that I need to know when the rotation has finished, so this isn't really helpful. This just allows the player to rotate uninhibitedly, and every frame, 45 gets added to rotationY and the player will just rotate constantly until it catches up with the ever-changing value of rotationY.

    Anyway, to the others who helped, thank you very much! I did not understand how Slerp/Lerp worked exactly. I was expecting it to work Vector3.MoveTowards() which is not the case (MoveTowards just caps so that you definitely reach the goal point and you always reach it without going over). Thank you for noticing and explaining my mistake! I will make some changes and see how it goes.
     
  8. Thaao

    Thaao

    Joined:
    Jun 9, 2014
    Posts:
    54
    I have changed a bit.

    Code (CSharp):
    1.     void Update ()
    2.     {
    3.         if(myTurn && !isInAction)
    4.         {
    5.             }
    6.             if(Input.GetAxis ("Horizontal") > 0)
    7.             {
    8.                 Debug.Log ("rot before " + rot);
    9.                 rot = (rot == 315) ? 0 : (rot + 45);
    10.                 Debug.Log ("rot after " + rot);
    11.                 goRot = Quaternion.Euler (0, rot, 0);
    12.                 isTurning = true;
    13.                 isInAction = true;
    14.             }
    15.             if(Input.GetAxis ("Horizontal") < 0)
    16.             {
    17.                 Debug.Log ("rot before " + rot);
    18.                 rot = (rot == 0) ? 315 : (rot - 45);
    19.                 Debug.Log ("rot after " + rot);
    20.                 goRot = Quaternion.Euler (0, rot, 0);
    21.                 isTurning = true;
    22.                 isInAction = true;
    23.             }
    24.         }
    25.  
    26.         if(isTurning)
    27.         {
    28.             if(Mathf.Approximately(transform.localRotation.eulerAngles.y, rot))
    29.             {
    30.                 Debug.Log ("Turning complete!");
    31.                 isInAction = false;
    32.                 isTurning = false;
    33.                 myTurn = true; //delete this later!
    34.             }
    35.             else
    36.             {
    37.                 Debug.Log ("Still Turning...");
    38.                 transform.localRotation = Quaternion.Slerp(transform.localRotation, goRot, Time.deltaTime * turnSpeed);
    39.             }
    40.         }
    41.     }
    Turning left works now!

    However, when moving from 315 to 0 or from 45 to 0, the rotation seems to take forever to approach 0. going from 315 to 0, it will jump around in negative values between -1 and -6, and eventually creep toward -1.6114 and stick there.. When going from 45 to 0, it will jump around in positive values from 1 to 6, and eventually creep toward 1.6114 and seem to stick there.

    I have no idea how Quaternions work so this is all so baffling to me T_T This is my first time using Unity to make a 3D game (I made a pretty simple 2D game where nothing needed to rotate lol) so I'm learning a lot. Thanks everyone for the help so far.
     
  9. JamesLeeNZ

    JamesLeeNZ

    Joined:
    Nov 15, 2011
    Posts:
    5,616
    um... no. It wouldn't continuously rotate... it would rotate until you reached the designated rotation... sure it wont tell you when its finished, but it wont do what you think it will do... Id explain how you could test that, but f*** it...

    meh... dunno why I bother helping on scripting sometimes.
     
  10. Thaao

    Thaao

    Joined:
    Jun 9, 2014
    Posts:
    54
    I'm sorry, I wasn't clear on what I meant by "constantly." I meant it would continue to rotate past the intended point. It needs to get to the next 45 degrees and stop and not accept input again until that happens. If the player were to press 'D' twice, they would rotate 90 degrees, which defeats the purpose of what I'm trying to do here. Plus for every frame a button is held, the value of rotationY in your code would be changing constantly, which is the opposite of what I'm trying to achieve. In your code, if the player pressed 'D', they could move 90 degrees, 360 degrees, who knows. It would depend on how many frames they held the button. That is not useful to this purpose at all, in which moving 45 degrees and stopping is the goal.
     
  11. BFGames

    BFGames

    Joined:
    Oct 2, 2012
    Posts:
    1,543
    Might be easier to just :

    transform.Rotate(new Vector3(0, rot * time.deltaTime, 0)); or something like that, and then handle it based on time.
     
  12. Thaao

    Thaao

    Joined:
    Jun 9, 2014
    Posts:
    54
    The reason I'm using things like MoveTowards and Lerp is becuase they will move toward a point with a cap. transform.Rotate doesn't really cap itself, so there's going to be that weird jerk at the end when I set the correct position once it's passed it. (for example, turning right, I check to see enough enough time has passed or if the rotation has reached or exceeded 45 degrees, then set it to exactly 45. Usually it's already past 45 by a tiny bit, and there's this jerk motion when it gets set to the proper rotation). Plus the motion just looks a lot more smooth when using Lerp.

    Is there a way to avoid the 'jerk' at least? I've always had this problem when getting moving things to stop at a very specific point.
     
  13. Ereous

    Ereous

    Joined:
    Aug 29, 2012
    Posts:
    163
    I'd help but I'm currently at work still. Here is how Lerp/Slerp works

    SLerp works on a time variable. 0 - 1.
    0 being your initial rotation. 1 being the targeted rotation. ( 0.5 would be the rotation between initial and target )
    360/45 = 8 rotation targets. 0.125f per 45 degrees.

    So you could possibly just increment or decrement by 0.125f. But I think this would only give you a right rotation. But you could have a left Slerp which reverses the initial and target to give it a left rotation.. Just play around with it.

    Code (CSharp):
    1. float currentRotation = 0.25f; //90 degree
    2. transform.localRotation=Quaternion.Slerp(new Vector3(0, 0, 0), new Vector3(0, 359, 0), currentRotation);
     
  14. NeverConvex

    NeverConvex

    Joined:
    Jun 26, 2013
    Posts:
    88
    My understanding is that Lerp/Slerp are set up so that they'll never 'truly' reach the target value, though they'll get arbitrarily close to it. Supposing I'm not misunderstanding things, there, you could do something like:

    if(distance_to_target_rotation > epsilon)
    do Lerp/Slerp
    else
    do N-step interpolation to desired end value

    where epsilon is some small value and "N-step interpolation" just means "Divide the steps from here to the current value up evenly and move along them one by one." A even/uniform N-step interpolation of that kind would have no jerky motion; it would hit the end point exactly (supposing the end point is a rational number).
     
  15. Thaao

    Thaao

    Joined:
    Jun 9, 2014
    Posts:
    54
    Thanks. I did play around with it, and it still seems to freak out when trying to approach 0. I noticed that in the game, the object/player SEEMED to be facing the right direction -- it was just the value of the rotation.y that was not going to 0 for some reason. I started to notice if I would pause and unpause the game quickly, it would suddenly return to 0 as intended.

    So I kept the Lerp in, but replaced the check to see if it has neared the intended angle with one that checks to see if enough time has passed. I don't really know how to write a formula to calculate how long the lerp should take, so I just played around until I found a good value that doesn't allow the player to turn extra. So thanks for BFG for suggesting I try with times. I think I'm understanding lerp more now but it doesn't seem to like to approach 0. I'd like to understand why, but I think it's beyond me right now and I'd rather get on with making my game :)

    Thank you everyone for your patience and great responses <3

    EDIT: Thanks, Neverconvex. Seems we posted at the same time almost haha. I will try something out like that later, though the weird behaviors it shows when it approaches 0 will probably prevent it from working properly. Right now it's working fine and I'm okay with it :) But if I need to change the turnSpeed later, it will be kinda annoying, so I'll keep this in mind.
     
  16. NeverConvex

    NeverConvex

    Joined:
    Jun 26, 2013
    Posts:
    88
    It's not really that hard to understand why Lerp doesn't actually approach the target value; if you look at the documentation for Vector3.Lerp, for example, it says it Lerps by a fraction, t.

    So, say t = 0.5, that you started at a value of 1 (ignore 3-space, for a second*), and that you wanted to interpolate to a value of 0. Then repeatedly calling Lerp on your current position would give you 1, divide by 2 to get 0.5, again to get 0.25, again to get 0.125, etc. The resulting sequences of values will get closer and closer to 0, but it will never exactly equal 0. I think this is what most implementations of Vector3.Lerp do, except they also smooth motion by Time.deltaTime, which adds some extra slow-down to the sequence of values and as a result makes the transformation look more natural as it proceeds in time, but doesn't change the qualitative fact that it'll never reach the target value.

    Just playing around with parameter values is a perfectly acceptable practice while learning, in my opinion. Nothing wrong with that, great short-cut to writing down explicit formula sometimes!

    *You could also just change the example to an interpolation from (1,1,1) to (0,0,0), and copy each number 3 times. That wouldn't change anything significant.
     
  17. NeverConvex

    NeverConvex

    Joined:
    Jun 26, 2013
    Posts:
    88
    Sorry for the double reply, noticed you edited to reply to me! You said:

    "I will try something out like that later, though the weird behaviors it shows when it approaches 0 will probably prevent it from working properly."

    I don't think this will be the case, though; I think the close-to-0 problems you're observing are caused by using Lerp/Slerp rather than a uniform interpolation. It's mostly a matter of picking N and epsilon (from the pseudo-code I suggested)---if you pick epsilon = 0.000000......001 (with, say, ten-thousand 0's) and N = 1, then you would run into the problems (although those are pretty silly values!), but the idea is hand-crafted to avoid the jittering issues you mention. The only real question is picking a reasonable value for epsilon, which should be easy---something between 0.1 and 1.5 would be fine, I'd think.

    EDIT: Really, you could even entirely replace Lerp/Slerp with the uniform/even interpolation I suggested as an end-step, so long as you set N to a large enough value (again in the pseudo-code). That would solve all of your problems in one go. Now that I think about it, it's a tad odd that there's no uniform interpolation built into Unity already.
     
    Last edited: Jul 8, 2014