Orbital behaviour in 3D, keeping a constant circular movement

I’m trying to make my character move around a tower (it’s a tower climb demo). For it i take only the horizontal input (A for left and D for right). I take only the X and Z components of the center of the tower to calculate directions. I can’t manage to get a perfect circular move using Rigidbody. I have only achieved a constant radius (distance from the player to the center) changing the position of the transform directly. Is it not possible to use Physics for this? It should be. So i’m missing something. I have tried all ForceModes btw.
I’ll leave my script here:

public class PlayerMovement : MonoBehaviour
{
    [SerializeField] private float moveSpeed = 5f; 
    [SerializeField] private Transform centerOfTower;

    private InputAction moveAction;
    private float radiusTower;
    private Rigidbody rb;
    private Vector2 movementInput = Vector2.zero;
    private float angle = 0f;
    private Vector3 tangent;
    private Vector3 cptForce;
    private void OnDrawGizmos()
    {
        Gizmos.color = Color.blue;
        Gizmos.DrawRay(transform.position, tangent*5f);
        Gizmos.color = Color.red;
        Gizmos.DrawRay(transform.position, cptForce*5f);
    }
    private void Awake()
    {
        moveAction = GetComponent<PlayerInput>().actions["Movement"];
        rb = GetComponent<Rigidbody>();
        radiusTower = GetDistanceToCenter();
    }
    private void FixedUpdate()
    {
        movementInput = moveAction.ReadValue<Vector2>(); 
        MovePlayer();
    }
    private void MovePlayer()
    {
        Vector3 directionToCenter = centerOfTower.position - transform.position;
        Vector3 directionXZ = new Vector3(directionToCenter.x, 0f, directionToCenter.z);
        Vector3 tangent = Vector3.Cross(-directionXZ, Vector3.up).normalized;
        transform.forward = Mathf.Sign(movementInput.x) * tangent;
        //rigidbody
        Vector3 vel = new Vector3(tangent.x * moveSpeed * movementInput.x, rb.velocity.y, tangent.z * moveSpeed * movementInput.x);
        Vector3 velXZ = new Vector3(vel.x, 0f, vel.z);

        //This makes the player get farther away from the center
        //rb.AddForce(directionXZ.normalized * (6.67384e-11f / Mathf.Pow(radiusTower, 2)), ForceMode.Force);

        //This makes the player get closer to the center
        //rb.AddForce(directionXZ.normalized * (velXZ.magnitude * velXZ.magnitude) / radiusTower, ForceMode.Acceleration);

        //This just does not work correctly
        //Vector3 cpt = directionXZ * (velXZ.magnitude * velXZ.magnitude) / radiusTower;
        //vel += cpt * Time.fixedDeltaTime;

        rb.velocity = vel;

        ////transform
        //angle += movementInput.x * moveSpeed * Time.deltaTime;

        //float x = centerOfTower.position.x + Mathf.Cos(angle) * radiusTower;
        //float z = centerOfTower.position.z + Mathf.Sin(angle) * radiusTower;
        //Vector3 nextPos = new Vector3(x, transform.position.y, z);

        //transform.position = nextPos;

        //Debug
        Debug.Log(GetDistanceToCenter());
        //Debug.Log("Cpt Force: " + (velXZ.magnitude * velXZ.magnitude) / radiusTower);
        //Debug.Log("Velocity: " + velXZ.magnitude);
        this.tangent = tangent;
        this.cptForce = directionXZ.normalized;
    }

    private float GetDistanceToCenter()
    {
        Vector3 playerPos = transform.position;
        playerPos.y = 0f; 

        Vector3 towerCenter = centerOfTower.position; 
        towerCenter.y = 0f; 
        return Vector3.Distance(playerPos, towerCenter);
    }
}

1 Answer

1

I think it is likely more trouble than it is worth to try to make a perfect circle by only manipulating the velocity.

I expect the issue is that moving the object along its tangent doesn’t keep it on the circle, it will always move further away (P1 below). The velocity needs to be modified by a centripetal acceleration towards the center (P2 below). According to Wikipedia, this acceleration is equal to tangent_speed^2 / radius.

image

Since you have directionToCenter, GetDistanceToCenter(), and velXZ.magnitude, you could calculate the velocity modification to stay on the orbital path. I’m still a bit doubtful that this will work with zero drift just due to the nature of the physics simulation. Calculating the point on the circle that you want to move to and using RigidBody.MovePosition may be a less frustrating solution.

Very usefull! Thanks for the help!