Wrapping Euler Angles To Achieve Mouse Look

I am attempting to limit the player's view at the x euler angle of -80 and 80 (not final values), to prevent the player looking upside down. I do this by "wrapping" the values so I can work with angles in the range of -90 to 90 instead of 90 to 0 and 270 to 360 which aren't good for clamping the values, which I also do (360 degrees will now be -90 and so on). That clamp part works just fine.

Please consider the following code:

var wrappedX : float;

Inside update function:

transform.localEulerAngles = Vector3(Mathf.Clamp (wrappedX, -80, 80), 0, 0);

if (transform.localEulerAngles.x >= 270) {
         wrappedX  = transform.localEulerAngles.x - 360;
    }
    else {
        wrappedX = transform.localEulerAngles.x;
    } 

print(Vector3(Mathf.Clamp (wrappedX, -80, 80), 0, 0));

It appears to be working fine except for the part where I actually assign the x euler angle as the new clamped value In this part of the code that you also read above:

transform.localEulerAngles = Vector3(Mathf.Clamp (wrappedX, -80, 80), 0, 0);

For some reason the player's x euler angle is forced to 0, therefor the player can only look side to side (handled in other code not shown), and not up or down at all. the player's view is stuck at 0 degrees.

So what specifically am I doing wrong here? I am very new to angles and stuff so exact code on what will fix it is perfered. Can anyone help me out here? Thanks.

EDIT: After more testing I have concluded that the problem is not that code, but the way it combines with this code:

transform.Rotate (lookSpeed * -Input.GetAxis ("Mouse Y"),0, 0)

This is causing the player's view to not be able to move.

6 Answers

6

What you're doing wrong is that you're using Eulerangles in a way you should not. As you can see here, you should directly set them to an absolute value and not increment them.

"Only use this variable to read and set the angles to absolute values. Don't increment them, as it will fail when the angle exceeds 360 degrees. Use Transform.Rotate instead."

I guess you could work around this by when ever it exceeds 360 or get's lower then 0, use modulo.

var x = 740;
var moduloX = 740 % 360 //which is 20;

But I advice just using transform.Rotate.


edit: do it like this:

var minimumX : float = -360;
var maximumX : float = 360;

var rotationX : float = 0;

private var originalRotation : Quaternion;

function Update () {
    rotationX += Input.GetAxis("Mouse X") * sensitivityX;
    rotationX = ClampAngle (rotationX, minimumX, maximumX);
    xQuaternion = Quaternion.AngleAxis (rotationX, Vector3.up);
    transform.localRotation = originalRotation * xQuaternion;
}

and include the ClampAngle function of course (my bad, sorry):

static function ClampAngle (angle : float, min : float, max : float) : float {
    if (angle < -360.0)
        angle += 360.0;
    if (angle > 360.0)
        angle -= 360.0;
    return Mathf.Clamp (angle, min, max);
}

Can you elaborate on how I would use transform.Rotate in this situation specifically?

I'd advice just checking out the mouseLook script that comes standard with Unity. It does exactly this for both x and y with a variable clamp. Just distill from there. :)

I edited the answer.

Unknown identifier: 'ClampAngle'.

Fixed it, check the edited answer :)

That's a rather odd sequence of operations you've got going on there. Why not get the 'wrappedX' variable to have the right value first, and then use it to set up localEulerAngles?

wrappedX += Input.GetAxis("Vertical");
while(wrappedX > 180) wrappedX -= 360;
while(wrappedX < -180) wrappedX += 360;
wrappedX = Mathf.Clamp(wrappedX, -80, 80);

transform.localEulerAngles = new Vector3(wrappedX, 0, 0);

That way you'll be more easily able to watch the value of wrappedX in the inspector.

Oh, now I see your answer... yes, that's his problem :)

@Joshua - please don't edit my answers without at least leaving a note as to what you've changed.

Oh, I'm sorry - I just changed the last sentence's grammar because it seemed wrong to me xD and I gave you my upvote to by the way ;). Also - you can check what I did for yourself if you click edit and check the revision history.

Ah, I see. Thanks for the pointer.

Here is an universal ClampAngle function that accepts euler angles works properly in any situation and any input values:

function ClampAngle (a : float, min : float, max : float) : float 
{
while (max < min) max += 360.0;
while (a > max) a -= 360.0;
while (a < min) a += 360.0;

if (a > max)
	{
	if (a - (max + min) * 0.5 < 180.0)
		return max;
	else
		return min;
	}
else
	return a;
}

It’s not mine, I found it somewhere (maybe at UnifyCommunity, don’t remember). I had problems with all other angle clamp methods, especially when smoothing the camera movement. But the above function is the only one that worked properly.

The default MouseLook script does this, or at least the original version did. This is a JS translation of it.

I have looked at that script and I still don't see what I am doing wrong here.

:D

let's see....

You declare variable, and it's initial value is 0.

var wrappedX : float;

Then you assign rotation to transform, creating Vector3(0,0,0).

transform.localEulerAngles = Vector3(Mathf.Clamp (wrappedX, -80, 80), 0, 0);

Then you read that rotation (0,0,0) and compute value to assign to wrappedX, value that will be always 0.

if (transform.localEulerAngles.x >= 270) {
         wrappedX  = transform.localEulerAngles.x - 360;
    }
    else {
        wrappedX = transform.localEulerAngles.x;
    }

And in new Update call, you do it all over again, wrappedX is still 0, and you reset localRotationAngles to (0,0,0).

Ehm, the variable wrappedX get's assigned a value in the inspector. Do you realize it's placed outside the update function? o.O

Hm, then he should supply more information, where, how and what he assigns to it... seems like problem is somewhere out there :) Because, his results are like wrappedX is never set, or set to 0. Of course, I don't exclude some other possibility, but this one should be checked first.

wrappedX is set to 0 at default.

wrappedX gets assigned properly when the game runs.

More information was added to the original post.

Why are you doing this?

if (angle < -360.0)
    angle += 360.0;
if (angle > 360.0)
    angle -= 360.0;

The better way to make sure that rotation will not be greater or less than 360 degrees, is this:

var deg = -370; // this is actually -10° and/or 350°
deg = deg % 360; // outputs -10°

Or you can make sure that degrees stays always positive:

deg = deg < 0 ? 360 - (deg % 360) : deg % 360;