So I’ve pretty much run up against the wall with my understanding of rotations.
I’m working on a fun little ball-rolling puzzle platformer, of which has gravity zones of various shapes (spherical, cylindrical, etc), and some of which add to normal gravity, and some that completely replace it.
Expectedly you’d want the camera to re-orient itself so that ‘up’ is with respect to this new gravity. That in itself hasn’t been too difficult, but I’ve had one issue I haven’t been able to overcome: if I go from one gravity direction to that of the opposite orientation (or roughly opposite), the camera flips around, facing ‘backwards’ with respect to the direction you came from.
Currently the set up is: one game object (GravityPointer) that immediately points ‘up’ to the current gravity direction, and rotates on this axis to player input; plus another game object (CameraOrientor) that rotates to match this one over time with a ‘Quaternion.SmoothDamp’ like implementation, with a cinemachine free-look camera that is locked to said object. Said camera only moves up and down itself, with the horizontal rotation being handled by the GravityPointer.
The hierarchy is as such:
In the video you can see GravityPointer snap to the opposite direction.
And this is the present code for the GravityPointer (ignore the Odin stuff):
namespace HIAS.Spheres
{
using UnityEngine;
using Cinemachine;
using Sirenix.OdinInspector;
using LizardBrainGames;
using HIAS.Physics;
[HideMonoScript]
public class GravityPointer : MonoBehaviour
{
#region Inspector Fields
[BoxGroup("R", LabelText = "References:")]
[SerializeField]
private SphereController sphereController;
[BoxGroup("R")]
[SerializeField]
private LocalRigidbody localRigidbody;
[BoxGroup("R")]
[SerializeField]
private CinemachineInputProvider inputProvider;
[MemberNameBoxGroup("X")]
[SerializeField, HideLabel]
private AxisState cameraYAxis = new(minValue: -180, maxValue: 180, wrap: true, rangeLocked: false, maxSpeed: 300f, accelTime: 0.1f, decelTime: 0.1f, name: null, invert: true);
#endregion
#region Internal Members
private Quaternion gravityAlignment = Quaternion.identity;
private Vector3 gravityForward = Vector3.forward;
private Quaternion lastFinalRotation = Quaternion.identity;
#endregion
#region Unity Callbacks
private void Awake()
{
cameraYAxis.SetInputAxisProvider(axis: 0, inputProvider);
}
private void Start()
{
UpdatePointerPosition();
UpdatePointerDirection();
}
private void LateUpdate()
{
UpdatePointerPosition();
UpdatePointerDirection();
}
#endregion
#region Gravity Pointer Methods
private void UpdatePointerPosition()
{
transform.position = sphereController.transform.position;
}
private void UpdatePointerDirection()
{
Vector3 gravityDirection = localRigidbody.GravityDirection;
float deltaTime = Time.deltaTime;
cameraYAxis.Update(deltaTime);
gravityAlignment = Quaternion.FromToRotation(gravityAlignment * Vector3.up, -gravityDirection) * gravityAlignment;
Quaternion orbitRotation = Quaternion.AngleAxis(cameraYAxis.Value, -gravityDirection);
Quaternion finalRotation = orbitRotation * gravityAlignment;
Quaternion difRot = finalRotation * Quaternion.Inverse(lastFinalRotation);
gravityForward = difRot * gravityForward;
Debug.DrawRay(transform.position, gravityForward * 3f, Color.blue);
Quaternion lookRotation = Quaternion.LookRotation(gravityForward, -gravityDirection);
transform.rotation = lookRotation;
lastFinalRotation = finalRotation;
}
#endregion
}
}
This is one of many attempts, with this one attempting to maintain a consistent ‘forward’ direction over time by rotating a direction over time by using the difference in overall rotation between frames, though the issue still happens.
I’d appreciate any pointers in the right direction here, as my understanding of Quaternions only covers the basics. Hell I imagine there’s a simpler implementation here. Cheers folks.