Third person camera collision

Hello,
I have decided to code my own script to check whether or not my character is being fully seen by my camera. It’s a third person camera which follows and orbits around the player.
I’ve successfully managed to do so to a certain extent. My problem is that the rays which go from the player to the camera to check if nothing is in the way do not work properly. In fact, they sometimes randomly stop detecting a collision at some random frame.

The code looks like this :

// Update is called once per frame
    void LateUpdate () {
        //Check if a collision happened
        bool checkIfCollision = false;

        //Clip points
        ClipPlanePoints nearPlanePointsCheck = cameraClipPlanePoints (0.35f);
        //Debug.Log (nearPlanePointsCheck.LowerLeft + ", " + nearPlanePointsCheck.LowerRight + ", " + nearPlanePointsCheck.UpperLeft + ", " + nearPlanePointsCheck.UpperRight);
        Debug.DrawLine (target.position, nearPlanePointsCheck.LowerLeft);
        Debug.DrawLine (target.position, nearPlanePointsCheck.LowerRight);
        Debug.DrawLine (target.position, nearPlanePointsCheck.UpperLeft);
        Debug.DrawLine (target.position, nearPlanePointsCheck.UpperRight);

        //Check where the ray hit
        RaycastHit hit;
        //List of points
        List<float> hitDistances = new List<float>();

        //Check if lower left hit
        if (Physics.Raycast (target.position, (nearPlanePointsCheck.LowerLeft - target.position), out hit, distanceFromTarget, clippable.value)) {   
            Debug.DrawLine (target.position, hit.point, Color.black);
            checkIfCollision = true;
            hitDistances.Add (hit.distance);
        }
        //Check if lower right hit
        if (Physics.Raycast (target.position, (nearPlanePointsCheck.LowerRight - target.position), out hit, distanceFromTarget, clippable.value)) {   
            Debug.DrawLine (target.position, hit.point, Color.black);
            checkIfCollision = true;
            hitDistances.Add (hit.distance);
        }
        //Check if upper left hit
        if (Physics.Raycast (target.position, (nearPlanePointsCheck.UpperLeft - target.position), out hit, distanceFromTarget, clippable.value)) {   
            Debug.DrawLine (target.position, hit.point, Color.black);
            checkIfCollision = true;
            hitDistances.Add (hit.distance);
        }
        //Check if upper right hit
        if (Physics.Raycast (target.position, (nearPlanePointsCheck.UpperRight - target.position), out hit, distanceFromTarget, clippable.value)) {   
            Debug.DrawLine (target.position, hit.point, Color.black);
            checkIfCollision = true;
            hitDistances.Add (hit.distance);
        }

        //Sort points to get the shortest one
        if (hitDistances.Count != 0 && hitDistances != null) {
            for (int x = 0; x < hitDistances.Count; x++) {
                if (hitDistances [0] >= hitDistances [x] && (hitDistances [0] - hitDistances[x]) > 0.005f ) {
                    hitDistances[0] = hitDistances[x];
                    Debug.Log(hitDistances[0]);
                }

            }
        }

        Debug.Log (checkIfCollision);
           
        //Check if camera collids with Clippable layer
        if (checkIfCollision == true) {   
            //Get the distance between player and hit point
            float dis = hitDistances[0];
            //Debug.LogWarning (dis);
            float newPosition = Mathf.SmoothDamp(distanceFromTarget, dis, ref yVelocity, smoothTimeIn);
            //New distance to place camera in
            distanceFromTarget = newPosition;

        } else if (checkIfCollision == false) {
            //Dezoom from collision
            float normalPosition = Mathf.SmoothDamp(distanceFromTarget, idealDistanceFromTarget, ref yVelocity, smoothTimeOut);
            distanceFromTarget = normalPosition;
        }
           

        //Movement of the camera which influences the player if he's moving, x or y
        horizontalMouse += Input.GetAxis ("Mouse X");
        verticalMouse += Input.GetAxis ("Mouse Y");
        //Clamp y
        verticalMouse = ClampAngle(verticalMouse, yMinLimit, yMaxLimit);

        //Calculating the rotation of the camera with smooth transition
        currenRotation = Vector3.SmoothDamp(currenRotation, new Vector3 (horizontalMouse, verticalMouse), ref rotationSmoothVelocity,rotationSmoothTime);
        //Rotate the camera
        transform.eulerAngles = currenRotation;

        //Position of the camera
        transform.position = target.position - transform.forward * distanceFromTarget;

    }

