Search code examples
unity-game-enginecamera

Prevent Camera from going below Player in Unity


I have a script where the player can control the camera with their mouse. The issue is that the player can use their mouse to move it below them, like this:

Using mouse to bring camera below player

I don't want this and want to stop the camera when it's Y Position is lower than the Player Y Position.

Here is my camera code:



using System.Collections;
using UnityEngine;

public class CameraController : MonoBehaviour
{
    private Vector3 offset;

    public Transform player;

    public float camPosX;
    public float camPosY;
    public float camPosZ;

    public float camRotationX;
    public float camRotationY;
    public float camRotationZ;

    public float turnSpeed;

    private void Start()
    {
        offset = new Vector3(player.position.x + camPosX, player.position.y + camPosY, player.position.z + camPosZ);
        transform.rotation = Quaternion.Euler(camRotationX, camRotationY, camRotationZ);
    }

    private void Update()
    {
        offset = Quaternion.AngleAxis(Input.GetAxis("Mouse X") * turnSpeed, Vector3.up) * Quaternion.AngleAxis(Input.GetAxis("Mouse Y") * turnSpeed, Vector3.right) * offset;
            transform.position = player.position + offset;
        transform.LookAt(player.position);
    }
}

I have tried to fix this by checking if the Camera Y Position is less than the Player Y position and if that is true, I don't update the camera position.

Here is the code that I used for that:

    private void Update()
    {
        
        offset = Quaternion.AngleAxis(Input.GetAxis("Mouse X") * turnSpeed, Vector3.up) * Quaternion.AngleAxis(Input.GetAxis("Mouse Y") * turnSpeed, Vector3.right) * offset;
        if (!((player.position + offset).y < player.position.y))
        {
            transform.position = player.position + offset;
        }
        transform.LookAt(player);
    }

This fixed the main issue, but made the whole camera movement thing sometimes glitchy... Like sometimes it completely stops moving and just looks at the player and not following it. Like this

Camera movement glitching

I am not sure how to fix this, any help appreciated


Solution

  • I have created a 3rd person camera as well, and I have solved this issue by shooting RayCast from the player to the camera. If the RayCast hit something, it placed the camera to the hit position.

    In my script, I first rotate the camera and then I "push" it away from the player. The RayCast tells me how far I can push the camera.

    1. You get user input and rotate the camera according to the input data. (At this point the camera is at the target position, eg.: player.)
    1. You shoot a RayCast from the target position towards the camera. If it hits something and it is in a range you specified, then you set the camera position to the RayCast hitpoint.

    I post my code here, so you can visualize what I'm talking about.

    private Quaternion rotation = Quaternion.identity;  //Creates a quaternion with only zeros (x=0, y=0, z=0, w=0)
    private float speedX;
    private float speedY;
    
    public void GetInput()
    {
        speedX = Input.GetAxis("Horizontal");
        speedY = Input.GetAxis("Vertical");
    }
    
    private void NormalRotation()
    {
        //Set rotation values
        rotation.x += speedX * horizontalSpeed * Time.deltaTime;
        rotation.y += speedY * verticalSpeed * Time.deltaTime;
        rotation.y = Mathf.Clamp(rotation.y, -50, 90);
    
        //Apply rotation
        transform.rotation = Quaternion.Euler(rotation.y, rotation.x, rotation.z);
    }
    

    The variable rotation is a predefined Quaternion where I store my input values. We only need to manage our X and Y coordinations as we only move on those axes.

    I clamp Y, so the camera won't go below the player too much and won't go over it.

    Then, I apply the rotation. (Keep in mind, that you might need to change the order of the axes according to your input handling. I probably changed up the two input, because my X and Y are switched.

    And here is the distance calculation:

    private void CalculateFinalPosition()
    {
        //Setup temporary variables
        RaycastHit hit;
        Vector3 targetPosition = target.position + target.up * height;
        Vector3 direction = (transform.position - targetPosition).normalized;
        finalDistance = maxDistance;
    
        //Check collision
        if (Physics.Raycast(targetPosition, direction, out hit, Mathf.Infinity)) { finalDistance = hit.distance - 0.1f; }
    
        //Apply position
        finalDistance = Mathf.Clamp(finalDistance, minDistance, maxDistance);
        transform.position = (target.position + target.up * height) - transform.forward * finalDistance;
    }
    

    Now, this might require some explanation.

    targetPosition is equals to your target's position, however, I use some offset (height), so I calculate the final target position here.

    maxDistance represents a floating point number that you set. It limits how far the camera can get from the target.

    We shoot the RayCast into direction and see if it collides. If it does, we modify finalDistance to the distance of the hit (hit.distance). And I want to make sure, that the camera never collides with anything, so I push it a little towards the target (by 0.1).

    I clamp the finalDistance to keep it in the desired range. Then, I place the camera in the target position (target.position + target.up * height) and push it away (-transform.forward * finalDistance).

    Here is a look of it's working process (You may want to manually set 0.1, because it might be a little buggy like here. You can experiment as much as you want with it to find the perfect value.):enter image description here

    I give you a link to the script if you would need it for reference or usage.