Search code examples
c#unity-game-enginevectorquaternions

Ensure rotation takes the same amount of time regardless of angle to be rotated in Unity


I hope somebody can help. I have a script that rotates a globe, over which a plane (which is stationary) flies from 1 country to another. This all works fine and is achieved with the following script

while (!DestinationReached)
    {
        Vector3 planetToCountry = (Destination.localPosition - Vector3.zero);//.normalized; //vector that points from planet to country
        Vector3 planetToCamera = (camTr.position - tr.position).normalized; //vector that points from planet to camera/plane

        Quaternion a = Quaternion.LookRotation(planetToCamera);
        Quaternion b = Quaternion.LookRotation(planetToCountry);

        b = Quaternion.Inverse(b);

        newRotation = a * b;
        tr.rotation = Quaternion.Slerp(tr.rotation, newRotation, 0.01f);

        if (Approximately(tr.rotation, newRotation, 0.0001f)) //if here the plane has arrived
        {
            Debug.Log("Destination reached");
            DestinationReached = true;
        }
        yield return new WaitForEndOfFrame();
        
    }

It essentially calculates the angle between the plane (the camera is attached to the plane GO and views it from above) and the destination the globe needs to rotate to so that the plane looks as though it flies to the destination.

The issue I have is I need to make the flight time uniform regardless of the angle the globe must rotate, so lets say it must be 5 seconds, regardless if the plane flies from Paris to Ireland or Paris to Australia. Anybody have any ideas on how to do this.

I have to admit, I nicked this script for the web, as my Vector and Quaternion mathematics is hopeless :)


Solution

  • If you want to be flexible and e.g. add some easing at beginning and end but still finish within a fixed duration I would do it like this (I'll just assume here that your calculating the final rotation is working as intended)

    // Adjust the duration via the Inspector
    [SerializeField] private float duration = 5f;
    
    private IEnumerator RotateRoutine()
    {
        // calculate these values only once!
    
        // store the initial rotation
        var startRotation = tr.rotation;
    
        // Calculate and store your target ratoation
        var planetToCountry = (Destination.localPosition - Vector3.zero);
        var planetToCamera = (camTr.position - tr.position);
    
        var a = Quaternion.LookRotation(planetToCamera);
        var b = Quaternion.LookRotation(planetToCountry);
        b = Quaternion.Inverse(b);
    
        var targetRotation = a * b;
    
        if(duration <= 0)
        {
            Debug.LogWarning("Rotating without duration!", this);
        }
        else
        {
            // track the time passed in this routine
            var timePassed = 0f;
            while (timePassed < duration)
            {
                // This will be a factor from 0 to 1
                var factor = timePassed / duration;
                // Optionally you can alter the curve of this factor
                // and e.g. add some easing-in and - out
                factor = Mathf.SmoothStep(0, 1, factor);
       
                // rotate from startRotation to targetRotation via given factor
                tr.rotation = Quaternion.Slerp(startRotation, targetRotation, factor);
    
                // increase the timer by the time passed since last frame
                timePassed += Time.deltaTime;
    
                // Return null simply waits one frame
                yield return null;   
            }
        }
    
        // Assign the targetRotation fix in order to eliminate 
        // an offset in the end due to time imprecision
        tr.rotation = targetRotation;
    
        Debug.Log("Destination reached");
    }