• 3D
  • [SOLVED] Third Person Camera Issue

Hey there =)

I am currently trying to have a Third Person camera, seen in the old classic 3D platformers. But I am having issues with it.

I want the player to be PARTIALLY independent of the camera. This means that when the player is looking around the world ... the Player Character will not rotate with the Cameras direction. But if the player starts to move again the world's directions will have been altered by the direction of the Camera.

Now here comes a strange bit, I've got everything for 99% working. But the last 1% has to do with a strange bug.

For some reason, if my CamerPivot's Y becomes larger than -90 or is smaller than 90 it is performing normally. But when it is smaller than -90 or bigger than 90 it inverts itself and shows this by circling.

me only moving mouse UP

Here is my C# code : You can explain it to me via GDScript as well =)

using Godot;
using System;

public class PlayerControlScript : KinematicBody
{
    private Vector3 gravity = Vector3.Down * 30f;

    private float speed;
    [Export] public float crawlSpeed = 3f;
    private bool isCrawling = false;
    [Export] public float walkSpeed = 7f;
    [Export] public float sprintSpeed = 21f;
    [Export] public float accel = 3f;

    private Vector3 velocity;

    [Export] public float jumpSpeed = 14f;
    private bool canJump = false;

    private Spatial camPivot;
    private Camera camera;

    [Export] public float mouseSens = 0.3f;
    [Export] public float minAngle = 50f;
    [Export] public float maxAngle = 50f;

    private Spatial playerMesh;

    public override void _Ready()
    {
        camPivot = GetNode<Spatial>("../Player/CameraPivot");
        camera = GetNode<Camera>("../Player/CameraPivot/Camera");
        playerMesh = GetNode<Spatial>("../Player/PlayerMesh");


        Input.SetMouseMode(Input.MouseMode.Captured);

        speed = walkSpeed;
    }

    public void _GetInput()
    {
        bool isMoving = false;
        float verticalVel = velocity.y;
        velocity = new Vector3();
        
        if (Input.IsActionPressed("Forward"))
        {
            velocity -= camera.GlobalTransform.basis.z * speed;
            isMoving = true;
        }                                                    
        else if (Input.IsActionPressed("Backward"))          
        {                                                    
            velocity += camera.GlobalTransform.basis.z * speed;
            isMoving = true;
        }

        if (Input.IsActionPressed("Left"))                   
        {                                                    
            velocity -= camera.GlobalTransform.basis.x * speed;
            isMoving = true;
        }                                                    
        else if (Input.IsActionPressed("Right"))             
        {                                                    
            velocity += camera.GlobalTransform.basis.x * speed;
            isMoving = true;
        }

        velocity.y = verticalVel;
        velocity.LinearInterpolate(velocity, accel * GetPhysicsProcessDeltaTime());

        if (isMoving)
        {
            float angle = Mathf.Atan2(-velocity.x, -velocity.z);
            Vector3 playerRot = playerMesh.Rotation;
            playerRot.y = angle;
            playerMesh.Rotation = Vector3.Up * playerRot;
        }

        if (Input.IsActionPressed("Sprint"))
        {
            speed = sprintSpeed;
        }
        else if (Input.IsActionPressed("Crawl"))
        {
            speed = crawlSpeed;
            isCrawling = true;
        }
        else
        {
            speed = walkSpeed;
            isCrawling = false;
        }

        canJump = false;

        if (Input.IsActionJustPressed("Jump") && !isCrawling)
        {
            canJump = true;
        }

        if (Input.IsActionJustPressed("ui_cancel"))
        {
            Input.SetMouseMode(Input.MouseMode.Visible);
        }
    }

    public override void _UnhandledInput(InputEvent @event)
    {
        if (@event is InputEventMouseMotion eventMouseMotion)
        {
            eventMouseMotion = @event as InputEventMouseMotion;
            camPivot.RotateX(Mathf.Deg2Rad(-eventMouseMotion.Relative.y * mouseSens));
            camPivot.RotateY(Mathf.Deg2Rad(-eventMouseMotion.Relative.x * mouseSens));


            Vector3 cameraRotation = camPivot.RotationDegrees;
            cameraRotation.x = Mathf.Clamp(cameraRotation.x, -minAngle, maxAngle);
            cameraRotation.z = Mathf.Clamp(cameraRotation.z, 0, 0);

            camPivot.RotationDegrees = cameraRotation;
            GD.Print(new Vector3(cameraRotation.x, cameraRotation.y, cameraRotation.z));
        }
    }

    public override void _PhysicsProcess(float delta)
    {
        velocity.y += gravity.y * delta;
        _GetInput();
        velocity = MoveAndSlide(velocity, Vector3.Up);

        if (canJump && IsOnFloor())
        {
            velocity.y = jumpSpeed;
        }
    }
}

I've pasted everything in since I am not sure what might be causing this issue.

Thanks for the help =)

I'm not sure if this is the case, but maybe it's because a single node is being used for the rotation on the X and Y axes? I know that at certain angles, Godot flips 180 to -180 and I wonder if this is what is causing the issue, as I found it more often occurs when there is rotation on more than one axis. I might try using two nodes for the camera pivot, one for the X rotation and another for Y that is a child of the X rotation (or vice versa) and see if that fixes the issue.

@TwistedTwigleg said: I'm not sure if this is the case, but maybe it's because a single node is being used for the rotation on the X and Y axes? I know that at certain angles, Godot flips 180 to -180 and I wonder if this is what is causing the issue, as I found it more often occurs when there is rotation on more than one axis. I might try using two nodes for the camera pivot, one for the X rotation and another for Y that is a child of the X rotation (or vice versa) and see if that fixes the issue.

Yes, this seems to be the case. I can see inside of my Debug that Godot turns my 90 into a negative within 1 frame.

The follow-up question is now going to be: how do I Rotate the camera on the X and Y values if it is a child object of the actual pivot?

And since one object can't seem to handle 2 axes at once for rotation, how do I need them to be connected to the camera?

@TwistedTwigleg Fixed the Issue thanks to you =) !!

I added another Spatial this one called CameraPivotX

Set Up a new Spatial Variable calling for that Object ` private Spatial camPivotY; private Spatial camPivotX;

_Ready() camPivotY = GetNode<Spatial>("../Player/CameraPivotY"); camPivotX = GetNode<Spatial>("../Player/CameraPivotY/CameraPivotX"); `

Next Changed camPivot .RotateX(Mathf.Deg2Rad(-eventMouseMotion.Relative.y * mouseSens)); camPivot.RotateY(Mathf.Deg2Rad(-eventMouseMotion.Relative.x * mouseSens)); Into camPivotX.RotateX(Mathf.Deg2Rad(-eventMouseMotion.Relative.y * mouseSens)); camPivotY.RotateY(Mathf.Deg2Rad(-eventMouseMotion.Relative.x * mouseSens));

Next Changed Vector3 cameraRotation = camPivot.RotationDegrees; Into Vector3 cameraRotation = camPivotX.RotationDegrees;

And Lastly Changed

camPivot.RotationDegrees = cameraRotation; Into camPivotX.RotationDegrees = cameraRotation;