- Home /
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: https://youtu.be/XAKR_LDCCho). 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!
When you commented //this.transform.rotation = Quaternion.LookRotation(GetComponent().velocity, Vector3.up); The program loses its $$anonymous$$d and jumps around to all sorts of crazy places with this function.
Is that 'Up' meant to be a Vector3.up or transform.up? Just asking, since your bicycle does traverse around a sphere.
Answer by purpl3grape · Apr 04, 2021 at 11:35 AM
EDIT: I've updated the answer below with the latest test I did, along with a repo if you'd like to test: https://github.com/purpl3grape/FauxGravityTest
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
Thanks so much for your help! I now have the bicycle facing the waypoint direction and will be tweaking the code so that it doesn't jump rotations so suddenly when switching target waypoints. I definitely would not have figured this out without your help, as you introduced me to a lot of new functions and strategies (i.e. Vector3.dot). Hopefully, as I move out of newb status, I will get better at these things. Here is a video of the code working :). https://youtu.be/aQs1KgUZTUQ
Have a great day!
Answer by onlineteachingnow · Apr 04, 2021 at 12:12 PM
@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: https://youtu.be/fYm-DFri2FA
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.
I've created a simple demo I think the second (turn left/right) rotation should be a Local Rotation I've put together a test.
https://github.com/purpl3grape/FauxGravityTest https://www.youtube.com/watch?v=vGQqEfqVhdQ
We need to find the direction with respect to our local Y rotation. So if the waypoint is on our relative left, we turn left (0,-10,0) on localY, vice versa for turning right.
If it's "moving into the word" it's because the second rotation, makes it "go into the earth" as opposed rotate along the surface of the earth.
So your context, Figuring out if waypoint is left vs right of player can be done like (Taken from my personal experience) I'll see if I can reproduce a 'Way-point' like you mention, or if I can get a copy of the project I can try to incorporate the following, where WaypointPosition is Next Desired Position:
private void RotateBicycle(Transform trans)
{
DistanceToPlane = Vector3.Dot(tr.up, NextWaypointPosition - tr.position);
PointOnPlane = NextWaypointPosition - (tr.up * DistanceToPlane);
Quaternion q = Quaternion.LookRotation(PointOnPlane - tr.position, tr.up);
HeadingTransform.localRotation = Quaternion.Slerp(HeadingTransform.rotation, q, 0.1f);
}
https://www.youtube.com/watch?v=emzjoM7y0Fo and the git is updated for this.
Note, you will want to tweak Lerp value as you approach the waypoint as the angle differences become larger per change in movement due to a singularity when at the same waypoint position. So you may want to also tweak the $$anonymous$$Distance threshold to resolve this.