How to change transform.rotation

I have been looking into quaternions for two days now and I still have a headache. Here is what I am trying to do.

transform.rotation = Quaternion.Slerp(from.rotation, to.rotation, Time.time * speed);

I am trying to slerp a cube into rotating 90 degrees from whatever it's current position is. So for the code above, I can just set transform.position in place of "from.rotation" but I do not know how to take transform.position and add 90 degrees on the y axis so that I can use that in place of the "to.rotation". I know that I can declare 

public transform endRotation;
void Start () {
endRotation = transform.rotation;
}



But after that, how do I take endRotation and change it by 90 degrees? 

  • Jonathan Gonzalez(jgonzalez) replied

    This is what I use currently for rotation in a game I'm working on:

    [Range(1,10)]
    public float rotateSpeed;
    public Vector3 rotateAmount;
    private Transform transformObj;
    private float elapsedTime;
    private Quaternion startRot;
    private Quaternion endRot;
    
    
    void Start () {
    
    transformObj = GetComponent<Transform>();
    rotateAmount = new Vector3(transformObj.eulerAngles.x + rotateAmount.x,transformObj.eulerAngles.y + rotateAmount.y, transformObj.eulerAngles.z + rotateAmount.z);
    
    startRot = transformObj.rotation;
    endRot = Quaternion.Euler(rotateAmount);
    
    StartCoroutine("RotateObject");
    
    }
    IEnumerator RotateObject ()
    {
    
    while (elapsedTime < rotateSpeed)
    {
    elapsedTime += Time.deltaTime;
    transform.localRotation = Quaternion.Lerp(startRot, endRot, elapsedTime/rotateSpeed);
    
    yield return null;
    
    }
    
    }


    Essentially what this does is it takes in a Vector3 for the "rotateAmount", that allows me to specify what angle I want to rotate in a specific axis. There are a few float values for determining the speed of this rotation and the elapsed time which will be used later. I also have two Quaternions for the starting and ending rotation. 

    In the Start method the "transformObj" line is optional, I originally had it in there from before when testing with other transforms. Nonetheless it just gets the transform of the object this is assigned to. 

    rotateAmount then gets a new Vector3 based off the original euler angles of the transformObj and adds the value from the Vector3 axes setup. This allows us to use the original rotation of the objects rotation, but add an additional rotation value to it based off the rotate amount. 

    startRot is the starting rotation of the object and the ending rotation is essentially the rotateAmount calculate above, but since this is a Vector3 we use Quaternion.Euler to use it. 

    I used a Coroutine to rotate the object. It uses a while loop to check if the elapsedTime is less than the rotateSpeed. Since Quaternion.Lerp (or Quaternion.Slerp) uses a value of 1 for the last parameter you can use a fraction to control the speed. elapsedTime increases based off Time.delta time so that it smoothly increments and once it matches the same value as rotateSpeed then it'll stop. 

    Man that took a lot longer to explain than I thought but hopefully that makes sense. I called the coroutine to start using "StartCoroutine" up above in the Start method. Give it a try. 

  • Hunter & Tiff Eidmann(eithman) replied

    jgonzalez 

    Hey Johnathon. The code took me a long time to parse through but it taught me a lot! The only questions I have iare why are you adding rotateAmount to the transformObj.eulerAngles?  When you declared rotateAmount at the top, you did not assign it a value so it seems like you would effectively not be adding anything? I’m sure I’m just misunderstanding something, I just wanted to give the context of how I am interpreting it.

  • Jonathan Gonzalez(jgonzalez) replied

    eithman Oh sorry I should've clarified that a bit more. rotateAmount is not given a value through script since it is a public variable that can be modified through the inspector. It could be changed through the script, but it's meant to be used as something that can be changed in the inspector and used as a general script for rotating any object.  Really that is the key thing you where asking about. You wanted to be able to control the amount of rotation and you could do that by using a Vector3. 

    This is what the script looks like when you apply it to an object. I can modify the rotation for the X, Y and Z axis as needed. That then gets added to the original XYZ axis rotation of the object. So in this case I would be rotating the object an additional 10 degrees in the x axis and an additional 30 degrees for the Y axis. If you have any other questions let me know. 



  • Hunter & Tiff Eidmann(eithman) replied

    jgonzalez 

    I am wondering if I can skip the step in your code where you convert rotateAmount into a quaternion with  endRot = Quaternion.Euler (assignment); Could I instead use  Transform.localEulerAngles = Lerp (startRot, endRot, rotateSpeed);  to make the object rotate or did you convert it back to a quaternion for a specific reason?

  • Jonathan Gonzalez(jgonzalez) replied

    eithman Euler Angles are used as a Vector3 not a Quaternion. So you couldn't use them directly like that. That's why they were converted in that line. Also "Lerp" wouldn't be used alone, you could use it for a variety of things but in this case you would have to determine what type of "Lerping" you wanted to do. So Quaternion.Lerp or something like Vector3.Lerp

    The script can be simplified a bit, but you'll lose some flexibility. If you wanted to hard code everything it would be a bit simpler. 

  • Hunter & Tiff Eidmann(eithman) replied

    jgonzalez  The reason I originally started learning how to rotate is because I am working on a game where animals are walking around a map and when the bump into a wall they will turn a random amount horizontally and then keep walking. I wanted to create a method to achieve this.  I modified your code in an attempt to make that method and it works perfectly except that rather than smoothly lerping when it rotates, it instantly goes to the end rotation. I must be doing something incorrectly with the lerp but I am not sure what. I tried changing it to Slerp but had the same result. I also tried changing the 1 at the end of the lerp to .1 but still got the same result. Here is the modified code. Any idea what I did wrong with the lerp?


    private Vector3 rotateAmount;
    private Transform transformObj;
    private Quaternion startRot, endRot;
    private int x = 0, random1;
    
    void Start(){
    transformObj = GetComponent<Transform>();
    rotateAmount = new Vector3(transformObj.eulerAngles.x , transformObj.eulerAngles.y , transformObj.eulerAngles.z );
    startRot = transformObj.rotation;
    endRot = Quaternion.Euler(rotateAmount);
    }
    
    private void Update() {
    x++;
    Debug.Log(x);
    
    if (x == 250){
    Rotation();
    }
    
    if (x == 300){
    transform.localRotation = Quaternion.Lerp(startRot, endRot, 1);
    x = 0;
    }
    }
    
    void Rotation(){
    random1 = Random.Range(10, 100);
    
    rotateAmount += new Vector3(0,random1,0);
    startRot = transformObj.rotation;
    endRot = Quaternion.Euler(rotateAmount);
    }
    
    
    



  • Jonathan Gonzalez(jgonzalez) replied

    eithman In the Quaternion.Lerp the last parameter is essentially how much rotation influence the object will have. It's a bit tough to explain, but if it was set to 0 the object wouldn't rotate at all. With a value of 1 it will be completely rotated to match "endRot". If it was 0.5 it would be somewhere in between. Think of the "1" as being 100% rotated and anything between 0-100 is the amount of rotation match it'll have. 

    In your script you have it set to 1, so it automatically snaps to the end rotation. In my script I used a fraction. Think of that last value slowly incrementing to one over time and it'll affect how much rotation will be applied smoothly. 

    elapsedTime/rotateSpeed allows me to do that. I increment elapsed time by adding Time.deltaTime which may start off with say 0.1, the next time it loops through it's 0.2, etc. I used a while loop to do this until elapsedTime equals what I have setup for rotateSpeed. When those numbers match that value will be 100% exact, or in order words it'll be "1". 

    If you need further clarification let me know. 

  • Hunter & Tiff Eidmann(eithman) replied

    jgonzalez  I tried using the below code to Lerp an object along an axis. It is basically identical to the Lerp you used for the rotation but it is making the object teleport like before. Do you know what mistake I am making? For context, this is a co routine that has been called and the variables inside it were declared at the top.

    {

    beginning = transform.position;
    ending = transform.position;
    ending.x += -1f;


    while (elapsedTime < 5){
    elapsedTime += Time.deltaTime;
    transform.position = Vector3.Lerp(beginning, ending, elapsedTime/5);
    }


    }





  • Jonathan Gonzalez(jgonzalez) replied

    eithman The code you posted seems fragmented. With a coroutine we need to start by saying "IEnumerator" followed by the name of the coroutine. Based off the code you've provided, it wouldn't actually work so more than likely it's not being called at all. Here is how I would fix it:


    IEnumerator RotateObject ()
    {
    Vector3 beginning = transform.position;
    Vector3 ending = new Vector3(-1f, 0,0);
    while (elapsedTime < 5)
    {
    elapsedTime+= Time.deltaTime;
    transform.position = Vector3.Lerp(beginning, ending, elapsedTime/5);
    yield return null;
    }
    
    
    }

    A few issues from the code you posted. I assume you want to modify the X axis, and use -1 but this will not move the object -1 along the x axis. With Vector3.Lerp we need two Vector3's, we're not accessing the axes individually. So for that I created a new Vector3 with -1 for the X value and zero for both the Y and Z. 

    The rest is identical except that with a Coroutine we must include a "yield return" in some form. In this case I just added yield return null, which means it won't return anything and instead continue looping every frame. 


    You can then call this coroutine anytime you need something to rotate, but it's not called exactly as any other method. If I wanted to call this within a method I'd write something like this:


    void MyMethod ()
    {
    if(Input.GetButtonDown("Fire1"))
    {
    StartCoroutine("RotateObject");
    }
    }

    In this method I'm checking to see if I'm pressing a button down, if so we then start the coroutine. Once the while loop determines that elapsedTime is no longer less than 5 it'll stop looping and end. 


    Hope that helps, Coroutines are a bit different than traditional methods so they need to be written and called differently. If you need further clarification let me know. 

  • Hunter & Tiff Eidmann(eithman) replied

      It turns out that I had the yield return null in the wrong spot. Thank you for the detailed response!

  • Jonathan Gonzalez(jgonzalez) replied

    eithman Great! If you have any other questions let me know. 

  • Hunter & Tiff Eidmann(eithman) replied

    jgonzalez  I realized a problem I am running into is that transform.position moves the object in world space rather than local space and I need it to move in local space. I tried fixing this by changing transform.position to transform.forward . Unfortunately rather than making my animal lerp forward, it instantly rotates it 90 degrees counter clockwise. Any idea why this is happening?


    IEnumerator RotateObject()
    {

    speed = 0;
    beginning = transform.position;
    ending = transform.position;
    ending.x += -1f;

    while (elapsedTime < 5){
    elapsedTime += Time.deltaTime;
    transform.forward = Vector3.Lerp(beginning, ending, elapsedTime / 5);
    Debug.Log(transform.position);
    yield return null;
    }

    }


  • Jonathan Gonzalez(jgonzalez) replied

    eithman transform.forward is used when you want to utilize the Z axis of that object, it wouldn't allow you to change to local coordinates. Also realized in the past few posts we've gone from using rotation to position. I'm not sure if you're trying to just rotate an object or position it, but the concept will essentially be the same. So lets keep this simple to a few lines. 

    If I wanted to move an object along it's local coordinates I would use:  

       transform.localPosition = new Vector3(0,10,0);
    

    This would move the object along the local Y axis 10 units up. You could do something very similar with rotation, but with that we'd use Euler angles which are also in Vector3 format. This is how you would rotate the same object along the local Y axis:

    transform.localRotation = Quaternion.Euler(45,10,0);
    

    In this case I'm rotating the object 45 degrees along the local x axis, and 10 along the local y axis. Whether you want to rotate or move the object along it's local coordinates, that's how you would do it. In the coroutine you'd replace transform.forward with transform.localPosition (to move the object), or if you're using it to rotate it would be transform.localRotation

  • Hunter & Tiff Eidmann(eithman) replied

    jgonzalez  :,( I feel like I am never going to be finished with these issues.  At least this issue seems fairly easy. 

    With all of the previous lerping statements and while statements we have been using in the co routines, I keep running into the problem of where to put the yield return null; .  If I put it inside the while loop at the very bottom, the lerp works percectly. But if instead, I put the yield return null; at the bottom of the coroutine, outside of the while loop, the lerping does not work at all and the object teleports from it's start position to it's end position.  The reason I want to put the yield return null; at the bottom of the coroutine instead of at the bottom of the while, is because when I put it at the bottom of the while this causes the code to break from the coroutine and go through all of the code above it for as many cycles as the while loop is programmed to go through, which is hundreds. This is causing problems because the above code is being run when I do not want it to. I would like to have the lerp move the object from start to finish smoothly and then leave the coroutine. Do you know how to achieve this? 


     

     
    IEnumerator FindFood()
    {
    rotateAmount = new Vector3(0, transformObj.eulerAngles.y + 90, 0);
    startRot = transform.rotation;
    endRot = Quaternion.Euler(rotateAmount);
    
    while (elapsedTime < 5){
    elapsedTime += Time.deltaTime;
    transform.localRotation = Quaternion.Lerp(startRot, endRot, elapsedTime / 5);
    yield return null;
    }
    elapsedTime = 0;    
    }
  • Jonathan Gonzalez(jgonzalez) replied

    eithman Without the yield it'll execute the entirety of the while loop almost instantly. Adding yield return null essentially makes it work in a similar way to the update method. It waits a frame before going through the loop again. So it goes through the while loop once, waits a frame, goes through again, waits a frame and does this over and over until the condition used for the while loop is met. In this case that's the elapsed time being less than 5. 

    The way the coroutine was written before should work correctly. The while loop will only be looped when elapsed time is less than 5. Once it reaches 5 or greater it should get out of the loop and finish off the rest of the coroutine. 

    If you add a Debug.Log at the end where your coroutine should end you should see it appear once elapsedTime is at or greater than 5. Here is an example

    IEnumerator FindFood()
    {
    rotateAmount = new Vector3(0, transformObj.eulerAngles.y + 90, 0);
    startRot = transform.rotation;
    endRot = Quaternion.Euler(rotateAmount);
    
    while (elapsedTime < 5){
    elapsedTime += Time.deltaTime;
    transform.localRotation = Quaternion.Lerp(startRot, endRot, elapsedTime / 5);
    yield return null;
    }
    elapsedTime = 0;
    Debug.Log("Coroutine is completed");    
    }