How to change game object's parent in hierarchy without changing it's transform's position?

When we move a game object to be child of another game object in hierarchy, Unity changes it’s transform position so that the game object can maintain it’s global position in the world.

I think it’s a pain to always reset and/or recalculate positions when moving a game object in hierarchy so I want to know if there’s a simple/easier way to do this: move the game object so that it can maintain it’s LOCAL position. As if we were just writing transform.parent = parentGameObject.transform via scripts.

For example:

  • EmptyObjectP1 position is (0, 0, 0)
  • EmptyObjectP2 position is (10, 0, 0)
  • EmptyObjectChild is currently child of EmptyObjectP1 and it’s position is (2, 0, 0)
  • Now, if we move EmptyObjectChild to become parent of EmptyObjectP2, it’s position will change to (-8, 0, 0) and it will stay in the same position (2, 0, 0) in the world. I want it to stay (2, 0, 0) so that it’s global position change to (12, 0, 0)

Here are a couple of scripts to add this functionality in Edit mode. In the Editor folder, add the script ObjectMoverInspector. You can place the script ObjectMover anywhere (in a folder called Helpers for example). Yes, I have awesome naming conventions =]

If the new menu item doesn’t appear, just reopen the project. Then you can select Custom > Object Mover. This creates an empty object in the scene with the script ObjectMover attached. Drag and drop your child and new parent object in the Inspector then click the button.

ObjectMoverInspector.js

#pragma strict

@CustomEditor( ObjectMover )


class ObjectMoverInspector extends Editor 
{
	
	var childToMove : SerializedProperty;
	var newParent : SerializedProperty;
	
	var obj : GameObject;
	var objScript : ObjectMover;
	
	
	function OnEnable() 
	{
		obj = Selection.activeGameObject;
		objScript = obj.GetComponent( ObjectMover );
		
		childToMove = serializedObject.FindProperty( "childToMove" );
		newParent = serializedObject.FindProperty( "newParent" );
	}
	
	
	function OnInspectorGUI() 
	{
		// Update the serializedProperty - always do this in the beginning of OnInspectorGUI.
		serializedObject.Update();
		
		
		// childToMove
		EditorGUILayout.BeginHorizontal();
		EditorGUILayout.PropertyField( childToMove );
		EditorGUILayout.EndHorizontal();
		
		// newParent
		EditorGUILayout.BeginHorizontal();
		EditorGUILayout.PropertyField( newParent );
		EditorGUILayout.EndHorizontal();
		
		
		// Editor GUI Buttons
		EditorGUILayout.BeginHorizontal();
		
		if ( GUILayout.Button( "Reparent and position", GUILayout.MinWidth( 80 ), GUILayout.MaxWidth( 250 ) ) )
		{
			objScript.ChangeParentAndReposition();
		}
		
		EditorGUILayout.EndHorizontal();
		
		
        // Apply changes to the serializedProperty - always do this in the end of OnInspectorGUI.
        serializedObject.ApplyModifiedProperties();
	}
}


@MenuItem( "Custom/Object Mover" )
static function CustomObjectMover() 
{
	var go : GameObject = new GameObject( "_ObjectMover" );
	go.transform.position = Vector3.zero;
	go.transform.rotation = Quaternion.identity;
	go.AddComponent( ObjectMover );
}

ObjectMover.js

#pragma strict

public var childToMove : Transform;
public var newParent : Transform;

private var localPos : Vector3;
private var localRot : Quaternion;


function ChangeParentAndReposition() 
{
	if ( !childToMove || !newParent )
	{
		Debug.LogWarning( "Assign Objects in Inspector" );
		return;
	}
	
	Debug.Log( "Change Parent And Reposition" );
	
	localPos = childToMove.localPosition;
	localRot = childToMove.localRotation;
	
	childToMove.parent = newParent;
	childToMove.localPosition = localPos;
	childToMove.localRotation = localRot;
}