What happens is that my camera starts jumping forward and backwards really quickly and I don’t know why.

Thanks for you help.

I don’t know if this is related to the problem, but you have hitDistances defined as a list, and later in the code it looks like you’re try to access it as an array. I myself would define it as:

float[] hitDistances = {0f, 0f, 0f, 0f};
//and later assign the value of the hit.distance like so:
if (Physics.Raycast (target.position, (nearPlanePointsCheck.LowerLeft - target.position), out hit, distanceFromTarget, clippable.value)) {  
            Debug.DrawLine (target.position, hit.point, Color.black);
            checkIfCollision = true;
            hitDistances.Add (hit.distance);
        }
        //Check if lower right hit
        if (Physics.Raycast (target.position, (nearPlanePointsCheck.LowerRight - target.position), out hit, distanceFromTarget, clippable.value)) {  
            Debug.DrawLine (target.position, hit.point, Color.black);
            checkIfCollision = true;
            hitDistances.Add (hit.distance);
        }
//and so on...

//then when you check the value of each hitDistances value, make sure that each hitDistances is not equal to zero
//Sort points to get the shortest one
        if (hitDistances.Count != 0 && hitDistances != null) {
            for (int x = 0; x < hitDistances.Count; x++) {
                if (hitDistances [0] >= hitDistances [x] && (hitDistances [0] - hitDistances[x]) > 0.005f && hitDistances[x] != 0f) {
                    hitDistances[0] = hitDistances[x];
                    Debug.Log(hitDistances[0]);
                }
           }
        }

Alternatively, you might prefer to use a ToArray method to create an array from the list:

float[] hitDistancesArray = hitDistances.ToArray()

And then iterate through that array.

Thanks for your reply, I’ve done the changes you’ve told me to do. I still get the problem that the camera zooms in and out frantically. Would screenshots help to understand the problem or rather the entire code?

PS: I don’t get this problem when using only one raycast to the center of the camera, but I get bleeding problems then.

I’ll probably need the rest of the code.

Ok here you go. (I followed tutorials for some parts)

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

public class playerCameraTest : MonoBehaviour {

    //Layer to hit if needed for camera collision
    public LayerMask clippable;
    //Smoothening to reach target
    public float smoothTimeIn = 0.3F;
    //Smoothening to get out of target
    public float smoothTimeOut = 0.5F;
    //Current velocity
    private float yVelocity = 0.0F;
    //Clamp camera
    float yMinLimit = -30;
    float yMaxLimit = 50;

    //Inputs of the mouse
    float horizontalMouse;
    float verticalMouse;
    //Sensivity of mouse
    public float mouseSensitivity = 10;
    //Lock the cursor to the middle of the screen
    public bool lockCursor;

    //target to follow with the camera
    public Transform target;
    //Distance the camera has from the target
    public float distanceFromTarget = 2;
    public float idealDistanceFromTarget = 2;

    //Smoothening of the rotation
    public float rotationSmoothTime = .12f;
    Vector3 rotationSmoothVelocity;
    Vector3 currenRotation;

    //Structure of all clip plane points
    public struct ClipPlanePoints{
        public Vector3 UpperLeft;
        public Vector3 UpperRight;
        public Vector3 LowerLeft;
        public Vector3 LowerRight;
    }

