Rotating a rigidbody player to face movement direction with 360 degree movement

Hi, I’m really struggling with rotating my rigidbody character’s forward to the movement forward, while also being able to run through a loop de loop upside down. I’ve tried a bunch of different variations of Quaternion.LookRotation, transform.LookAt, transform.RotateAround etc to not much luck. Currently I’ve just got it rotating towards the InputDirection since trying to make it face it’s velocity just caused in to constantly spin. The grey debug line is the forward direction. The rotation on the rigidbody is locked so it won’t fall over from the force.

Example with and without player rotating to movement dir

The Movement Script:

public bool Grounded = false;
public Vector3 GroundNormal { get; set; }

public bool RotateToFacingCondition = true;
Rigidbody PlayerRigidbody;


   
Vector3 Gravity;
Vector3 JumpForce;

float FallOffTimer = 0.0f;

public GameObject cameraview;

Vector3 MovementDirection;
public float Speed = 5;

public AnimationCurve AnimCurve;
public float AnimCurveTime = 0.0f;

RaycastHit info = new RaycastHit();



void Start()
{
   
    PlayerRigidbody = this.GetComponent<Rigidbody>();
    
    Gravity = new Vector3(0, -1.25f, 0);
  
  
}


// Update is called once per frame
//  void FixedUpdate()
private void Update()
{
    PlayerInput();
    
}

private void FixedUpdate()
{
    SurfaceAlignment();
    GroundPhysics();
}


void PlayerInput()
{
   
    float vertinp = Input.GetAxis("Vertical"); float horinp = Input.GetAxis("Horizontal");

 
        //transform.rotation = Quaternion.Euler(0, Mathf.Atan2(horinp, vertinp) * Mathf.Rad2Deg, 0);
    // PlayerRigidbody.MoveRotation(transform.rotation *  Quaternion.Euler(0, vertinp, 0));

 
    Vector3 moveInp = new Vector3(horinp, 0, vertinp);

    if (moveInp != Vector3.zero)
    {
        Vector3 transformedInput;
        transformedInput = Quaternion.FromToRotation(Camera.main.transform.up,info.normal) * (Camera.main.transform.rotation * moveInp);
        transformedInput = transform.InverseTransformDirection(transformedInput);
        transformedInput.y = 0.0f;
        MovementDirection = transformedInput;

       
    }
    else
    {
        //Debug.Log ("InputNull");
        Vector3 transformedInput = Quaternion.FromToRotation(Camera.main.transform.up, info.normal) * (Camera.main.transform.rotation * moveInp);
        transformedInput = transform.InverseTransformDirection(transformedInput);
        transformedInput.y = 0.0f;
        MovementDirection = transformedInput;
       
    }

}


