Rotating Game Object in Movement Direction on planet with Faux Gravity Unity 3D

Hi all,

I am modeling a bicycle traveling around the globe in 3D space. (see video of what I’m doing here: bicycle direction troubleshooting - YouTube). The problem is that I cannot figure out how to rotate the bicycle towards the direction it’s moving in or towards the next waypoint. I believe the problem is two-fold. In my faux gravity attractor script, I am setting the orientation of the bicycle to upright (i.e. perpendicular to the surface of the earth), however, I am then trying to change the bike’s rotation in another script. The second problem (I believe) is that I am trying to rotate in world space, whereas I need to rotate in local space such that it travels along the curvature of the earth and doesn’t rotate into the surface. I have been researching all day and trying a bunch of solutions, such as the following with comments on what happens when I use the code:

//this.transform.rotation = Quaternion.LookRotation(GetComponent<Rigidbody>().velocity, Vector3.up); The program loses its mind and jumps around to all sorts of crazy places with this function.
            //this.transform.rotation = Quaternion.LookRotation(_direction); //This is the closest I have to it working. However, the bicycle leans hard towards the earth and I want it to maintain the upright position.
            //transform.LookAt(_targetPosition, _earth.ReturnBodyUp()); //earth.ReturnBodyUp returns the local y axis of the game object, i.e. Perpendicular to the surface of the earth. With this function, the bike doesn't move at all.
            //this.transform.position = Vector3.RotateTowards(_currentPosition, _targetPosition, 0.01f, 0.0f); //The bike doesn't move at all because (I think), it's trying to rotate into the earth's surface and getting stopped by the collider.

Here is the faux gravity attractor script where I initially set the orientation of the bicycle:

public class FauxGravityAttractor : MonoBehaviour
{
    [SerializeField] float _gravity = -20;
    Vector3 _gravityUp;
    Vector3 _bodyUp;
    Vector3 _bodyForward;
    Quaternion _bodyRotationValue;
    public void Attract(Transform body)
    {
        _gravityUp = (body.position - this.transform.position).normalized;
        _bodyUp = body.up;
        _bodyForward = body.forward; //getting the local z axis position. 

        body.GetComponent<Rigidbody>().AddForce(_gravityUp * _gravity);
        Quaternion _targetRotation = Quaternion.FromToRotation(_bodyUp, _gravityUp) * body.rotation;
        body.rotation = Quaternion.Slerp(body.rotation, _targetRotation, 50 * Time.deltaTime);
    }

    public Quaternion ReturnBodyUp()
    {
        return _bodyRotationValue;
    }
}

and here is the script I use to move the bicycle along the various waypoints:

public class TransportPathing : MonoBehaviour
{
    TripConfig _tripConfig;
    TripSpawner _tripSpawner;
    List<Transform> _wayPoints;
    FauxGravityAttractor _earth;
    private Vector3 _original;
    private int _wayPointsIndex = 0;
    Vector3 _direction;

    void Start()
    {
        _tripSpawner = FindObjectOfType<TripSpawner>();
        _wayPoints = _tripConfig.GetWayPoints();
        _original = _wayPoints[_wayPointsIndex].transform.position;
        _earth = FindObjectOfType<FauxGravityAttractor>();
        this.transform.position = _original;
        //_direction = (_wayPoints[_wayPointsIndex].transform.position - this.transform.position).normalized;
    }

    void Update()
    {
        MoveTransport();
    }

    public void SetTripConfig(TripConfig tripConfigToSet)
    {
        this._tripConfig = tripConfigToSet;
    }

    private void MoveTransport()
    {
        if (_wayPointsIndex < _wayPoints.Count)
        {
            var _moveSpeed = _tripConfig.GetMoveSpeed();
            var _targetPosition = _wayPoints[_wayPointsIndex].transform.position;
            var _currentPosition = this.transform.position;
            var _movementThisFrame = _tripConfig.GetMoveSpeed() * Time.deltaTime;
            Vector3 _relativePos = _targetPosition - _currentPosition;
             

            this.transform.position = Vector3.MoveTowards(_currentPosition, _targetPosition, _movementThisFrame);
            //this.transform.rotation = Quaternion.LookRotation(GetComponent<Rigidbody>().velocity, Vector3.up); The program loses its mind and jumps around to all sorts of crazy places with this function.
            //this.transform.rotation = Quaternion.LookRotation(_direction); //This is the closest I have to it working. However, the bicycle leans hard towards the earth and I want it to maintain the upright position.
            //transform.LookAt(_targetPosition, _earth.ReturnBodyUp()); //earth.ReturnBodyUp returns the local y axis of the game object, i.e. Perpendicular to the surface of the earth. With this function, the bike doesn't move at all.
            //this.transform.position = Vector3.RotateTowards(_currentPosition, _targetPosition, 0.01f, 0.0f); //The bike doesn't move at all because (I think), it's trying to rotate into the earth's surface and getting stopped by the collider.
            
            if (Vector3.Distance(_currentPosition, _targetPosition) < 0.08)
            {
                _wayPointsIndex++;
            }
        }
        else
        {
            _tripSpawner.ManageLastWayPoint(true);
            Destroy(this.gameObject);
        }
    }
}

