Transform floating point precision errors in Unity

Everyone that’s used Unity for even a small amount of time has probably noticed that it exhibits Transform floating point precision errors in certain situations. This is when your GameObject’s Transform had nice round integers for position, rotation, and scale just a second ago… and suddenly the values look like 0.99999 or 7.528331e-05. This will often happen when your GameObject is not a root object in the scene and you try to change its parent or duplicate it.

This isn’t a big deal. It really isn’t. You’ll probably never notice any difference in your game due to those tiny imperfections.

But I guess I’m a little OCD because it still drives me crazy. If I added up all the time I’ve spent fixing these precision errors over the years, I’m sure it wouldn’t be trivial. I hate browsing my scene hierarchy and seeing values like that. It makes me think something is wrong. And honestly, Unity should probably handle this better. There’s no reason that when I duplicate a GameObject it doesn’t have the EXACT same values in its Transform.

So, I finally decided to do something about it.

I present to you Precise Duplicate. This is an Editor script that gives you a new command called Precise Duplicate. It is meant to be a complete replacement for the built-in Unity Duplicate function. The standard Duplicate is triggered by Ctrl-D. Unfortunately, there doesn’t seem to be a way to override that keyboard shortcut. So, I ended up just assigning Alt-D to Precise Duplicate. Get this script, switch to using Alt-D instead of Ctrl-D, and you’ll never have precision errors due to duplicating GameObjects again.

I was a little surprised how easy it was to write this script. As I was writing it I think I also figured out how the built-in Duplicate actually works. If you create a new GameObject in the root of the scene, assign it the same global position/rotation/etc as the GameObject you’re duplicating, and then assign it to the original’s parent, you will get the old floating point errors. Presumably, this is because there are rounding errors when transforming a global transform to a local transform on a new parent. Really, they should be assigning the parent first, and then assigning the local transform values.

Anyway, here’s the script:

    [ MenuItem( "GameObject/Precise Duplicate &d" ) ]
    public static void PreciseDuplicate( ) {
        if ( Selection.activeGameObject != null ) {

            GameObject newObj = GameObject.Instantiate<GameObject>( Selection.activeGameObject );
            newObj.name = Selection.activeGameObject.name;
            newObj.transform.SetParent( Selection.activeGameObject.transform.parent, false );

            Undo.RegisterCreatedObjectUndo( newObj, "Precise Duplicate " + Selection.activeGameObject.name );
            Selection.activeGameObject = newObj;
        }
    }

So, that takes care of the problem when you just need to create new siblings from existing GameObjects. However, you can still get precision errors when you change a GameObject’s parent or do a copy/paste into some other part of the scene.

To help with those situations, I created a Round Off Values script. This Editor script adds a context menu command to Transform and RectTransform components. So, if you click on the little gear at the top right of the component, you’ll see a new Round Off Values option. Executing that command will round off, localPosition, localEulerAngles, and localScale to the nearest integer. It also rounds anchoredPosition and sizeDelta for RectTransforms.

Here’s the script:

    [ MenuItem( "CONTEXT/Transform/Round Off Values" ) ]
    public static void RoundOffValues( MenuCommand command ) {
        // Handle rounding slightly differently for Transform vs. RectTransform
        if ( command.context is RectTransform ) {
            RectTransform rt = command.context as RectTransform;
            Undo.RecordObject( rt, "Round Off " + rt.name );

            rt.anchoredPosition = RoundVector2( rt.anchoredPosition );
            rt.localEulerAngles = RoundVector3( rt.localEulerAngles );
            rt.localScale = RoundVector3( rt.localScale );
            rt.sizeDelta = RoundVector2( rt.sizeDelta );
        } else {
            // Regular Transform
            Transform t = command.context as Transform;
            Undo.RecordObject( t, "Round Off " + t.name );

            t.localPosition = RoundVector2( t.localPosition );
            t.localEulerAngles = RoundVector3( t.localEulerAngles );
            t.localScale = RoundVector3( t.localScale );
        }
    }

    private static Vector2 RoundVector2( Vector2 v ) {
        return new Vector2( Mathf.Round( v.x ), Mathf.Round( v.y ) );
    }

    private static Vector3 RoundVector3( Vector3 v ) {
        return new Vector3( Mathf.Round( v.x ), Mathf.Round( v.y ), Mathf.Round( v.z ) );
    }

That’s it! You can also just download my TransformPrecision.cs which contains all the above code in a single C# script that you can just drop directly in your project’s Editor folder. Hopefully this script gives satisfaction to perfectionists without all the messy clicking and typing of doing it by hand. Good luck my OCD brethren!

Comments

comments