Camera Rotation Limits

here is a full script (note that another script already locks the cursor):

using UnityEngine;

public class PlayerController : MonoBehaviour
{
    [SerializeField] private GameObject _camera;
    private Vector3 _playerVelocity;
    private void Start()
    {
        
    }
    private void Update()
    {
        _playerVelocity = new Vector3(0, 0, 0);
        if (Input.GetKey("w"))
        {
            _playerVelocity += new Vector3(0, 0, 1);
        }
        if (Input.GetKey("a"))
        {
            _playerVelocity += new Vector3(-1, 0, 0);
        }
        if (Input.GetKey("s"))
        {
            _playerVelocity += new Vector3(0, 0, -1);
        }
        if (Input.GetKey("d"))
        {
            _playerVelocity += new Vector3(1, 0, 0);
        }
        gameObject.transform.Translate(_playerVelocity * Time.deltaTime * 5);
        gameObject.transform.Rotate(new Vector3(0, Input.GetAxis("Mouse X") * 3, 0));
        _camera.transform.Rotate(new Vector3(-Input.GetAxis("Mouse Y") * 3, 0, 0));
    }
}

What i want is for there to be a limit on how much the player can rotate on the X axis so that they cant look past straight up or past straight down. line 32 rotates the camera on the X axis, so use that mainly. thankyou in advance :slight_smile:

If you’re just writing yet another player controller, here’s a great one to study before you do:

That one has run, walk, jump, slide, crouch… it’s crazy-nutty!!

You might even want to start from it.

Otherwise, here’s rotation nitty-gritty:

Your best bet is to have local-only float variables to represent traverse and elevation, then change and clamp those floats, and finally drive .localRotation of the parts on their correct Transform axis.

Notes on clamping rotations and NOT using .eulerAngles because of gimbal lock:

How to instantly see gimbal lock for yourself:

All about Euler angles and rotations, by StarManta:

https://starmanta.gitbooks.io/unitytipsredux/content/second-question.html

ALSO…

Camera stuff is pretty tricky… I hear all the Kool Kids are using Cinemachine from the Unity Package Manager.

There’s even a dedicated Camera / Cinemachine area: see left panel.

If you insist on making your own camera controller, do not fiddle with camera rotation.

The simplest way to do it is to think in terms of two Vector3 points in space:

  1. where the camera is LOCATED
  2. what the camera is LOOKING at
private Vector3 WhereMyCameraIsLocated;
private Vector3 WhatMyCameraIsLookingAt;

void LateUpdate()
{
  cam.transform.position = WhereMyCameraIsLocated;
  cam.transform.LookAt( WhatMyCameraIsLookingAt);
}

Then you just need to update the above two points based on your GameObjects, no need to fiddle with rotations. As long as you move those positions smoothly, the camera will be nice and smooth as well, both positionally and rotationally.

1 Like

Is there a really simple way to just stop the camera from rotating further than -90 and 80 on the x axis?
Sorry it’s just I’m fresh out the oven new to this, and while I’ve used Game engines before, this is my first non tutorial unity project

The way you’re rotating the camera is a little unconventional but it’s still possible to clamp it:

using UnityEngine;
public class PlayerController : MonoBehaviour
{
    [SerializeField] Transform _camera; // Reference the camera's transform instead of its gameObject and then we can do _camera.Rotate instead of _camera.transform.Rotate etc.
    Vector3 _playerVelocity;
    void Start()
    {

    }
    void Update()
    {
        _playerVelocity = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));
        transform.Translate(_playerVelocity * Time.deltaTime * 5);
        transform.Rotate(new Vector3(0, Input.GetAxis("Mouse X") * 3, 0));
        _camera.Rotate(new Vector3(-Input.GetAxis("Mouse Y") * 3, 0, 0));
        _camera.eulerAngles = new Vector3(Mathf.Clamp(-Mathf.DeltaAngle(_camera.eulerAngles.x, 0), -90, 80), _camera.eulerAngles.y, 0); // clamp the x axis
    }
}

And moving the player by changing its transform is also unusual because it means it can’t be blocked by collisions. So if you need it to be blocked by collisions then you’ll need to use a character controller or a rigidbody.

It helps to store your rotation values separate to the camera, as Unity uses quaternion rotations and euler angles cannot be relied on. Then you can very easily cap these values, before generating a rotation out of them by using Quaternion.AngleAxis to produce vertical and horizontal rotations, and them combining them all together.

Totally untested example code:

public class MouseLookExample : MonoBehaviour
{
	[SerializeField]
	private Camera _camera; // reference via components not game objects
	
	[SerializeField]
	private float _xMin = -80;
	
	[SerializeField]
	private float _xMax = 80;
	
	[SerializeField]
	private float _mouseLookSensitivity = 5f; // I have no idea what a good value is
	
