Welcome, Guest. Please login or register.

Login with username, password and session length

 
Advanced search

1411433 Posts in 69363 Topics- by 58418 Members - Latest Member: Pix_RolleR

April 20, 2024, 06:54:18 AM

Need hosting? Check out Digital Ocean
(more details in this thread)
TIGSource ForumsDeveloperTechnical (Moderator: ThemsAllTook)Handling EulerAngle 360/0 in Unity
Pages: [1]
Print
Author Topic: Handling EulerAngle 360/0 in Unity  (Read 13761 times)
Sundrop
Level 1
*

Grandma cooks best!


View Profile WWW
« on: April 12, 2014, 07:22:06 AM »

I've been trying to replicate the rotations you see in FEZ but the way Unity deals with rotations is driving me crazy! Apparently, if an object's eulerAngle gets up to 360, it's reset to 0. Another weird thing is when I tried to round off a values (Mathf.Round) as rotations are completed (0, 90, 180, 270), instead of returning to 0 after a full rotation, the value gets rounded off to a 1.001791e-05, making the object spin out of control. Is this one of Unity's quirks or am I looking at this the wrong way?

Code:
	private float rotationValue;
private bool rotating = false;
private float rotationGap;

void Update ()
{
Debug.Log (rotationGap);
Debug.Log (transform.eulerAngles.y);

if (Input.GetButtonDown ("Horizontal") && rotating == false)
{
rotating = true;

if (Input.GetAxis ("Horizontal") > 0)
{
rotationValue = transform.eulerAngles.y + 90f;
}
else if (Input.GetAxis ("Horizontal") < 0 && transform.eulerAngles.y != 0)
{
rotationValue = transform.eulerAngles.y - 90f;
}
}

if (rotating == true)
Rotate ();
}

void Rotate ()
{
rotationGap = transform.eulerAngles.y - rotationValue;

transform.Rotate (0, -(rotationGap) * Time.deltaTime * 10f, 0);

if (Mathf.Abs (rotationGap) < .1f)
{
Vector3 tempEuler = transform.eulerAngles;
tempEuler.y = Mathf.Round (transform.eulerAngles.y);
transform.eulerAngles = tempEuler;
rotationGap = 0;
rotating = false;
}

if (rotationGap == 0 && transform.eulerAngles.y < 2)
{
Vector3 tempEuler = transform.eulerAngles;
tempEuler.y = 0;
transform.eulerAngles = tempEuler;
}
}

FYI: The main reason this is a problem is because I'm trying to get some easing between rotations. Without them, everything's fine.
« Last Edit: April 12, 2014, 10:20:58 AM by Sundrop » Logged

RandyGaul
Level 1
*

~~~


View Profile WWW
« Reply #1 on: April 12, 2014, 10:24:20 AM »

I can't really answer about Unity specifics because I have no experience (so you'll have to wait for someone else to hop in), but for interpolation between rotations I know that the standard is to use either spherical linear interpolation between quaternions, or to use lerp for smaller angles as an approximation.

Euler angles are usually good for setting the orientation of something as a designer tweaking a value. Things like UI, or the initial placement of an NPC can be good for this. I don't think you'll end up sticking with Euler angles for something like rotating your world/camera over and over.
Logged
Sundrop
Level 1
*

Grandma cooks best!


View Profile WWW
« Reply #2 on: April 12, 2014, 11:24:26 AM »

Thanks for helping me realise I've been going about this the wrong way. Euler angles seem to work well enough for rotations within 90~180 degrees but beyond that, yea I'll have to look up how Quaternions work. Cheers, Randy.
Logged

powly
Level 4
****



View Profile WWW
« Reply #3 on: April 13, 2014, 02:11:16 AM »

The amount of rotation isn't where Euler angles go wrong, it's the amount of axes you rotate around. If you need to go around a single axis they work just as well, with two axis (like a first person 3D camera) you can still get them to work, but once you start rotating around all three (like in a flight simulator or something) you'll run into trouble. (well, if you have only very small angles, Euler angles are almost correct)

