How could I handle rotations when setting an objects angle with FromToRotation?

I’m working on a custom gravity system for my game. I’m trying to get my character to walk on walls and rotate to the direction the normal the character is standing on is facing.

So far I have it semi working. When the player comes across a wall, the gravity switches, and the player is standing on the wall.

Here is my custom gravity script:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Gravity : MonoBehaviour
{
    [SerializeField] Transform raycaster;

    public Vector3 upDirection;

    public bool isGrounded;
    public Rigidbody rb;


    void Start()
    {
        rb = GetComponent<Rigidbody>();
        upDirection = Vector3.up;
    }



    private void FixedUpdate()
    {
        Vector3 raycastPos = raycaster.position + (raycaster.up * 1.5f);

        RaycastHit down_hit;
        Physics.Raycast(raycastPos, -raycaster.up, out down_hit);

        RaycastHit forward_hit;
        Physics.Raycast(raycastPos, raycaster.forward, out forward_hit);

        RaycastHit back_hit;
        Physics.Raycast(raycastPos, -raycaster.forward, out back_hit);

        RaycastHit right_hit;
        Physics.Raycast(raycastPos, raycaster.right, out right_hit);

        RaycastHit left_hit;
        Physics.Raycast(raycastPos, -raycaster.right, out left_hit);



        if (down_hit.collider != null)
        {
            Debug.DrawLine(raycastPos, down_hit.point, Color.green);
        }

        if(forward_hit.collider != null)
        {
            Debug.DrawLine(raycastPos, forward_hit.point, Color.blue);
        }

        if(back_hit.collider != null)
        {
            Debug.DrawLine(raycastPos, back_hit.point, Color.cyan);
        }

        if (right_hit.collider != null)
        {
            Debug.DrawLine(raycastPos, right_hit.point, Color.red);
        }

        if (left_hit.collider != null)
        {
            Debug.DrawLine(raycastPos, left_hit.point, Color.yellow);
        }



        if (Vector3.Distance(forward_hit.point, raycastPos) < 1f && forward_hit.collider != null)
        {
            rb.velocity = Vector3.zero;
            upDirection = forward_hit.normal;
            transform.position = forward_hit.point;
        }
        
        else if(Vector3.Distance(back_hit.point, raycastPos) < 1f && back_hit.collider != null)
        {
            rb.velocity = Vector3.zero;
            upDirection = back_hit.normal;
            transform.position = back_hit.point;

        }

        else if (Vector3.Distance(right_hit.point, raycastPos) < 1f && right_hit.collider != null)
        {
            rb.velocity = Vector3.zero;
            upDirection = right_hit.normal;
            transform.position = right_hit.point;
        }

        else if (Vector3.Distance(left_hit.point, raycastPos) < 1f && left_hit.collider != null)
        {
            rb.velocity = Vector3.zero;
            upDirection = left_hit.normal;
            transform.position = left_hit.point;
        }

        else if (Vector3.Distance(down_hit.point, raycastPos) < 1f && down_hit.collider != null)
        {
            rb.velocity = Vector3.zero;
            upDirection = down_hit.normal;
            transform.position = down_hit.point;
        }

        if (transform.position == down_hit.point)
        {
            isGrounded = true;

        }
        else
        {
            isGrounded = false;
        }

        if (!isGrounded)
        {
            rb.velocity = -upDirection * 9.81f;
        }
    }



}

However, I’m having trouble rotating the character. I want the player to turn left and right, but with my current system, I am unable to figure out how to do so.

Rotation is handled in the PlayerMovement script:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;


public class PlayerMovement : MonoBehaviour
{
    Gravity gravity;
 
    [SerializeField] float speed;
    [SerializeField] float turnSpeed;


    void Start()
    {
        gravity = GetComponent<Gravity>();
    }


    void Update()
    {
        float forwardMove = Input.GetAxis("Vertical") * speed * Time.deltaTime;
        float sidewaysMove = Input.GetAxis("Horizontal") * speed * Time.deltaTime;

        float turnX = Input.GetAxis("Mouse X") * turnSpeed;

        transform.Rotate(new Vector3(0, 360, 0) * turnX * Time.deltaTime);
        transform.rotation = Quaternion.FromToRotation(Vector3.up, gravity.upDirection);


        transform.Translate(transform.forward * forwardMove + transform.right * sidewaysMove, Space.World);


    }
}

