Custom character controller issue

After many attempts to creating a custom character controller, I made what i needed to, however i ran into some bugs that I’m not sure how to fix, or why they are happening. If i don’t add a parent body to the camera, the script will do the mouse look and movement just fine, however if the body parent is there (such as a capsule) it will either freeze mouse looking into only being able to utilize y. Or the capsule will randomly spin like crazy out of control and stop movement. Other wise with no body the cam. does what it should. Here is the code.

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

[AddComponentMenu("Camera/Simple Smooth Mouse Look")]
public class CustomFP : MonoBehaviour {

    Vector2 _mouseAbsolute;
    Vector2 _smoothMouse;

    public Vector2 clampInDegrees = new Vector2(360, 180);
    public bool lockCursor;
    public Vector2 sensitivity = new Vector2(2, 2);
    public Vector2 smoothing = new Vector2(3, 3);
    public Vector2 targetDirection;
    public Vector2 targetCharacterDirection;

    public GameObject characterBody;


    public float speed = 6.0F;
    public float jumpSpeed = 8.0F;
    public float gravity = 20.0F;
    private Vector3 moveDirection = Vector3.zero;

    // Use this for initialization
    void Start () {

        targetDirection = transform.localRotation.eulerAngles;

        if (characterBody) targetCharacterDirection = characterBody.transform.localRotation.eulerAngles;
       
    }
   
    // Update is called once per frame
    void Update () {

        Screen.lockCursor = lockCursor;

        var targetOrientation = Quaternion.Euler(targetDirection);
        var targetCharacterOrientation = Quaternion.Euler(targetCharacterDirection);

        var mouseDelta = new Vector2(Input.GetAxisRaw("Mouse X"), Input.GetAxisRaw("Mouse Y"));

        mouseDelta = Vector2.Scale(mouseDelta, new Vector2(sensitivity.x * smoothing.x, sensitivity.y * smoothing.y));

        _smoothMouse.x = Mathf.Lerp(_smoothMouse.x, mouseDelta.x, 1f / smoothing.x);
        _smoothMouse.y = Mathf.Lerp(_smoothMouse.y, mouseDelta.y, 1f / smoothing.y);

        _mouseAbsolute += _smoothMouse;

        if (clampInDegrees.x < 360)
            _mouseAbsolute.y = Mathf.Clamp(_mouseAbsolute.y, -clampInDegrees.y * 0.5f, clampInDegrees.y * 0.5f);

        var xRotation = Quaternion.AngleAxis(-_mouseAbsolute.y, targetOrientation * Vector3.right);
        transform.localRotation = xRotation;

        if (clampInDegrees.y < 360)
            _mouseAbsolute.y = Mathf.Clamp(_mouseAbsolute.y, -clampInDegrees.y * 0.5f, clampInDegrees.y * 0.5f);

        transform.localRotation *= targetOrientation;

        if (characterBody)
        {
            var yRotation = Quaternion.AngleAxis(_mouseAbsolute.x, Vector3.up);
            characterBody.transform.localRotation = yRotation;
            characterBody.transform.localRotation *= targetCharacterOrientation;
        }
        else
        {
            var yRotation = Quaternion.AngleAxis(_mouseAbsolute.x, transform.InverseTransformDirection(Vector3.up));
            transform.localRotation *= yRotation;
        }
        CharacterController controller = GetComponent<CharacterController>();
        if (controller.isGrounded)
        {
            moveDirection = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));
            moveDirection = transform.TransformDirection(moveDirection);
            moveDirection *= speed;
            if (Input.GetButton("Jump"))
                moveDirection.y = jumpSpeed;
        }
        moveDirection.y -= gravity * Time.deltaTime;
        controller.Move(moveDirection * Time.deltaTime);
    }
}

Hello,

My advice would be to always have your script on the higher parent of your character and colliders as children.

I tested your code and it only occur when characterBody point to his own GameObject, right ? (It disable Y axis).

Years ago I used to have an old optical mouse which occasionnaly would spin like crazy in any FPS, the spin isn’t from your mouse ?

no, my scroll doesn’t even function on my mouse lol but yes, the x axis is always disabled when a body is on but without the body it works just fine

Bump

Bump

bump

Hi there,

in this piece of code:

if (characterBody)
        {
            var yRotation = Quaternion.AngleAxis(_mouseAbsolute.x, Vector3.up);
            characterBody.transform.localRotation = yRotation;
            characterBody.transform.localRotation *= targetCharacterOrientation;
        }

you set localRotation to yRotation, doesn’t this effectively throw away the xRotation work you did a few lines up?

This only happens inside this side of the conditional, on the other side you use the existing value.

I’ll have a closer look once I get home from work and can boot up Unity, easier for me to understand with debugging line by line.

Kind Regards,
Joe

1 Like

First off, thank you for the response! I really appreciate it, Im gonna test around with a few things that might fix that, but it seems what you are saying is exactly what is occuring, i’ll continue to see what i can figure out, if you find anything when you get home please keep me posted, thanks so much!

Okay, so that seemed to solve the main issue but there is the issue of the cam, its doing bounces when walking forward or backwards at certain cam angles, such as when i look down and walk backwards or when i look up and walk forward, so i tried putting a rigid body on with no gravity and making it kinematic but the bounce still remains. I think it has something to do with the character controller component, because i used gizmos to check and see if it was maybe the capsule doing it, but no collision is occurring with it.

