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
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:
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.
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;
}
}
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
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.
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.
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.
also for anyone else with this problem, since thats why the forum exists, here is the final code. it all functions as needed
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;
}
}
}