localEulerAngles has trouble above 87.5 degrees

I’ve got a sun object that rotates around the world just fine, but when I try to make a time system that gauges the time by the sun’s position in the sky, using transform.localEulerAngles.x (it rotates around a Game Object at world origin).

It all works perfectly until I get to 87.5 degrees above the horizon, in which it rounds it up to 90 (see: pic). Is this just a quirk of Eulers that they get weird above that number, or does this look like a bug?

In your screenshot, I notice that the Y and Z rotations of your sun are approximately 180 each. If you keep an eye on it longer, you might notice that your X-axis rotation ranges from 0-90, then goes back down to 0, resets to 360, goes to 270, then back up to 360/0 to 90, etc. This is a problem related to Gimbal Lock.

Look at it from this perspective: If you rotate an object by 180 degrees on all three of the X, Y, and Z axes, your object will look like it hasn’t been rotated at all. This is because there are numerous ways of interpreting the same rotation.

When Unity constructs its rotations, all that really matters is the current final state; because there are an infinite number of ways to represent the same final orientation after a rotation, Unity is simply picking one that works first.

However, this doesn’t factor in the use of Quaternion rotations, which can be given more explicit instructions. With Euler angles, you’re essentially saying “rotate to 570 degrees, which is also known as 210 degrees, which is also known as -150 degrees” – By contrast, using Quaternions, you would say “Perform 1.583333 revolutions around suchandsuch axis”. The expected outcome is the same, but the actual results are very different.

So, what does this mean for you?

It means you should take a different approach to calculating the current rotation of the sun. What I would recommend is creating a function to calculate a signed angle between two vectors, and use that to determine the sun’s angle instead.

// C#

// worldFwd is your baseline. What direction does the sun point at, say, noon?
// localFwd is the current direction the sun is facing
// localRight is a perpendicular vector to localFwd located on the rotational plane
// In the case of your example, this would be the sun's -Y axis by default
// (i.e. "up" would therefore be the axis rotated around in this case or, in your example, X axis)
float SignedAngle(Vector3 worldFwd, Vector3 localFwd, Vector3 localRight)
	float outAngle = Vector3.Angle(worldFwd, localFwd);
	if(Vector3.Dot(worldFwd, localRight) < 0)
		outAngle *= -1;
	return outAngle;

Using this as-is, your return angle will range from -180 to 180 degrees relative to your “worldFwd” starting point, going counterclockwise.