Any and all insights would be much appreciated. Thanks!

EDIT: I’ve updated the answer below with the latest test I did, along with a repo if you’d like to test: GitHub - purpl3grape/FauxGravityTest: Testing Rotations for given Faux Gravity.

Also another note. I’m not sure if you’re using the same transform to do the Faux Gravity.Attact(TransformA), and the MoveTransport() where it also uses transform.rotation = …

Because it’s not moving/ or it is fighting with its rotation, as the same transform is being assigned a rotation (different or not) by both of the Gravity.Attact and MoveTransport functions.

what you should do is multiply (DesiredRotationToYouWant x CurrentRotation) for either one, or best to just seperate it out into its own Player.Rotate() function to be clearer in the rotations happening to the player.

public class FauxGravityTest : MonoBehaviour
{
public Transform attractor;
public Vector3 _gravityUp;
public GameObject Waypoints;

private Transform NextWaypoint;
private void Awake()
{
    Waypoints = GameObject.FindGameObjectsWithTag("WayPoint");
}

void Update()
{
    //Gravity
    _gravityUp = (attractor.position - transform.position).normalized * (attractor.localScale.x / 4);//Some Speed Scale for larger objects
    if (Vector3.Distance(attractor.position, transform.position) > (attractor.localScale.x / 2 + transform.localScale.y))
        transform.position += _gravityUp * Time.deltaTime;
        
    //X-Z Rotation
    Quaternion _targetRotation = Quaternion.FromToRotation(-transform.up, _gravityUp) * transform.rotation;
    transform.rotation = Quaternion.Slerp(transform.rotation, _targetRotation, 50 * Time.deltaTime);

    RotateBicycle();
    //X-Z Inputs Movement
    if (Input.GetKey(KeyCode.W))
    {
        transform.position += transform.forward;
    }
    else if (Input.GetKey(KeyCode.S))
    {
        transform.position -= transform.forward;
    }
    else if (Input.GetKey(KeyCode.D))
    {
        transform.position += transform.right;
    }
    else if (Input.GetKey(KeyCode.A))
    {
        transform.position -= transform.right;
    }

    //Local Y rotation Inputs
    if (Input.GetKey(KeyCode.Q))
    {
        transform.localRotation *= Quaternion.Euler(0, 10, 0);
    }
    else if (Input.GetKey(KeyCode.E))
    {
        transform.localRotation *= Quaternion.Euler(0, -10, 0);
    }

}

float DistanceToPlane;
Vector3 DistToWaypoint;
Vector3 PointOnPlane;
Quaternion q;
int waypointIndex = 0;
private void RotateBicycle()
{
    NextWaypoint = Waypoints[waypointIndex].transform;
    DistToWaypoint = NextWaypoint.position - transform.position;
    if (DistToWaypoint.magnitude < 2)
    {
        if (waypointIndex == Waypoints.Length -1)
        {
            waypointIndex = 0;
            Debug.Log("Starting Waypoint");
        }
        else
        {
            waypointIndex++;
            Debug.Log("Next Waypoint");
        }
    }
    DistanceToPlane = Vector3.Dot(transform.up, DistToWaypoint);
    PointOnPlane = NextWaypoint.position - (transform.up * DistanceToPlane);
    q = Quaternion.LookRotation(PointOnPlane - transform.position, transform.up);

    transform.localRotation = Quaternion.Slerp(transform.rotation, q, 0.2f);
}

}

So perhaps changing just the MoveTransport rotation line to:
transform.rotation = Quaternion.LookRotation(_direction, transform.up) x transform.rotation;
may do the trick

@purpl3grape Thanks for the two great thoughts. I actually didn’t know the difference between transform.up and Vector3.up, so that definitely would have contributed to the problem. Your second point was also correct. I hadn’t actually referenced or worked with the rotation that I was implementing in the faux gravity script. I went ahead and implemented the code that you recommended, but unfortunately, it’s still not working. The bike moves a bit but then stops. It’s also weirdly on its side. Here’s a video of the behavior: still stuck - YouTube

The following is what I changed in Faux Gravity

        body.GetComponent<Rigidbody>().AddForce(_gravityUp * _gravity);
        Quaternion _targetRotation = Quaternion.FromToRotation(_bodyUp, _gravityUp) * body.rotation;
        body.rotation = Quaternion.Slerp(body.rotation, _targetRotation, 50 * Time.deltaTime);
        _currentObjectRotation = body.rotation; //I have also set this to _targetRotation, which produces the same behavior. 
    }

    public Quaternion ReturnCurrentRotation()
    {
        return _currentObjectRotation;
    }

And what I changed in the pathing script:

        _direction = (_wayPoints[_wayPointsIndex].transform.position - this.transform.position).normalized; //The direction to the next waypoint

            this.transform.rotation = Quaternion.LookRotation(_direction, transform.up) * _earth.ReturnCurrentRotation(); //Sets the direction to look in equal to the normalized vector to the next waypoint. Multiplies by the current object rotation as set in the faux gravity script.