The easiest way might be to ditch the unity way and just keep track of the angles yourself.
Logged
Polly
Level 6
*



View Profile
« Reply #4 on: April 13, 2014, 03:51:41 AM »

Apparently, if an object's eulerAngle gets up to 360, it's reset to 0.

Regardless of how it works internally ( Unity uses Quaternions for rotation on their Transform component, so it's probably converted back and forth ), wrapping the eulerAngles values around a set range prevents precision loss.

Is this one of Unity's quirks or am I looking at this the wrong way?

Instead of only setting a rotationValue target on input, set both a in and out value for use with your interpolation. So if the current rotation is 0 and you want to subtract 90 degrees, in becomes 360 and out 270.
Logged
Sundrop
Level 1
*

Grandma cooks best!


View Profile WWW
« Reply #5 on: April 14, 2014, 06:44:32 AM »

I made some changes to the code so that if I need a negative Y rotation beginning at 0 (ideally 360 degrees), eulerAngles.y gets set to 359. I also manually set eulerAngles.y to either 0, 90, 180, or 270 when no rotation is taking place, depending on which angle it's current value is closest too. Not the most elegant way to do things but it was good practice. Here's what I have now:

Code:
private float rotationValue;
private bool rotating = false;

void Update ()
{

// If pressing left/right & not rotating.
if (Input.GetButtonDown ("Horizontal") && rotating == false)
{
rotating = true;

// If direction is positive, current y rotation + 90.
if (Input.GetAxis ("Horizontal") > 0)
rotationValue = transform.eulerAngles.y + 90;
else if (Input.GetAxis ("Horizontal") < 0)
{
// If y is less than 10, go straight to 359.
if (transform.eulerAngles.y < 2)
{
Vector3 tempRotation = transform.eulerAngles;
tempRotation.y = 359;
transform.eulerAngles = tempRotation;
}

// If direction is negative, current y rotation - 90.
rotationValue = transform.eulerAngles.y - 90;
}
}

if (rotating == true)
Rotate ();

// If not rotating.
if (rotating == false && rotationValue == 0)
{
// If y is less than 2 or more than 358.
if (transform.eulerAngles.y < 2 || transform.eulerAngles.y > 358)
{
Vector3 rotationAdjustment1 = transform.eulerAngles;
rotationAdjustment1.y = 0;
transform.eulerAngles = rotationAdjustment1;
}
// If y is between 92 & 88.
else if (transform.eulerAngles.y < 92 && transform.eulerAngles.y > 88)
{
Vector3 rotationAdjustment2 = transform.eulerAngles;
rotationAdjustment2.y = 90;
transform.eulerAngles = rotationAdjustment2;
}
// If y is between 182 & 178.
else if (transform.eulerAngles.y < 182 && transform.eulerAngles.y > 178)
{
Vector3 rotationAdjustment3 = transform.eulerAngles;
rotationAdjustment3.y = 180;
transform.eulerAngles = rotationAdjustment3;
}
// If y is between 272 & 268.
else if (transform.eulerAngles.y < 272 && transform.eulerAngles.y > 268)
{
Vector3 rotationAdjustment4 = transform.eulerAngles;
rotationAdjustment4.y = 270;
transform.eulerAngles = rotationAdjustment4;
}
}

}

void Rotate ()
{

// Determine gap between origin and target rotation.
float rotationGap = transform.eulerAngles.y - rotationValue;
transform.Rotate (new Vector2 (0, -(rotationGap) * Time.deltaTime * 15f));

// Round y to closest number.
if (Mathf.Abs (rotationGap) < .05f)
{
Vector3 tempRotation = transform.eulerAngles;
tempRotation.y = Mathf.Round (transform.eulerAngles.y);
transform.eulerAngles = tempRotation;
rotationValue = 0;
}

// End rotation.
if (rotationValue == 0)
rotating = false;

}
Logged

jgrams
Level 3
***



View Profile
« Reply #6 on: April 14, 2014, 11:23:15 AM »

I haven't tried Unity (yet), so I be missing something. But there are several things about your code that look suspicious to me. So here's how I'd try writing it, then I'll go through and explain why I've made the changes.

Code:
private float rotationValue;
private bool rotating = false;

void Update ()
{

// If pressing left/right & not rotating.
if (Input.GetButtonDown ("Horizontal") && rotating == false)
{
rotating = true;

if (Input.GetAxis ("Horizontal") > 0)
{
rotationValue += 90;
if (rotationValue > 359) rotationValue = 0;
}
else if (Input.GetAxis ("Horizontal") < 0)
{
rotationValue -= 90;
if (rotationValue < 1) rotationValue = 270;
}
}

if (rotating == true)
Rotate ();
}

void Rotate ()
{
// Determine gap between origin and target rotation.
float rotationGap = transform.eulerAngles.y - rotationValue;
// wrap it so it's in a valid range
while(rotationGap > 180) rotationGap -= 360;
while(rotationGap <= -180) rotationGap += 360;
transform.Rotate (new Vector2 (0, -(rotationGap) * Time.deltaTime * 15f));

// Stop exactly at rotationValue.
if (Mathf.Abs (rotationGap) < .05f) {
transform.eulerAngles.y = rotationValue;
rotating = false;
}
}

Code:
// If direction is positive, current y rotation + 90.
if (Input.GetAxis ("Horizontal") > 0)
rotationValue = transform.eulerAngles.y + 90;

I would probably just add to rotationValue rather than setting it based on eulerAngles.y. That way it would only be able to take values of 0, 90, 180, or 270. The way it is now, if you rotate while the view is in between, rotationValue will get some in-between value, which I think is not what you want?

Then you should wrap rotation value so it's in the range [0, 359], so I would make that:

Code:
rotationValue += 90;
if(rotationValue > 359) rotationValue = 0;

I'd do the same thing for the other direction (and deal with the rest of the wrapping problem in Rotate):

Code:
rotationValue -= 90;
if(rotationValue < 1) rotationValue = 270;

I'd drop all the "if not rotating" stuff.

Code:
	void Rotate ()
{
// Determine gap between origin and target rotation.
float rotationGap = transform.eulerAngles.y - rotationValue;

Now, since you wrapped rotationValue, you might have transform.eulerAngles.y = 270 and rotationValue = 0 (wrapped around from 360). Or something like that. So let's keep our rotationGap in the range [-180, 180] by adding:

Code:
if(rotationGap > 180) rotationGap -= 360;
else if(rotationGap < -180) rotationGap += 360;

Code:
		// Round y to closest number.
if (Mathf.Abs (rotationGap) < .05f)
{
Vector3 tempRotation = transform.eulerAngles;
tempRotation.y = Mathf.Round (transform.eulerAngles.y);
transform.eulerAngles = tempRotation;

I don't understand the use of tempRotation here. Are you not allowed to do this?

Code:
transform.eulerAngles.y = Mathf.Round (transform.eulerAngles.y);


Code:
		if (Mathf.Abs (rotationGap) < .05f)
{
...
rotationValue = 0;
}

// End rotation.
if (rotationValue == 0)
rotating = false;

}

Er, what? I don't understand why you're using rotationValue here. Shouldn't that be rotationGap?
Logged
Trent
Level 0
***


Someday I'll make games!


View Profile WWW
« Reply #7 on: April 14, 2014, 01:14:26 PM »

In C# with Unity, you can't do...

Code:
transform.eulerAngles.y = Mathf.Round (transform.eulerAngles.y);


...sadly. I think its because its just a bunch of getters, and not references to the actual data. But I'm just talking out my ass.
Logged

jgrams
Level 3
***



View Profile
« Reply #8 on: April 14, 2014, 03:16:15 PM »

Ah, yeah. Looks like it uses quaternions internally.

http://docs.unity3d.com/Documentation/ScriptReference/Transform-rotation.html

Odd, though. The Unity docs make it sound like you *can* set them individually, just that it's a bad idea.

http://docs.unity3d.com/Documentation/ScriptReference/Transform-eulerAngles.html
Logged
Trent
Level 0
***


Someday I'll make games!


View Profile WWW
« Reply #9 on: April 14, 2014, 03:23:02 PM »

I hear it works in UnityScript but not C#. One of those little differences I guess.  Shrug

Also, this is again from my ass because it's something I think I read ages ago.
Logged

Fallsburg
Level 10
*****


Fear the CircleCat


View Profile
« Reply #10 on: April 15, 2014, 05:43:07 AM »

You can do something like
Code:
Vector3 rotation = transfrom.eulerAngles;
rotation.y = Mathf.Round (transform.eulerAngles.y);
transform.eulerAngles = rotation;

But yes, Trent is right, C# won't let you do it in one line.
Logged
Sundrop
Level 1
*

Grandma cooks best!


View Profile WWW
« Reply #11 on: April 16, 2014, 12:20:39 AM »

I haven't tried Unity (yet), so I be missing something. But there are several things about your code that look suspicious to me. So here's how I'd try writing it, then I'll go through and explain why I've made the changes.

Code:
private float rotationValue;
private bool rotating = false;

void Update ()
{

// If pressing left/right & not rotating.
if (Input.GetButtonDown ("Horizontal") && rotating == false)
{
rotating = true;

if (Input.GetAxis ("Horizontal") > 0)
{
rotationValue += 90;
if (rotationValue > 359) rotationValue = 0;
}
else if (Input.GetAxis ("Horizontal") < 0)
{
rotationValue -= 90;
if (rotationValue < 1) rotationValue = 270;
}
}

if (rotating == true)
Rotate ();
}

void Rotate ()
{
// Determine gap between origin and target rotation.
float rotationGap = transform.eulerAngles.y - rotationValue;
// wrap it so it's in a valid range
while(rotationGap > 180) rotationGap -= 360;
while(rotationGap <= -180) rotationGap += 360;
transform.Rotate (new Vector2 (0, -(rotationGap) * Time.deltaTime * 15f));

// Stop exactly at rotationValue.
if (Mathf.Abs (rotationGap) < .05f) {
transform.eulerAngles.y = rotationValue;
rotating = false;
}
}


Hey thanks for taking the time to do this! Your setting a range for rotationValue and rotationGap made the rotations so much more solid. One thing I've noticed is that when the object's at 90 degrees and you want to do a -90 rotation, it does a 180 spin instead. I'll work this out but I'm still frustratingly new to programming.

You can do something like
Code:
Vector3 rotation = transfrom.eulerAngles;
rotation.y = Mathf.Round (transform.eulerAngles.y);
transform.eulerAngles = rotation;

But yes, Trent is right, C# won't let you do it in one line.

Yea you have to make a new temporary variable every time you wish to change the transform's rotation and position (just about all I ever use). I wonder how much of an impact this has on the game performance but for now it's just annoying.
Logged

Fallsburg
Level 10
*****


Fear the CircleCat


View Profile
« Reply #12 on: April 16, 2014, 03:08:52 AM »

As I understand it, C#'s garbage collector is intended for that kind of short-lived object and pools them so that there really is no cost after the initial creation. But I've never done any actual test myself to verify.
Logged
Trent
Level 0
***


Someday I'll make games!


View Profile WWW
« Reply #13 on: April 16, 2014, 09:04:15 AM »

Code:
transform.eulerAngles = Vector3(transform.eulerAngles.x, Mathf.Round(transform.eulerAngles.y), transform.eulerAngles.z);

There it is with 1 line and no temp vars.  WTF
Logged

jgrams
Level 3
***



View Profile
« Reply #14 on: April 16, 2014, 03:48:09 PM »

One thing I've noticed is that when the object's at 90 degrees and you want to do a -90 rotation, it does a 180 spin instead. I'll work this out but I'm still frustratingly new to programming.

Oh, bother. I got the range check wrong there. Try checking against -1 instead of 1. (Or 0, but I usually play it safe with floats, even if the value should always be a smallish integer and therefore safe...).
Logged
Pages: [1]
Print
Jump to:  

Theme orange-lt created by panic