Here is an Editor Script to Help Place Objects on Ground

I was looking for a simple way to place objects on the ground or surface below the object and couldn’t seem to find any way in Unity to do it easily.

Here is an editor script that you can use to place the selected object on the surface below it. Just put this code into a file called DropObjectEditor.js in your Editor folder.

When you run it a small window opens that lets you place it by bottom, origin or center and you can simply dock the window in your editor and it’s always available then.

Please let me know of any bugs.

Cheers

EDIT: Realized multi-select dropping was trivial and still worked for single selections, so changed the code to drop multiple items. Easier to populate a large area or put items on a table…

class DropObjectEditor extends EditorWindow
{
	// add menu item
	@MenuItem ("Window/Drop Object")
	
	static function Init ()
	{
		// Get existing open window or if none, make a new one:
		var window : DropObjectEditor = EditorWindow.GetWindow(DropObjectEditor);
		window.Show ();
	}
	
	function OnGUI ()
	{
		GUILayout.Label ("Drop Using:", EditorStyles.boldLabel);
		GUILayout.BeginHorizontal();
		
		if(GUILayout.Button("Bottom"))
		{
			DropObjects("Bottom");
		}
		
		if(GUILayout.Button("Origin"))
		{
			DropObjects("Origin");
		}
		
		if(GUILayout.Button("Center"))
		{
			DropObjects("Center");
		}
		
		GUILayout.EndHorizontal();
	}
	
	function DropObjects(method : String)
	{
		// drop multi-selected objects using the right method
		for(var i : int = 0; i < Selection.transforms.length; i++)
		{
			// get the game object
			var go : GameObject = Selection.transforms[i].gameObject;
			
			// don't think I need to check, but just to be sure...
			if(!go)
			{
				continue;
			}
			
			// get the bounds
			var bounds : Bounds = go.renderer.bounds;
			var hit : RaycastHit;
			var yOffset : float;
		
			// override layer so it doesn't hit itself
			var savedLayer : int = go.layer;
			go.layer = 2; // ignore raycast
			// see if this ray hit something
			if(Physics.Raycast(go.transform.position, -Vector3.up, hit))
			{
				// determine how the y will need to be adjusted
				switch(method)
				{
					case "Bottom":
						yOffset = go.transform.position.y - bounds.min.y;
						break;
					case "Origin":
						yOffset = 0.0;
						break;
					case "Center":
						yOffset = bounds.center.y - go.transform.position.y;
						break;
				}
				go.transform.position = hit.point;
				go.transform.position.y += yOffset;
			}
			// restore layer
			go.layer = savedLayer;
		}
	}
}
2 Likes

Wow, that’s really helpful. Thank you so much! Unity should support something like this out of the box. But one question…

Is it possible to place it in the Editor as Button right there where all the other positioning “gizmos” like move/rotate/scale are? Haven’t done some Editor GUI scripting before so I have no idea if it’s possible or not.

In fact it seems there is a trick (but I know it only for Mac)

Place-on-surface function if you hold shift and apple, click the center of the GO’s gizmo and move it around (pivot point must be set to center in the top of Unity)
http://forum.unity3d.com/viewtopic.php?p=239837#239837

1 Like

It’s ctrl+shift for windows.

Yeah that does work - thank you :).

Awesome. Thanks!

I’ve customized your code to deal with a mixture of game objects who’s top object doesn’t have a renderer component on it. I just set the bounds, as in my case the location of that object is the bottom in game terms. Some other intrepid user might want to do a look up through the hierarchy to calculate a bounding volume based on child objects.

Thanks for posting the original!.

                // get the bounds
                var bounds : Bounds;
                
                if(go.renderer)
                {
					bounds = go.renderer.bounds; 
				}
                else
                {
                	bounds.center=go.transform.position;
                	bounds.extents=Vector3(0,0,0);                	
                }

Thank you!!

hey thanks bibbinator,
I converted the script to C#, and added the possibility to have the object’s rotation to be updated to match the surface’s normal direction. I hope it’s useful to someone!

