Third person Camera rotation problem
Howdy I'm working on a third person game. I have a problem with the camera when I rotate it on the Y axis. This changes its position X when it reaches a certain rotation. I want to keep the X position that I have when I rotate the camera but I do not succeed .. Here is a video with what happens: https://drive.google.com/open?id=1fY0AivmvUDfHzVuNfs35YxP16T_9RcmF
Below is the code:
cameraController.cs
public class CameraController : MonoBehaviour
{
public Transform target; //Player
public Transform pivot; //Pivot for rotating camera
public Vector3 offset;
public bool useOffsetValues;
public float rotateSpeed;
public float maxViewAngle;
public float minViewAngle;
public bool invertY;
void Start()
{
if (!useOffsetValues)
{
offset = target.position - transform.position;
}
pivot.transform.position = target.transform.position;
//pivot.transform.parent = target.transform;
pivot.transform.parent = null;
Cursor.lockState = CursorLockMode.Locked; //Hides Mouse's Cursor
}
void LateUpdate()
{
pivot.transform.position = target.transform.position;
//Rotating Player by Mouse X Axis
float horizontal = Input.GetAxis("Mouse X") * rotateSpeed;
pivot.Rotate(0, horizontal, 0);
//Rotating Player's Pivot by Mouse Y Axis
float vertical = Input.GetAxis("Mouse Y") * rotateSpeed;
//pivot.Rotate(-vertical, 0, 0);
if (invertY)
{
pivot.Rotate(vertical, 0, 0);
} else
{
pivot.Rotate(-vertical, 0, 0);
}
//Limit up/down Camera rotation
if (pivot.rotation.eulerAngles.x > maxViewAngle && pivot.rotation.eulerAngles.x < 180f)
{
pivot.rotation = Quaternion.Euler(maxViewAngle, pivot.rotation.eulerAngles.y, 0);
}
if (pivot.rotation.eulerAngles.x > 180f && pivot.rotation.eulerAngles.x < 360f + minViewAngle)
{
pivot.rotation = Quaternion.Euler(360f + minViewAngle, pivot.rotation.eulerAngles.y, 0);
}
//Rotate Camera by Mouse X Axis
float desiredYAngle = pivot.eulerAngles.y;
float desiredXAngle = pivot.eulerAngles.x;
Quaternion rotation = Quaternion.Euler(desiredXAngle, desiredYAngle, 0);
transform.position = target.position - (rotation * offset);
//Camera follow Player
if (transform.position.y < target.position.y)
{
transform.position = new Vector3(transform.position.x, target.position.y -.5f, transform.position.z);
}
transform.LookAt(target);
}
}
playerController.cs
public class PlayerController : MonoBehaviour
{
public float moveSpeed;
public float jumpForce;
public CharacterController controller;
public float gravityScale;
public Animator anim;
public Transform pivot;
public float rotateSpeed;
public GameObject playerModel;
private Vector3 moveDirection;
void Start()
{
controller = GetComponent<CharacterController>();
}
void Update()
{
float yStore = moveDirection.y;
moveDirection = (transform.forward * Input.GetAxis("Vertical")) + (transform.right * Input.GetAxis("Horizontal"));
moveDirection = moveDirection.normalized * moveSpeed;
moveDirection.y = yStore;
if (controller.isGrounded)
{
moveDirection.y = 0f;
if (Input.GetButtonDown("Jump"))
{
moveDirection.y = jumpForce;
}
}
//Applying gravity
moveDirection.y = moveDirection.y + (Physics.gravity.y * gravityScale * Time.deltaTime);
controller.Move(moveDirection * Time.deltaTime);
if (Input.GetAxisRaw("Horizontal") != 0 || Input.GetAxisRaw("Vertical") != 0)
{
transform.rotation = Quaternion.Euler(0f, pivot.rotation.eulerAngles.y, 0);
Quaternion newRotation = Quaternion.LookRotation(new Vector3(moveDirection.x, 0f, moveDirection.z));
playerModel.transform.rotation = Quaternion.Slerp(playerModel.transform.rotation, newRotation, rotateSpeed * Time.deltaTime);
}
anim.SetBool("isGrounded", controller.isGrounded);
anim.SetFloat("Speed", (Mathf.Abs(Input.GetAxis("Vertical")) + Mathf.Abs(Input.GetAxis("Horizontal"))));
}
}
Answer by streeetwalker · Apr 03, 2020 at 08:50 PM
@unity_QPt4yqS82mflYg, I don't see where the problem is, but the code seems a bit complicated. For one, the pivot rotation is calculated and clamped on the x and y axes, and the Eulers are extracted from that to create to create the final rotation. Maybe I'm missing it, but not just apply the pivot rotation directly and skip that step?
Have you put a debugging statement in the see what the Euler values of x and y when it happens? You might be hitting some kind floating point rounding problem if the values are very small, and you are extracting them from a Quaternion and then applying them to another Quaternion - I don't know.
Another thing that probably does not anything to do with it, but I think the code should calculate the offset of the camera in every frame.
Here is a script I wrote from scratch about a month ago that seems to work perfectly. You can limit both axes rotation, lock both, or let them rotate free. The only thing it does not do yet is "chase" the player - the offset distance to the camera does not change, so the camera remains locked to the player. In the Update loop, you'll need to delete all the mouse down statements, and just leave the 2 input statements.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CameraFollow : MonoBehaviour {
[Header( "Target" )]
public Transform target;
[Header( "Camera will be placed behind target", order = 1 )]
[Header( "At distance and Min Pitch Degrees", order = 2 )]
[Header(" ", order = 2 )]
public float distance = 3.5f;
[Header( "Camera Rotation Speeds" )]
public float y_sensitivity = 1f;
public float x_sensitivity = 1f;
[Header( "Rotation Clamps In Degrees -180 to 180", order = 1 )]
[Header( "Max < Min Free Rotation, Min = Max Lock Rotation", order = 2 )]
public float maxPitchDegrees = 70f;
public float minPitchDegrees = 20f;
public float maxYawDegrees = 90f;
public float minYawDegrees = -90f;
[Header( "Rotation Clamp Overrides" )]
public bool pitchRotLocked = false;
public bool yawRotLocked = false;
public bool pitchRotFree = false;
public bool yawRotFree = false;
// Other private variables
private Vector3 offset;
private float pitch, yaw;
private Quaternion oldTargetRotation;
private bool dragging = false;
private Transform my; // shortcut to this camera transform
private Transform tg; // shortcut to the target
void Start() {
my = transform; // make short cuts
tg = target;
// set my position using distance and minPitchDegrees
float z = Mathf.Sin( minPitchDegrees ) * distance;
float y = Mathf.Cos( minPitchDegrees ) * distance;
my.position = new Vector3( tg.position.x,
tg.position.y + y, tg.position.z - z );
my.LookAt( tg.transform, Vector3.up ); // start out focusing on object
// record these for use later
offset = my.position - tg.position;
oldTargetRotation = tg.transform.rotation;
}
// Update
void Update() {
// Get axis of X & Y using mouse to use when we
// orbit the mouse around the object
// here we are making the user left click and drag
// if you want rotation without dragging, delete all the mousedown code
//
if( Input.GetMouseButtonDown( 0 ) ) {
dragging = true;
}
if( Input.GetMouseButtonUp( 0 ) ) {
dragging = false;
}
pitch = 0;
yaw = 0;
if( Input.GetMouseButton( 0 ) && dragging ) {
if( !pitchRotLocked ) pitch = Input.GetAxis( "Mouse Y" ) * y_sensitivity;
if( !yawRotLocked ) yaw = Input.GetAxis( "Mouse X" ) * x_sensitivity;
}
}
private void FixedUpdate() {
// For each input axis:
// 1 if have input axis motion
// 2 if we do not have free rotation we need to clamp rotation:
// 3 get preview angle for rotateAround for each axis:
// 3.a subtract any target rotation then
// 3.b add to rotateAround angle
// 4 get preview angle in min/max degrees
// 5 clamp preview angle to min/max if input in direction of limit
// 6 if rotation allowed, rotate around
// 7 else free rotate around
//
if( Mathf.Abs( pitch ) > 0 ) { // 1
if( !pitchRotFree ) { // 2
Vector3 prevAngle = ( ( my.rotation * Quaternion.Inverse( tg.rotation ) ) *
Quaternion.AngleAxis( pitch, -my.right ) ).eulerAngles; // 3.a & 3.b
float xDeg = RotToDeg( prevAngle.x ); // 4
if( xDeg < minPitchDegrees && pitch > 0 ) pitch = 0; // 5
else if( xDeg > maxPitchDegrees && pitch < 0 ) pitch = 0; // 5
if( Mathf.Abs( pitch ) > 0 ) my.RotateAround( tg.position, -my.right, pitch ); // 6
} else my.RotateAround( tg.position, -my.right, pitch ); // 7
}
if( Mathf.Abs( yaw ) > 0 ) { // 1
if( !yawRotFree ) { // 2
Vector3 prevAngle = ( ( my.rotation * Quaternion.Inverse( tg.rotation ) ) *
Quaternion.AngleAxis( yaw, Vector3.up ) ).eulerAngles; // 3.a & 3.b
float yDeg = RotToDeg( prevAngle.y ); // 4
if( yDeg < minYawDegrees && yaw < 0 ) yaw = 0; // 5
else if( yDeg > maxYawDegrees && yaw > 0 ) yaw = 0; // 5
if( Mathf.Abs( yaw ) > 0 ) my.RotateAround( tg.position, Vector3.up, yaw ); // 6
} else my.RotateAround( tg.position, -my.right, pitch ); // 7
}
// update my offset vector of any rotateAround based on target position
offset = my.position - tg.position;
// update my offset vector if we have rotated my target in its movement script
// 1 if the current target rotation != to last frame's target rotation:
// 2 subtract the current target rotation from old rotation to get the difference
// 3 add the difference to my current offset vector
// 4 add the difference to my current rotation
// 5 reset old target rotation for next frame
//
if( Quaternion.Dot( tg.rotation, oldTargetRotation ) <= 1f - 0.000001f ) { // 1
Quaternion deltaRot = tg.rotation * Quaternion.Inverse( oldTargetRotation ); // 2
offset = deltaRot * offset; // 3
my.rotation = deltaRot * my.rotation; // 4
oldTargetRotation = tg.rotation; // update for next time around
}
// finally, apply all the offset changes to my position
my.position = tg.position + offset;
}
private float RotToDeg( float rot ) {
float deg = 0;
if( rot > 180f ) {
deg = rot - 360f;
} else {
deg = rot;
}
return deg;
}
private void OnValidate() {
if( maxPitchDegrees > 180 ) {
maxPitchDegrees = 180;
} else if( maxPitchDegrees < -180 ) {
maxPitchDegrees = -180;
}
if( minPitchDegrees >= maxPitchDegrees ) {
minPitchDegrees = maxPitchDegrees;
pitchRotLocked = true;
} else if( minPitchDegrees < -180 ) {
minPitchDegrees = -180;
pitchRotLocked = false;
}
if( maxYawDegrees > 180 ) {
maxYawDegrees = 180;
} else if( maxYawDegrees < -180 ) {
maxYawDegrees = -180;
}
if( minYawDegrees >= maxYawDegrees ) {
minYawDegrees = maxYawDegrees;
yawRotLocked = true;
} else if( minYawDegrees < -180 ) {
minYawDegrees = -180;
yawRotLocked = false;
}
}
}
Answer by unity_QPt4yqS82mflYg · Apr 03, 2020 at 10:06 PM
@streeetwalker Thanks for your answer, but I think the problem is from here..
if (pivot.rotation.eulerAngles.x > maxViewAngle && pivot.rotation.eulerAngles.x < 180f)
{
pivot.rotation = Quaternion.Euler(maxViewAngle, pivot.rotation.eulerAngles.y, 0);
}
if (pivot.rotation.eulerAngles.x > 180f && pivot.rotation.eulerAngles.x < 360f + minViewAngle)
{
pivot.rotation = Quaternion.Euler(360f + minViewAngle, pivot.rotation.eulerAngles.y, 0);
}
Or here
//Rotate Camera by Mouse X Axis
float desiredYAngle = pivot.eulerAngles.y;
float desiredXAngle = pivot.eulerAngles.x;
Quaternion rotation = Quaternion.Euler(desiredXAngle, desiredYAngle, 0);
transform.position = target.position - (rotation * offset);
I can't figure out.. 2 days of searching and modifying and nothing.. I also tried as you said to take offset for each frame, but then the camera starts to spin very fast and does not keep the values.
I followed this tutorial for the camera. https://www.youtube.com/watch?v=VH5kS1Pjdyk&list=PLiyfvmtjWC_V_H-VMGGAZi7n5E0gyhc37∈dex=10&t=0s
The Y axis of the offset is set to -2. If I change it in 2 it doesn't do the same if I keep the camera on the ground level but if I try to raise it it makes it worse.
@unity_QPt4yqS82mflYg, I just posted code I created. You can compare it with yours, or use it if you want.
If you modify an objects euler rotation directly in the inspector, watch the values of x and y as you rotate. At some point you should note the x flips over. That;s what I was saying, you need to slip a debugger in and see what the angles are when you rotate around and the problem happens.
Quaternion rotation = Quaternion.Euler(desiredXAngle, desiredYAngle, 0);
transform.position = target.position - (rotation * offset);
Debug.Log( "Offset Rotation = " + (rotation * offset) + " > Pivot Angles = " + pivot.eulerAngles );
@streeetwalker Greetings ! Thanks for the reply. In the debug the position of the camera changes every time the position x of the pivot reaches 360, but still I have not found a way to solve it.
@unity_QPt4yqS82mflYg, OK, It's hard to tell from your code, but make sure you are not rotating y on the camera's Y axis, but ins$$anonymous$$d the use the world Vector3.up.
It can be confusing because changing the x rotation changes the height of the camera. It is easier to think in aeronautical terms: rotation on x is $$anonymous$$ch, and rotation on y is Yaw.
Importantly, the order of operations matters, if you pitch the camera, it's local y axis changes.
If you change the $$anonymous$$ch of the camera with respect to its local x transform, and then change its Yaw with respect to its local y, you are spinning around the tilted Y axis.
I may be wrong, but I have a hunch that is causing the problem, and why the effect is more pronounced the greater the $$anonymous$$ch is. You want to $$anonymous$$ch on local x, but Yaw (spin ) everything on world Y.
Sorry but i don't understand how to do what you say.. can you show me an example on my code ? 3 days and nothing..