Does anyone have any suggestions on what to do? I tried to multiply Quaternion.FromToRotation(Vector3.up, gravity.upDirection) with transform.rotation, but that only worked when the player’s up direction was world up.

Maybe you want to look at tutorials and guides doing similar things.

Cat Like Coding has a movement system tutorial that includes custom gravity: Custom Gravity

And you can probably see in the tutorial is quickly grows to complicated and advanced stuff.

That said to answer your question, you need to be holding onto the player’s last ‘up’ direction, and steadily rotate that towards the current direction of gravity. Probably with RotateTowards: Unity - Scripting API: Quaternion.RotateTowards

Also you really don’t need all those if statements in your gravity script. When you see yourself writing repeated if-statements, you should really think about using collections. A simple array of Vector3 directions can be used here instead.

That said, gravity generally works the other way around. Other objects inform an object that it should be pulled towards it. When I did localised and custom gravity for a ball-rolling game, I just had trigger zones that would pull objects towards themselves.

Thank you! I’ll check the tutorial out! As for your suggestion, how would that work if I wanted to turn the character left and right? Normally when I try to rotate the transform.up of the character to the up direction of the normal, the other axis remained fixed. Meaning that the player’s forward axis will always face in the direction as the forward axis of the hit normals.

I was planning on getting rid of the repeated if statements later on when I got everything figured out and I could just focus on tidying up my code.

I was thinking of using the method of having objects pull the player, but I eventually decided that it didn’t really fit the need of my game.

Same sort of idea, if you want to turn the character, that needs to be done on your cached ‘up’ direction, and you can generate a rotation around an axis with Quaternion.AngleAxis.

1 Like

Okay, I did some testing with transform.rotation = Quaternion.AngleAxis(90 * turnX * Time.deltaTime, gravity.upDirection);, but for some reason, the rotation always comes out to 0 on the y axis. Meaning, that when I try to rotate, it “stutters” as it tries to keep the rotation equal to zero.

I tested it with transform.rotation = Quaternion.AngleAxis(90 * turnX * Time.deltaTime, Vector3.zero); and it still didn’t work. I commeneted out everything in my code relating to setting rotations, so there isn’t anything that is overriding this code.

Instead of using that big Gravity script to get the up normal you can use OnCollisionStay. And you shouldn’t really be moving a rigidbody with transform.Translate.

Try this:

using UnityEngine;
public class PlayerMovement : MonoBehaviour // A rigidbody controller for walking on walls
{  // Usage: Place this script onto a rigidbody capsule and freeze the rotation of the rigidbody and disable gravity
    Rigidbody rb;
    Vector3 up=Vector3.up;
    void Start()
    {
        rb=GetComponent<Rigidbody>();
    }

    void Update()
    {
        rb.MoveRotation(Quaternion.Slerp(transform.rotation,Quaternion.FromToRotation(transform.up,up)*transform.rotation,10*Time.deltaTime)); // smoothly adjust our up direction
        rb.MoveRotation(rb.rotation*Quaternion.Euler(0,Input.GetAxis("Mouse X"),0)); // and turn with mouse
    }

    void FixedUpdate()
    {
        rb.AddRelativeForce(new Vector3(Input.GetAxisRaw("Horizontal"),0,Input.GetAxisRaw("Vertical")).normalized*20);
        rb.AddForce(-transform.up*20f); // add local downwards thrust/gravity
    }

    void OnCollisionStay(Collision c)
    {
        up=c.GetContact(0).normal;
    }
    
    void OnCollisionExit(Collision c)
    {
        up=Vector3.up;
    }  
}

Oh my gosh thank you!

 transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.FromToRotation(transform.up, gravity.upDirection) * transform.rotation, 50 * Time.deltaTime); // smoothly adjust our up direction
 transform.rotation = transform.rotation * Quaternion.Euler(0, turnX, 0);

did the trick!

Quaternions are definitely something that I struggle with, but this code is pretty understandable!

Again, thank you!

Because you’re directly assigning the result of the AngleAxis. It’s only producing a small rotation, and you need to be adding that to your existing rotation using the quaternion * quaternion operator.

1 Like