Help with forcing clamp on x axis?

Hi there! Right now I am trying to basically duplicate the camera that is in RunesScape if you haven’t played it before here is a demo: Screen capture - 6c2d43cb42ec51985a82e9ea09300bbb - Gyazo

Where I am currently at is that my camera moves around the player and up and down with a zoom in and out function. However the clamping for the Y-axis is more like a suggestion than it is forcing camera to not exceed certain values. If I move the mouse (not even all that fast) the final value of the rotation will exceed what is supposedly the max and the faster I move the mouse the greater it will exceed the max value. I assume it’s some inefficiency that is delaying stopping the camera but if an 11 year old java game can not allow it’s camera to exceed certain angles I’m sure my unity project can as well. The code says the max is 45 but it’s actually 70 in the inspector.

Vertical Angle variable stops at 70 but Rotation on the X axis goes a little above: Screenshot - 64ff8c3d202b51c332595b5bdb5a00e2 - Gyazo

Moving the mouse faster results in the x rotation exceeding the max even more: Screenshot - 8335d3227682c31119cbaa335c4a8fc2 - Gyazo

Note: Camera speed is currently very very low and it still goes over so it has to be some kind of really bad method for updating and checking if it’s reached it’s threshold?

Here is the full code as I’m not sure exactly what is most needed:

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

public class CameraScript : MonoBehaviour {

    public Transform player;
    private float rotationX;
    private float rotationY;
    public float min = 0f;
    public float max = 45.0f;
    private float fov;
    private float minfov = 30f;
    private float maxfov = 100f;
    public float zoomSpeed = 2.0f;
    public float verticalAngle;

    void Start()
    {
        fov = 60.0f;
    }

    void Update () {

        //Camera points to Player
        transform.LookAt(player);

        Camera.main.fieldOfView = fov;

       

        //Check to see if Player is holding down Middle Mouse to move the camera
        if (Input.GetMouseButton(2))
        {
            rotationX = Input.GetAxis("Mouse X");

            rotationY = -Input.GetAxis("Mouse Y");  
        }
        else
        {
            rotationX = 0;

            rotationY = 0;
        }

        verticalAngle = transform.localEulerAngles.x > 180 ? transform.localEulerAngles.x - 360 : transform.localEulerAngles.x;
        verticalAngle = Mathf.Clamp(verticalAngle, min, max);

        //Check if rotation of the camera exceeds the defined max angles
        if (verticalAngle >= max && rotationY >= 0)
        rotationY = 0;

        else if (verticalAngle < min && rotationY < 0)
        rotationY = 0;

        transform.position = new Vector3(transform.position.x, transform.position.y, transform.position.z);
        fov = Mathf.Clamp(fov, minfov, maxfov);

            if (Input.GetAxis("Mouse ScrollWheel") > 0)
            {
                if(fov > minfov)
                    fov -= zoomSpeed;
            }
            else if (Input.GetAxis("Mouse ScrollWheel") < 0)
            {
                if(fov < maxfov)
                    fov += zoomSpeed;
            }

        //Functions to actually rotate the camera around the Player Object
        transform.RotateAround(player.transform.position, Vector3.up, rotationX);
        transform.RotateAround(player.transform.position, transform.right, rotationY);

    }
}

A few things come to mind…

  1. I think it looks nicer to move the camera closer/farther away, as opposed to changing the field of view. When I first started in Unity, I tried the fov, and didn’t care for the results.
  2. Limiting your x-axis rotation can be done by modifying a variable from your input, clamping it, then applying it to your rotation. I find a 3 part camera to be very useful (and intuitive) for this sort of thing. Much like the free look camera in the standard assets.

Hopefully that helps.

Hi there! I have tried what you’ve mentioned moving the camera closer and further except my attempt at it did not work as when you rotate around the character the z axis changes to be negative therefore inverting your controls. Also the game I am cloning uses fov manipulation for the zoom in function anyways so I suppose it fits.

Additionally I have tried clamping the rotationY variable as you suggested but it doesn’t work unfortunately. Do you by chance have an example or any input on my current implementation that might change that?