using UnityEngine;
using UnityEditor;
public class DropObjectsEditor : EditorWindow
{
    RaycastHit hit;
    float yOffset;
    int savedLayer;
    bool AlignNormals;
    Vector3 UpVector = new Vector3(0, 90, 0);
    [MenuItem("Window/Drop Object")]                                                // add menu item
    static void Awake()
    {
        EditorWindow.GetWindow<DropObjectsEditor>().Show();                         // Get existing open window or if none, make a new one
    }

    void OnGUI()
    {
        GUILayout.Label("Drop using: ", EditorStyles.boldLabel);
        EditorGUILayout.BeginHorizontal();

        if (GUILayout.Button("Bottom"))
        {
            DropObjects("Bottom");
        }

        if (GUILayout.Button("Origin"))
        {
            DropObjects("Origin");
        }

        if (GUILayout.Button("Center"))
        {
            DropObjects("Center");
        }
        EditorGUILayout.EndHorizontal();
        AlignNormals = EditorGUILayout.ToggleLeft("Align Normals", AlignNormals);  // toggle to align the object with the normal direction of the surface
        if (AlignNormals)
        {
            EditorGUILayout.BeginHorizontal();
            UpVector = EditorGUILayout.Vector3Field("Up Vector", UpVector);          // Vector3 helping to specify the Up vector of the object
                                                                                     // default has 90° on the Y axis, this is because by default
                                                                                     // the objects I import have a rotation.
                                                                                     // If anyone has a better way to do this I'd be happy
                                                                                     // to see a better solution!
            GUILayout.EndHorizontal();
        }
    }

    void DropObjects(string Method)
    {
        for (int i = 0; i < Selection.transforms.Length; i++)                       // drop multi-selected objects using the selected method
        {
            GameObject go = Selection.transforms[i].gameObject;                     // get the gameobject
            if (!go) { continue; }                                                  // if there's no gameobject, skip the step — probably unnecessary but hey…

            Bounds bounds = go.GetComponent<Renderer>().bounds;                     // get the renderer's bounds
            savedLayer = go.layer;                                                  // save the gameobject's initial layer
            go.layer = 2;                                                           // set the gameobject's layer to ignore raycast

            if (Physics.Raycast(go.transform.position, -Vector3.up, out hit))       // check if raycast hits something
            {
                switch (Method)                                                     // determine how the y position will need to be adjusted
                {
                    case "Bottom":
                        yOffset = go.transform.position.y - bounds.min.y;
                        break;
                    case "Origin":
                        yOffset = 0f;
                        break;
                    case "Center":
                        yOffset = bounds.center.y - go.transform.position.y;
                        break;
                }
                if (AlignNormals)                                                   // if "Align Normals" is checked, set the gameobject's rotation
                                                                                    // to match the raycast's hit position's normal, and add the specified offset.
                {
                    go.transform.up = hit.normal + UpVector;
                }
                go.transform.position = new Vector3(hit.point.x, hit.point.y + yOffset, hit.point.z);
            }
            go.layer = savedLayer;                                                  // restore the gameobject's layer to it's initial layer
        }
    }
}

If anyone has an idea on how to make it so you can “undo” the script’s actions, that’d also be super awesome.

4 Likes

Some necro! But it’s a usefull script.

To enable Undo, just add Undo.RecordObjects(Selection.Transforms, "Drop Objects") to the top of the DropObjects script.

I would also add a null-check to the GetComponent - if the user has selected some objects that doesn’t have renderers, the script will fail.

1 Like

Saved me so much time. Thanks for these scripts.

Thanks guys! This should be put on some type of very useful scripts repository, very very useful!

Hey guys,

I believe I noticed a few bugs, and have a slightly modified implementation which has worked well for my purposes so far.

One is that the yOffset is not really what you want if your up vector is anything but perfectly vertical. Otherwise, you’ll be translating in y only, when really what you want is to translate some distance along the object’s y/up axis.