void SurfaceAlignment()
{
    Grounded = false;
 
    Ray ray = new Ray(transform.position, -transform.up);
    
    info = new RaycastHit();
    Quaternion RotationRef = Quaternion.Euler(0, 0, 0);

    //tag ground
    Debug.DrawRay(ray.origin, ray.direction * 1.5f, Color.cyan);
    if (Physics.Raycast(ray, out info, 1.5f))
    {
        Grounded = true;
        Debug.Log("rot");
        RotationRef = Quaternion.Slerp(transform.rotation, Quaternion.FromToRotation(transform.up, info.normal) * transform.rotation, AnimCurve.Evaluate(AnimCurveTime));
        float angle = Quaternion.Angle(transform.rotation, RotationRef);
        
        Debug.Log(angle);
        if (angle <= 30)
        {
            transform.rotation = RotationRef;
        }
        FallOffTimer = 0;
        /*
        if (PlayerRigidbody.linearVelocity.magnitude > 4.5)
        {
            PlayerRigidbody.useGravity = false;
        }
        else
        {
    }
    else
    {
        
        RotationRef = Quaternion.Slerp(transform.rotation, Quaternion.FromToRotation(transform.up, Vector3.up) * transform.rotation, FallOffTimer);

        transform.rotation = RotationRef;
        FallOffTimer += Time.deltaTime;
    }
}

void GroundPhysics()
{
   // MovementDirection = transform.TransformDirection(MovementDirection);

    
    Vector3 CurrentVelocity = PlayerRigidbody.linearVelocity;
  
    Vector3 TargetVelocity = MovementDirection;
    TargetVelocity *= Speed; 

    TargetVelocity = transform.TransformDirection(TargetVelocity);

    Vector3 VelocityChange = (TargetVelocity - CurrentVelocity);
    if (Grounded == false || PlayerRigidbody.linearVelocity.magnitude < 4.5)
    {

    VelocityChange += (Physics.gravity * 0.75f);
    }
     VelocityChange = Vector3.ClampMagnitude(VelocityChange, 1); 
                                                                 

    if (RotateToFacingCondition)
    {

        Quaternion targetRotation = Quaternion.LookRotation(MovementDirection);
        transform.localRotation = Quaternion.Slerp(transform.rotation, targetRotation, Time.deltaTime * 10f);
    }

    PlayerRigidbody.AddForce(VelocityChange, ForceMode.VelocityChange);


}

Any help or advice would be wonderful, as I’m pretty bad with 3D math. Thanks!

Firstly, if you’re using a rigidbody, then you want to rotate it via the rigidbody and not the transform component.

Then to rotate it towards your velocity heading, accounting for ‘up’, you take your current heading and the heading of your velocity, get the rotation between the two (Quaternion.FromToRotation), work out your final rotation, and then rotate towards that.

To illustrate the concept:

Vector3 currentHeading = GetCurrentHeading(); // ensure normalized
Vector3 velocityHeading = rigidbody.velocity.normalized;

Quaternion currentRotation = rigidbody.rotation;
Quaternion fromToRotation = Quaternion.FromToRotation(currentHeading, velocityHeading);
Quaternion targetRotation = currentRotation * fromToRotation;

Quaternion rotateTowards = Quaternion.RotateTowards(currentRotation, targetRotation, someMaxDelta);
rigidbody.rotation = rotateTowards; // or Rigidbody.MoveRotation

You can run the delta value forQuaternion.RotateTowards through an animation curve to smooth out the rotation as well.

It works best when you maintain your own Vector3 for the players current heading, or potentially what the current ‘up’ value is, and update via the described method.

Hey, thanks for the tip about the rigidbody/transform rotation. As for the other bit, could you extrapolate on what you mean by heading? So far I’ve tried using Rigidbody.transform.forward.normalised which somewhat works but is very jittery and leans slightly, the MovementDirection itself, the raycast normal, the previous velocity etc and I’m kind of at a loss. Also not too sure if maybe my camera control is affecting its effectiveness…
Thanks!

Rotate the rigidbody with MoveRotation. It won’t be smooth if you rotate it by changing its transform or setting the rigidbody rotation directly.

The rotate towards probably just needs to account for time. Ergo, someMaxDelta = someValue * Time.deltaTime;, alongside the advice above.

As for the heading, it depends on the project. The last time I did something like this, it was to orient the camera for a ball-rolling puzzle game, which included variable gravity. The camera would need to orient towards ‘up’ correctly when it changed. So in that case, the ‘heading’ was the current direction of gravity.

Presuming you’re making some kind of Sonic game, and have some means to determine what ‘up’ is, then I think you’ll want to use a direction that is perpendicular to ‘up’, pointing inline with the rigidbody’s velocity. This is where a double Vector3.Cross comes in.

For example:

Vector3 up = GetUpDirection();
Vector3 forwards = rigidbody.velocity.normalized;

Vector3 crossRight = Vector3.Cross(up, forwards);
// this points inline with velocity, prependicular to 'up'
Vector3 heading = Vector3.Cross(crossRight, up); 

Some code and an image to illustrate the concept:

public class TestMonoBehaviour : MonoBehaviour
{
	private void OnDrawGizmos()
	{
		Vector3 up = transform.up;
		Vector3 forward = Vector3.forward;
		Vector3 crossRight = Vector3.Cross(up, forward);
		Vector3 crossForward = Vector3.Cross(crossRight, up);

		Color c = Gizmos.color;

		Gizmos.color = Color.green;
		Gizmos.DrawRay(transform.position, up);

		Gizmos.color = Color.blue;
		Gizmos.DrawRay(transform.position, forward);

		Gizmos.color = Color.red;
		Gizmos.DrawRay(transform.position, crossRight.normalized);

		Gizmos.color = Color.yellow;
		Gizmos.DrawRay(transform.position, crossForward.normalized);

		Gizmos.color = c;
	}
}

Though a Sonic style game needs a REALLY good understanding of how to use vectors and quaternions, probably more than my current understanding quite frankly.

Better brush up on my 3D math primer..

As for the rotation, still only half luck, It has it’s proper delta now but no dice. Either the heading and the velocity heading are the exact same, or I swap the forwards to the rigidbodies forward which kinda sorta works minus constantly leaning and overshooting, adding the forward to the heading half works but is very slow and not fully synced, etc. Been using my raycast normal and my transforms up as up which both seem to somewhat work?


Had one of my friends who is more math inclined look over it and help me with it for a few hours but they didn’t get too far either with it, so maybe I should try out MovePosition or a kinematic controller. As a sidenote, do you know any good programs out there to visualize this sort of stuff?
Thanks for your continued help :slight_smile:

… have you tried… Unity3D? :slight_smile: That’s what Spiney is doing with the OnDrawGizmos() stuff above. You can really go nuts with that.

Blending multiple axes of motion like this, eg, being able to look in any direction as you go around a loop, is always brain-bending because of the shifting coordinate systems involved. Sometimes parenting objects below each other is an answer, but other times that may be unsuitable, depending on what you’re trying to do exactly.

One way to make it easier is to make some tooling to pass your player through the loop based on one set of controls, eg, make your Z / X buttons go forward and backward through the loop path, letting you iterate and test your vectors at each point in space.

You might find some insight from the approaches used for walking on a spherical world, for which there are many tutorials out there, such as those by Sebastian Lague.

Was kinda wanting to know if there was something like mathlab or something for easy visualization, but doing it in engine makes works just as well.

Meanwhile, for insights, I’ve dug through at least a dozen other third person/gravity based projects, turns out most of them just sidestep it and rotate a player model instead while keeping the rigidbody only rotating towards the ground. Not quite what I wanted, but I guess it’ll be easier and cause less headaches in the long run.

Games rarely try to do what is physically or pedantically correct.

Games are always some form of Potemkin Village designed to yield a particular player experience.

A huge part of that experience is the millisecond-to-millisecond mapping of player input to result, such as facing direction, motion, etc.