    public ClipPlanePoints cameraClipPlanePoints(float distance){
       
        ClipPlanePoints clipPlanePoints = new ClipPlanePoints();

        Transform transform = Camera.main.transform;
        Vector3 pos = transform.position;
        float halfFOV = (Camera.main.fieldOfView * 0.5f) * Mathf.Deg2Rad;
        float aspect = Camera.main.aspect;

        float height = Mathf.Tan(halfFOV) * distance;
        float width = height * aspect;

        //Lower Right
        clipPlanePoints.LowerRight = pos + transform.forward * distance;
        clipPlanePoints.LowerRight += transform.right * width;
        clipPlanePoints.LowerRight -= transform.up * height;

        //Lower Left
        clipPlanePoints.LowerLeft = pos + transform.forward * distance;
        clipPlanePoints.LowerLeft -= transform.right * width;
        clipPlanePoints.LowerLeft -= transform.up * height;

        //Upper Right
        clipPlanePoints.UpperRight = pos + transform.forward * distance;
        clipPlanePoints.UpperRight += transform.right * width;
        clipPlanePoints.UpperRight += transform.up * height;

        //Upper Left
        clipPlanePoints.UpperLeft = pos + transform.forward * distance;
        clipPlanePoints.UpperLeft -= transform.right * width;
        clipPlanePoints.UpperLeft += transform.up * height;

        return clipPlanePoints;
    }

    // Use this for initialization
    void Start () {
        //Lock and hide cursor, escape to make cursor appear
        if (lockCursor) {
            Cursor.lockState = CursorLockMode.Locked;
            Cursor.visible = false;
        }
    }

    void Update(){


    }

    // Update is called once per frame
    void LateUpdate () {
        //Check if a collision happened
        bool checkIfCollision = false;

        //Clip points
        ClipPlanePoints nearPlanePointsCheck = cameraClipPlanePoints (0.35f);
        //Debug.Log (nearPlanePointsCheck.LowerLeft + ", " + nearPlanePointsCheck.LowerRight + ", " + nearPlanePointsCheck.UpperLeft + ", " + nearPlanePointsCheck.UpperRight);
        Debug.DrawLine (target.position, nearPlanePointsCheck.LowerLeft);
        Debug.DrawLine (target.position, nearPlanePointsCheck.LowerRight);
        Debug.DrawLine (target.position, nearPlanePointsCheck.UpperLeft);
        Debug.DrawLine (target.position, nearPlanePointsCheck.UpperRight);

        //Check where the ray hit
        RaycastHit hit;
        //List of points
        float[] hitDistances = {0f, 0f, 0f, 0f};

        //Check if lower left hit
        if (Physics.Raycast (target.position, (nearPlanePointsCheck.LowerLeft - target.position), out hit, distanceFromTarget, clippable.value)) {   
            Debug.DrawLine (target.position, hit.point, Color.black);
            checkIfCollision = true;
            hitDistances[0] = (hit.distance);
        }
        //Check if lower right hit
        if (Physics.Raycast (target.position, (nearPlanePointsCheck.LowerRight - target.position), out hit, distanceFromTarget, clippable.value)) {   
            Debug.DrawLine (target.position, hit.point, Color.black);
            checkIfCollision = true;
            hitDistances[1] = (hit.distance);
        }
        //Check if upper left hit
        if (Physics.Raycast (target.position, (nearPlanePointsCheck.UpperLeft - target.position), out hit, distanceFromTarget, clippable.value)) {   
            Debug.DrawLine (target.position, hit.point, Color.black);
            checkIfCollision = true;
            hitDistances[2] = (hit.distance);
        }
        //Check if upper right hit
        if (Physics.Raycast (target.position, (nearPlanePointsCheck.UpperRight - target.position), out hit, distanceFromTarget, clippable.value)) {   
            Debug.DrawLine (target.position, hit.point, Color.black);
            checkIfCollision = true;
            hitDistances[3] = (hit.distance);
        }

        //Sort points to get the shortest one
        if (hitDistances.Length != 0 && hitDistances != null) {
            for (int x = 0; x < hitDistances.Length; x++) {
                if (hitDistances [0] >= hitDistances [x] && (hitDistances [0] - hitDistances[x]) > 0.005f && hitDistances[x] != 0f ) {
                    hitDistances[0] = hitDistances[x];
                    Debug.Log(hitDistances[0]);
                }

            }
        }

        Debug.Log (checkIfCollision);
           
        //Check if camera collids with Clippable layer
        if (checkIfCollision == true) {   
            //Get the distance between player and hit point
            float dis = hitDistances[0];
            //Debug.LogWarning (dis);
            float newPosition = Mathf.SmoothDamp(distanceFromTarget, dis, ref yVelocity, smoothTimeIn);
            //New distance to place camera in
            distanceFromTarget = newPosition;

        } else if (checkIfCollision == false) {
            //Dezoom from collision
            float normalPosition = Mathf.SmoothDamp(distanceFromTarget, idealDistanceFromTarget, ref yVelocity, smoothTimeOut);
            distanceFromTarget = normalPosition;
        }
           

        //Movement of the camera which influences the player if he's moving, x or y
        horizontalMouse += Input.GetAxis ("Mouse X");
        verticalMouse += Input.GetAxis ("Mouse Y");
        //Clamp y
        verticalMouse = ClampAngle(verticalMouse, yMinLimit, yMaxLimit);

        //Calculating the rotation of the camera with smooth transition
        currenRotation = Vector3.SmoothDamp(currenRotation, new Vector3 (verticalMouse, horizontalMouse), ref rotationSmoothVelocity,rotationSmoothTime);
        //Rotate the camera
        transform.eulerAngles = currenRotation;

        //Position of the camera
        transform.position = target.position - transform.forward * distanceFromTarget;

    }

