Rotation Jumping values (0 to 180)

I am having an issue where eulerAngles.y is returning a Y rotation value that jumps when I move the character head up and down (X axis).

So when I move the head up (above green line), eulerAngles.y returns 0. But when I move down (below green line), eulerAngles.y returns 180. Keep in mind that moving the head up and down rotates around the X axis.

Check out the Video (YouTube) explaining the issue.

You can see in the second picture that the local transform and rotation for the head are not default and are a bit rotated.

Head Up Down

Head Transform

The Lines coming from the head are just gizmos created as so:

Gizmos.color = Color.red;
Gizmos.DrawLine(headTransform.position, headTransform.position + headTransform.up*3);

Gizmos.color = Color.green;
Vector3 forward = headTransform.up;
Gizmos.DrawLine(headTransform.position, headTransform.position + (new Vector3(forward.x, 0, forward.z)*3));

I know that eularAngles has a rotation order but how do I get the Y rotation in world space that I can apply to any object and it will point in the same direciton.


My goal is to make the body turn to where the head is looking and have the head counteract the body motion so they are aligned. Although I am having other issues, this is just one I am stuck on why this is happening. When I add the code below, the character just spins around and around.

// Make the body rotate to where our head is pointing
bodyTransform.eulerAngles = new Vector3(bodyTransform.eulerAngles.x, headTransform.eulerAngles.y, bodyTransform.eulerAngles.z);

// Counter-act the body by rotating the head back into place
headTransform.eulerAngles = new Vector3(headTransform.eulerAngles.x, bodyTransform.eulerAngles.y, headTransform.eulerAngles.z);

Update:

I tried what @robertbu suggested using Quaternion.LookRotation but I am having trouble getting the desired head, body alignment with the below code. I tried messing with the upwards parameter but nothing works. “Z axis will be aligned with forward and the Y axis with upwards” (Unity documentation) so I am wondering if the transform on the head is messing with it. I am assuming the first part is right because it does turn the body in the direction the head is pointing but I am failing at making the head align with the body to stop it from spinning.

// Make the body rotate to where our head is pointing
/* */
Vector3 headForwardDirection = headTransform.up;
headForwardDirection.y = 0f;
Quaternion qTo = Quaternion.LookRotation(headForwardDirection);
bodyTransform.rotation = qTo;
/* */

// Counter-act the body by rotating the head back into place
/* */
Vector3 bodyForwardDirection = bodyTransform.forward;
bodyForwardDirection.y = 0f;
Quaternion qTo2 = Quaternion.LookRotation(bodyForwardDirection);
headTransform.rotation = qTo2;
/* */

Update #2 (Solution):

Just got the project working as expected with the advice of @robertbu of fixing my model head pivot/axis so that Z points forward and Y up. I added a empty/null and parented the head bone.

Here is the script I used to complete the align. It is a combination of a head look and align when moving.

using UnityEngine;
using System.Collections;

public class HeadMoveScript : MonoBehaviour {

	public Transform headTransform; // Used to rotate the head
	public Transform bodyTransform; // Used to rotate the body when head rotation limit reached
	public Transform testTransform;

	public GameObject cMotorGameObject;

	public enum RotationAxes { MouseXAndY = 0, MouseX = 1, MouseY = 2 }
	public RotationAxes axes = RotationAxes.MouseXAndY;
	public float sensitivityX = 15F;
	public float sensitivityY = 15F;
	
	public float minimumX = -360F;
	public float maximumX = 360F;
	
	public float minimumY = -60F;
	public float maximumY = 60F;

	float rotationX = 0F;
	float rotationY = 0F;
	
	Vector3 originalForward;
	Vector3 originalUp;
	Vector3 originalRight;

	Quaternion originalRotation;
	