I’ve tested the code, just to confirm I have the same setup as you.

You have a parent Gameobject with a capsule mesh.

Then as a child of that object you have the camera. The camera has the CustomFP script attached with a character controller?

When I play my scene, I see the camera moving around but the parent object is stationary, the rotation happens around the parents Axis so the further away you move the wilder it gets as you rotate.

I think the bumping problem is down to this piece of code:

 moveDirection = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));
            moveDirection = transform.TransformDirection(moveDirection);

What you are doing here is adding a Y value to movement vector because of the direction of the camera. This in turn means the controller is being propelled into the air a little or being pushed into the ground depending on the input.

I think part of the problem is there are conflicting actions here that are confusing the matter. Camera Orientation and Movement/Rotation of the body.

It might be simpler to split this out into two scripts, each with its own purpose.

Try this:

Add the character controller and both of these scripts to the parent capsule, which are just quick refactors of your existing code. And place the camera inside as a child.

Move:

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

public class Move : MonoBehaviour
{

    Vector2 _mouseAbsolute;
    Vector2 _smoothMouse;

    public Vector2 clampInDegrees = new Vector2(360, 180);
    public bool lockCursor;
    public Vector2 sensitivity = new Vector2(2, 2);
    public Vector2 smoothing = new Vector2(3, 3);
    public Vector2 targetDirection;
    public Vector2 targetCharacterDirection;

    public GameObject characterBody;


    public float speed = 6.0F;
    public float jumpSpeed = 8.0F;
    public float gravity = 20.0F;
    private Vector3 moveDirection = Vector3.zero;

    // Use this for initialization
    void Start()
    {
        targetDirection = transform.localRotation.eulerAngles;
    }

    // Update is called once per frame
    void Update()
    {

        Screen.lockCursor = lockCursor;

        var targetOrientation = Quaternion.Euler(targetDirection);

        var mouseDelta = new Vector2(Input.GetAxisRaw("Mouse X"), Input.GetAxisRaw("Mouse Y"));

        mouseDelta = Vector2.Scale(mouseDelta, new Vector2(sensitivity.x * smoothing.x, sensitivity.y * smoothing.y));

        _smoothMouse.x = Mathf.Lerp(_smoothMouse.x, mouseDelta.x, 1f / smoothing.x);

        _mouseAbsolute += _smoothMouse;

        var yRotation = Quaternion.AngleAxis(_mouseAbsolute.x, transform.InverseTransformDirection(Vector3.up));
        transform.localRotation = yRotation;

        CharacterController controller = GetComponent<CharacterController>();
        if (controller.isGrounded)
        {
            moveDirection = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));
            moveDirection = transform.TransformDirection(moveDirection);
    
            moveDirection *= speed;
            if (Input.GetButton("Jump"))
                moveDirection.y = jumpSpeed;
        }
        moveDirection.y -= gravity * Time.deltaTime;
        controller.Move(moveDirection * Time.deltaTime);
    }
}

And

Look:

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

public class Look : MonoBehaviour
{

    Vector2 _mouseAbsolute;
    Vector2 _smoothMouse;

    public Vector2 clampInDegrees = new Vector2(360, 180);
    public bool lockCursor;
    public Vector2 sensitivity = new Vector2(2, 2);
    public Vector2 smoothing = new Vector2(3, 3);
    public Vector2 targetDirection;

    public Transform cameraTransform;

    // Use this for initialization
    void Start()
    {

        targetDirection = cameraTransform.localRotation.eulerAngles;
    }

    // Update is called once per frame
    void Update()
    {
        var targetOrientation = Quaternion.Euler(targetDirection);
        var mouseDelta = new Vector2(Input.GetAxisRaw("Mouse X"), Input.GetAxisRaw("Mouse Y"));

        mouseDelta = Vector2.Scale(mouseDelta, new Vector2(sensitivity.x * smoothing.x, sensitivity.y * smoothing.y));

        _smoothMouse.y = Mathf.Lerp(_smoothMouse.y, mouseDelta.y, 1f / smoothing.y);

        _mouseAbsolute += _smoothMouse;

        _mouseAbsolute.y = Mathf.Clamp(_mouseAbsolute.y, -clampInDegrees.y * 0.5f, clampInDegrees.y * 0.5f);

       

        var xRotation = Quaternion.AngleAxis(-_mouseAbsolute.y, targetOrientation * Vector3.right);
        cameraTransform.localRotation = xRotation;
    }
}

All Move does is rotate the parent and move the in the input direction.
All Look does is tilt the camera on the x-axis so looking up and down is controlled with this script.

It gives pretty smooth results.

1 Like

One step I missed, make sure to reference cameraTransform on the Move script, by dragging the camera into in the inspector.

[Edit]
Just to point out, there could be further optimisations made here, there’s a lot of code repetition between them, I just did a quick split to demonstrate. :slight_smile:

1 Like

I actually have a Camera then a capsule parent to it
-the camera has the script on it with a character controller component
-the capsule is a child or has been parented to the camera and has only the mesh on it

That might just work!! I didn’t think of spliting it into two scripts, ill go and test it out, thank you for taking time out of your day to help me.

it worked perfectly!! thank you so much!!

1 Like

No problem at all! Glad it helped.

All the correct code was there and written nicely, it was just interacting together in a way that created a strange result.

1 Like