	private float _xRotation;
	private float _yRotation;
	
	private void Update()
	{
		float hozInput = Input.GetAxis("Mouse X");
		float vertInput = Input.GetAxis("Mouse Y");
		
		float delta = Time.deltaTime * _mouseLookSensitivity;
		_yRotation += hozInput * delta;
		_xRotation += vertInput * delta;
		
		_xRotation = Mathf.Clamp(_xRotation, _xMin, _xMax); // clamp vertical rotation
	}
	
	private void LateUpdate() //camera should be update in late update
	{
		Quaternion identity = Quaternion.identity;
		
		Quaternion horizontalRotation = Quaternion.AngleAxis(_yRotation, Vector3.up);
		Quaternion verticalRotation = Quaternion.AngleAxis(_xRotation, _camera.transform.right);
		
		Quaternion result = (identity * horizontalRotation) * verticalRotation;
		_camera.transform.rotation = result;
	}
}

Just to clarify - the camera only needs to be updated in LateUpdate if its not a child of the player.

The character controller posted above has working functional code that does exactly what you want.

I certainly cannot explain here in English in this little text box any better than BasicFPCC.cs does. The ProcessLook(); method has the Mathf.Clamp()

So if I’m understanding this correctly, the Mathf.Clamp(); method takes three arguements, the variable you want to clamp and then the min and max? If so that would work well. Correct me if I’m (probably) wrong though :slight_smile:

As for this:

I tried using a rigid body but because the physics engine updates with frames, it means that when I changed frame rate (I test be unplugging my charger) the whole game slowed right down, whereas with translate/rotate I can use Time.deltaTime.

I’m probably being a ‘new unity user’ here but with the tutorials I’ve used to learn the basics, that’s what I understand. :sweat_smile:

This is incorrect. Unity’s physics do not update on a per-frame basis. They are updated on a fixed time-step independant of the framerate. You can see where it is run in the order of execution in the docs: Unity - Manual: Order of execution for event functions

To that end, any interaction you have with physics should generally be done in FixedUpdate() so as to also remain frame rate independant.

ahhh i think i get why i thought it was per frame then. its because i was adding a force to the player (for movement) in the Update method, which yeah would depend on framerate. youre saying that if I used FixedUpdate() then it would work the same regardless of framerate?

in addition, the Clamp(); method! I checked it out a bit (research) and it looks like exactly what i need. Ill just clamp the value before rotating the camera so that it stays within the desired range!

Correct. Though conversely, you should only poll input in Update. So in situations where you want to control a rigidbody with via player input, you need to poll said input in Update, and apply it in FixedUpdate.

ok ill test that when i get home. it sounds like it should work! :slight_smile:

It’s all working now! I used a rigid body and AddRelativeForce to move the player, and implemented the Clamp(); function to make the X rotation have limits! thankyou @spiney199 and @Kurt-Dekker, and @zulo3d too. this was my first post on the forum. :slight_smile:

also for anyone else with this problem, since thats why the forum exists, here is the final code. it all functions as needed :smiley:

using UnityEngine;

public class PlayerController : MonoBehaviour
{
    [SerializeField] private GameObject _camera;
    private Vector3 _playerVelocity;
    private float _playerRotation = 0;
    private bool _grounded = false;
    private void Start()
    {
        
    }
    private void Update()
    {
        _playerVelocity = new Vector3(0, 0, 0);
        if (Input.GetKey("w"))
        {
            _playerVelocity += new Vector3(0, 0, 1);
        }
        if (Input.GetKey("a"))
        {
            _playerVelocity += new Vector3(-1, 0, 0);
        }
        if (Input.GetKey("s"))
        {
            _playerVelocity += new Vector3(0, 0, -1);
        }
        if (Input.GetKey("d"))
        {
            _playerVelocity += new Vector3(1, 0, 0);
        }
        if (Input.GetKey("space") && _grounded)
        {
            _playerVelocity += new Vector3(0, 3, 0);
        }
        gameObject.transform.Rotate(new Vector3(0, Input.GetAxis("Mouse X") * 3, 0));
        _playerRotation = Mathf.Clamp(_playerRotation - Input.GetAxis("Mouse Y") * 3, -90, 80);
        _camera.transform.eulerAngles = new Vector3(_playerRotation, gameObject.transform.eulerAngles.y, gameObject.transform.eulerAngles.z);
    }
    private void FixedUpdate()
    {
        gameObject.GetComponent<Rigidbody>().AddRelativeForce(_playerVelocity * 10);
    }
    private void OnCollisionEnter(Collision _collider)
    {
        if (_collider.gameObject.tag == "Ground")
        {
            _grounded = true;
        }
    }
    private void OnCollisionExit(Collision _collider)
    {
        if (_collider.gameObject.tag == "Ground")
        {
            _grounded = false;
        }
    }
}