Camera movement should be done in LateUpdate(), not Update(). transform.Lookat() should be the very last thing your camera does, after you’ve processed your movement logic, so move it to the end of the function. On line 56, you’re setting the transforms position to itself. No need for that. Below is an example of a script that I created last night. It’s a mouse aim script, but should get headed in the right direction:

    public Transform player;
    public float rotateSpeed = 5f;
    Vector3 offset;
    float vertical;
    float yMin;
    private void Start()
    {
         offset = this.transform.position - player.position;
    }
    private void FixedUpdate()
    {
     
        RaycastHit downInfo;
        if (Physics.Raycast(down, out downInfo))
        {
            Debug.DrawLine(down.origin, downInfo.point, Color.green);
       
            Vector3 target = downInfo.point - transform.position;
            float angle = Vector3.Angle(transform.forward, target) + 10;
       
            float min = (Mathf.Rad2Deg * Mathf.Acos(downInfo.distance * 0.1f)) - angle;
            yMin = min;
         }
    }
    void LateUpdate()
    {
   
        float horizontal = Input.GetAxis("Mouse X") * rotateSpeed;
        vertical += (Input.GetAxis("Mouse Y") * rotateSpeed) * -1;
        vertical = Mathf.Clamp(vertical, yMin, 30);
        target.transform.Rotate(0, horizontal, 0);
        float desiredAngle = target.transform.eulerAngles.y;
        Quaternion rotation = Quaternion.Euler(vertical, desiredAngle, 0);
        Vector3 targetPos = target.transform.position - (rotation * offset);
        transform.position = target.transform.position - (rotation * offset);
        transform.LookAt(target.transform);
   
    }

This isn’t completely what you want, as this aims the player along the horizontal axis when moving the mouse, but you should be able to read this and understand how to calculate the angle of x and constrain it appropriately.

This script calculates the minimum vertical angle that the camera can be at without going through the terrain. This is extremely useful when you start working with uneven terrains. Then I constrain it’s maximum to 30. You may choose whatever number you like.

Well, for the clamping part, you have to modify the variable, such as += or -= of the input. Then, you can clamp the new value. That’s what I’ve done and it works well.

rotationY += -Input.GetAxis("Mouse Y");
rotationY = Mathf.Clamp(rotationY, -50, 50); // or whatever

As for the axis issue, this is again where the 3 part camera comes in handy.
With a setup:
empty game object (only rotates on the x axis)

  • child empty game object (only rotates on the local y axis)
  • child of child: actual camera
    Either the first child, or the child of the child can move on the z-axis to “zoom”.

Ah I get what you mean on the 3 part camera. As for the example you provided that’s exactly what I’ve tried. It doesn’t clamp the rotation is clamps the amount that the value it’s adding can be.

Here’s what gets printed to the console:https://gyazo.com/b47d21446bc9ed7dded2735b858f3cb2

It’s not even an angle value it’s just numbers being adding to the rotation to make it rotate I believe.

You need to multiply your input by a rotation speed. Input.GetAxis only returns a value between 1 and -1.

I’ve done a mixture of what both of you have said and this is still the result: https://gyazo.com/ea198304187dab20a7bd6848359b6569

I don’t think the clamping of rotationY does anything or at least in my case. I don’t know what I’m missing.

Okay, well the example was incomplete.
If you were modifying a 3 part camera, and wanted localRotation (could be rotation for the parent part):

childObj.localRotation = Quaternion.Euler(rotationY, 0, 0); // where 'childObj' is a Transform

The rotationX doesn’t matter so much, again in the 3 part camera…
As in, you do not need to track the value, because I don’t think you’re clamping it…

someTransform.localRotation *= Quaternion.Euler(0, rotationX, 0);

Last but not least, ‘LookAt’ is not required if you use this kind of setup, because it’s implicit when you have the camera setup correctly.
You only have to set the camera’s position (parent game object) to equal that of the player. In addition, you can raise the first child up slightly on the y-axis, and move the first or second child back on the z-axis.
Hopefully that makes sense. :slight_smile: (sorry, I am a little tired…)