    /// <summary>
    /// Clamps the angle.
    /// </summary>
    /// <returns>The angle.</returns>
    /// <param name="angle">Angle.</param>
    /// <param name="min">Minimum.</param>
    /// <param name="max">Max.</param>
    public static float ClampAngle(float angle, float min, float max)
    {
        if (angle < -360F)
            angle += 360F;
        if (angle > 360F)
            angle -= 360F;
        return Mathf.Clamp(angle, min, max);
    }
}

PS : I choose the layer to collide with in the inspector.

1 Like

Is the script attached to the camera or another object?

The script is attached to the camera and the target is an empty game object which is a child of the player (named pivot). Furthermore, i have created a new layer called “Clippable” to set the layer of the terrain that the camera should collide with.

I haven’t tested this but…

Whether you have a collision or not, you change the value of distanceFromTarget after the collision check here:

else if (checkIfCollision == false) {
//Dezoom from collision
float normalPosition = Mathf.SmoothDamp(distanceFromTarget, idealDistanceFromTarget, ref yVelocity, smoothTimeOut);
distanceFromTarget = normalPosition;
}

I suspect this puts you back into a collision situation. I would suggest you check to see if distanceFromTarget equals idealDistanceFromTarget first, before your collision check.

To do that you would remove that check above, and add something like the following up at the beginning of LateUpdate.

//Dezoom from collision
float normalPosition = Mathf.SmoothDamp(distanceFromTarget, idealDistanceFromTarget, ref yVelocity, smoothTimeOut);
distanceFromTarget = normalPosition;

Thanks for your idea.

There is one thing I don’t get in what your saying. The collision check makes the camera go backwards if there isn’t any collision, that’s want I want. Otherwise, the camera would always be zoomed in, wouldn’t it? I think my question wasn’t really explicit. The camera only goes backwards and forwards frantically when a collision happens.
Furthermore, if I put the code at the beginning of the LateUpdate, wouldn’t the camera go backwards even if there was a collision?

Your LateUpdate function happens just before the frame gets rendered. In that function, you check for a collision, if there is one, you move the camera forward, in front of where the collision occurs. Then the function ends and the frame is rendered.

During the next frame, you do your collision check, it is false because the camera has been moved. But then you move the camera back towards its ideal position. This is making the camera go back after the collision check, so it may move the camera into a collision position. Then the frame would get rendered.

Then the next frame does a collision check and finds the camera is in a collision position, and moves the camera forward.

Then the next frame does a collision check and sees no collision, and moves the camera back.

Back and forth. Back and forth.

If you move the camera towards its ideal position before the collision check, this should not happen.

1 Like

Thank you very much for your help. It works now and your explanation is very helpful. I’ve got another problem now that is when I go to close to a wall, it does the same again, but I guess the camera has nowhere to go now. I’ll just have to move the camera past the player I guess.