	void Update ()
	{
		rotationX += Input.GetAxis("Mouse X") * sensitivityX;
		rotationY += Input.GetAxis("Mouse Y") * sensitivityY;

		// Rotate the body when we reach the rotation limits
		if (axes == RotationAxes.MouseXAndY || axes == RotationAxes.MouseX)
			bodyTransform.localEulerAngles = new Vector3(bodyTransform.localEulerAngles.x, bodyTransform.localEulerAngles.y + ClampExcess(rotationX, minimumX, maximumX), bodyTransform.localEulerAngles.z);

		rotationX = Mathf.Clamp(rotationX, minimumX, maximumX);
		rotationY = Mathf.Clamp(rotationY, minimumY, maximumY);



		Quaternion xQuaternion = Quaternion.AngleAxis(rotationX, originalUp);
		Quaternion yQuaternion = Quaternion.AngleAxis(rotationY, -originalRight);

		if (axes == RotationAxes.MouseXAndY)
		{
			headTransform.localRotation = originalRotation * xQuaternion * yQuaternion;
		}
		else if (axes == RotationAxes.MouseX)
		{
			headTransform.localRotation = originalRotation * xQuaternion;
		}
		else
		{
			headTransform.localRotation = originalRotation * yQuaternion;
		}


		/* */
		Vector3 moveVelocity = cMotorGameObject.GetComponent<CharacterMotor>().movement.velocity;
		// When we start to move forward, make sure we run in the direction our head is pointing
		if(Mathf.Abs(moveVelocity.x) > 0 || Mathf.Abs(moveVelocity.z) > 0)
		{
			// Make the body rotate to where our head is pointing
			/* */
			Vector3 headForwardDirection = headTransform.forward;
			headForwardDirection.y = 0f;
			Quaternion qTo = Quaternion.LookRotation(headForwardDirection);
			bodyTransform.rotation = qTo;
			/* */
			

			// Counter-act the body by rotating the head back into place
			/* */
			Vector3 bodyForwardDirection = bodyTransform.forward;
			bodyForwardDirection.y = 0f;
			Quaternion qTo2 = Quaternion.LookRotation(bodyForwardDirection) * Quaternion.AngleAxis(rotationY, -originalRight);
			headTransform.rotation = qTo2;
			/* */

			// Also reset the look rotation of the head
			// So we do not jump back after a new loop
			rotationX = 0F;
		}
		/* */

	}
	
	void Start()
	{
		originalForward = headTransform.forward;
		originalUp = headTransform.up;
		originalRight = headTransform.right;
		originalRotation = headTransform.localRotation;
	}


	void OnDrawGizmos()
	{
		Gizmos.color = Color.red;
		Gizmos.DrawLine(headTransform.position, headTransform.position + headTransform.forward*3);
		
		Gizmos.color = Color.green;
		Vector3 forward = headTransform.forward;
		Gizmos.DrawLine(headTransform.position, headTransform.position + (new Vector3(forward.x, 0, forward.z)*3));
	}



	// Returns the excess from min or max
	float ClampExcess(float value, float min, float max)
	{
		if(value < min)
			return value - min;
		else if(value > max)
			return value - max;

		return 0;
	}

}

Euler angles are derived from the Quaternion. There are multiple euler angle for any given physical rotation, and Unity may change them on you. Here is a simple example:

transform.eulerAngles = new Vector3(180,0,0);
Debug.Log(transform.eulerAngles);

The values output are (0,180,180). So that means if you were only reading the ‘y’ value, you would see an immediate 180 degree shift even though the ‘y’ orientation did not change.

The typical way to solve this problem is to work with either Quaternion.LookRotation() or Transform.LookAt() depending on how you are working with your code. So you would do something like:

Vector3 direction = head.forward;
direction.y = 0.0;
Quaternion qTo = Quaternion.LookRotation(direction);

This takes the forward vector of the head, eliminates any elevation and then calculates a rotation to look in that direction.

To rotate the body over time, to face this direction, you can use Quaternion.RotateToward() or Quaternion.Slerp() depending on whether you want eased movement or not:

transform.rotation = Quaternion.RotateTowards(transform.rotation, qTo, speed * Time.deltaTime);