The other bug is that you need to use GetComponentsInChildren< Renderer >() to get the given GameObject’s full hierarchy of Renderers and compute a full bounding box from that, rather than assuming that if the given GameObject itself does not have a renderer, then neither will any of its descendents. For my purposes, I ended up not needing to compute any bounding box, but I believe something like this will work if you need it:

    Bounds GetBoundsForGameObjectHierarchy( GameObject go )
    {
        Bounds bounds = new Bounds();
        bounds.center = go.transform.position;
        bounds.extents = Vector3.zero;

        // Deal with parent and all descendents (GetComponentsInChildren() gets everything)
        Renderer[] rgDescendentRenderers = go.GetComponentsInChildren< Renderer >();
        foreach( Renderer renderer in rgDescendentRenderers  )
        {
            if ( !renderer )
                continue;

            bounds.Encapsulate( renderer.bounds );
        }

        return bounds;
    }

The original solution also does some stuff where it manipulates the rotation/up vector of the given game objects that that I didn’t need and I’m not sure makes sense. My solution will search down along the given set of game objects’ up vectors and place the objects on the closest objects it finds, and if you have “align rotations” checked, it will correctly align the given objects to the normal at the hit point(s).

The original solution also sets the GameObject’s layer property to ‘2’ temporarily, which seemed a bit hacky to me and potentially unreliable (from Googling a bit, it seemed like 2 was arbitrary and not representative of a constant somewhere and is not guaranteed to work). I changed the casting to a RaycastAll() and grabbed the closest object. From what I understand, it is acceptable to assume that the raycast will not hit the GameObject in question, assuming the origin of the cast starts within any collider, if the GameObject has one.

For my purposes, I’m dropping objects onto planets and what I’ve got here has been working well so far.

using UnityEngine;
using UnityEditor;

public class DropObjectsEditorWindow : EditorWindow
{
    private bool m_bAlignRotations = true;

    // Add a menu item
    [ MenuItem("Window/Drop Object(s)") ]

    static void Awake()
    {
        // Get or create an editor window
        EditorWindow.GetWindow< DropObjectsEditorWindow >().Show();
    }

    void OnGUI()
    {
        EditorGUILayout.BeginHorizontal();

        if ( GUILayout.Button( "Drop/align selected object(s)" ) )
        {
            DropObjects();
        }

        EditorGUILayout.EndHorizontal();

        // Add checkbox for rotation alignment
        m_bAlignRotations = EditorGUILayout.ToggleLeft( "Align Rotations", m_bAlignRotations );
    }

    void DropObjects()
    {
        Undo.RecordObjects( Selection.transforms, "Drop Objects" );

        for ( int i = 0; i < Selection.transforms.Length; i++ )
        {
            GameObject go = Selection.transforms[i].gameObject;
            if ( !go )
                continue;

            // Cast a ray and get all hits
            RaycastHit[] rgHits = Physics.RaycastAll( go.transform.position, -go.transform.up, Mathf.Infinity );

            // We can assume we did not hit the current game object, since a ray cast from within the collider will implicitly ignore that collision
            int iBestHit = -1;
            float flDistanceToClosestCollider = Mathf.Infinity;
            for( int iHit = 0; iHit < rgHits.Length; ++iHit )
            {
                RaycastHit CurHit = rgHits[ iHit ];

                // Assume we want the closest hit
                if ( CurHit.distance > flDistanceToClosestCollider )
                    continue;

                // Cache off the best hit
                iBestHit = iHit;
                flDistanceToClosestCollider = CurHit.distance;
            }

            // Did we find something?
            if ( iBestHit < 0 )
            {
                Debug.LogWarning( "Failed to find an object on which to place the game object " + go.name + "." );
                continue;
            }

            // Grab the best hit
            RaycastHit BestHit = rgHits[ iBestHit ];

            // Set position
            go.transform.position = new Vector3( BestHit.point.x, BestHit.point.y, BestHit.point.z );

            // Set rotation
            if ( m_bAlignRotations )
            {
                go.transform.rotation *= Quaternion.FromToRotation( go.transform.up, BestHit.normal );
            }
        }
    }
}

I’m new to C#, so please do let me know if you notice anything weird in what I’m doing, or see any bugs/problems/etc. Thanks a lot.

6 Likes

@4Xrn7oIe : thanks a million – this has saved me countless hours of arranging fiddly little things across a terrain :slight_smile:

Hi. It doesn’t work correctly for some objects.