Everything about editor scripting in Unity with C# code

1. Add prefabs within circle

Some time ago I needed to make a forest. My trees were prefabs and I tried to use Unity's tree-brush tool - but it didn't work so I decided to make my own tool. This is the result:

Forest in Unity

To replicate this custom tree-brush tool you first need to add a plane which will act as a ground. It's important that this plane has a collider attached to it so we can send rays towards it to determine where we want to place the trees. Then you need to make a tree (or whatever gameobject you want to add) and make it a prefab by dragging it into the project's folder.

The first script you need is called ObjectManagerCircle and you need to attach it to an empty gameobject in the Editor, and drag the prefab to this script in the Editor. This script is kinda basic if you've been into Unity and it will just add or remove gameobjects that are within a certain radius. It will also remove all gameobjects if we want to restart the making of the forest.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ObjectManagerCircle : MonoBehaviour 
{
    //The object we want to add
    public GameObject prefabGO;

    //Whats the radius of the circle we will add objects inside of?
    public float radius = 5f;

    //How many GOs will we add each time we press a button?
    public int howManyObjects = 5;

    //Should we add or remove objects within the circle
    public enum Actions { AddObjects, RemoveObjects }

    public Actions action;

    //Add a prefab that we instantiated in the editor script
    public void AddPrefab(GameObject newPrefabObj, Vector3 center)
    {
        //Get a random position within a circle in 2d space
        Vector2 randomPos2D = Random.insideUnitCircle * radius;

        //But we are in 3d, so make it 3d and move it to where the center is
        Vector3 randomPos = new Vector3(randomPos2D.x, 0f, randomPos2D.y) + center;

        newPrefabObj.transform.position = randomPos;

        newPrefabObj.transform.parent = transform;
    }

    //Remove objects within the circle
    public void RemoveObjects(Vector3 center)
    {
        //Get an array with all children to this transform
        GameObject[] allChildren = GetAllChildren();

        foreach (GameObject child in allChildren)
        {
            //If this child is within the circle
            if (Vector3.SqrMagnitude(child.transform.position - center) < radius * radius)
            {
                DestroyImmediate(child);
            }
        }
    }

    //Remove all objects
    public void RemoveAllObjects()
    {
        //Get an array with all children to this transform
        GameObject[] allChildren = GetAllChildren();

        //Now destroy them
        foreach (GameObject child in allChildren)
        {
            DestroyImmediate(child);
        }
    }

    //Get an array with all children to this GO
    private GameObject[] GetAllChildren()
    {
        //This array will hold all children
        GameObject[] allChildren = new GameObject[transform.childCount];

        //Fill the array
        int childCount = 0;
        foreach (Transform child in transform)
        {
            allChildren[childCount] = child.gameObject;
            childCount += 1;
        }

        return allChildren;
    }
}

The next script you need to add is the Editor script that will improve the script above. In an editor script you will be able to instantiate a prefab, which you can't in a regular script. It's really important that you place this Editor script in a folder called Editor. Remember that you can have multiple Editor folders to make it easier to organize your project.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;

[CustomEditor(typeof(ObjectManagerCircle))]
public class ObjectManagerEditor : Editor
{
    private ObjectManagerCircle objectManager;

    //The center of the circle
    private Vector3 center;

    private void OnEnable()
    {
        objectManager = target as ObjectManagerCircle;

        //Hide the handles of the GO so we dont accidentally move it instead of moving the circle
        Tools.hidden = true;
    }

    private void OnDisable()
    {
        //Unhide the handles of the GO
        Tools.hidden = false;
    }

    private void OnSceneGUI()
    {        
        //Move the circle when moving the mouse
        //A ray from the mouse position
        Ray ray = HandleUtility.GUIPointToWorldRay(Event.current.mousePosition);

        RaycastHit hit;

        if (Physics.Raycast(ray, out hit))
        {
            //Where did we hit the ground?
            center = hit.point;

            //Need to tell Unity that we have moved the circle or the circle may be displayed at the old position
            SceneView.RepaintAll();
        }


        //Display the circle
        Handles.color = Color.white;

        Handles.DrawWireDisc(center, Vector3.up, objectManager.radius);


        //Add or remove objects with left mouse click

        //First make sure we cant select another gameobject in the scene when we click
        HandleUtility.AddDefaultControl(0);

        //Have we clicked with the left mouse button?
        if (Event.current.type == EventType.MouseDown && Event.current.button == 0)
        {
            //Should we add or remove objects?
            if (objectManager.action == ObjectManagerCircle.Actions.AddObjects)
            {
                AddNewPrefabs();

                MarkSceneAsDirty();
            }
            else if (objectManager.action == ObjectManagerCircle.Actions.RemoveObjects)
            {
                objectManager.RemoveObjects(center);

                MarkSceneAsDirty();
            }
        }
    }

    //Add buttons this scripts inspector
    public override void OnInspectorGUI()
    {
        //Add the default stuff
        DrawDefaultInspector();

        //Remove all objects when pressing a button
        if (GUILayout.Button("Remove all objects"))
        {
            //Pop-up so you don't accidentally remove all objects
            if (EditorUtility.DisplayDialog("Safety check!", "Do you want to remove all objects?", "Yes", "No"))
            {
                objectManager.RemoveAllObjects();

                MarkSceneAsDirty();
            }
        }
    }

    //Force unity to save changes or Unity may not save when we have instantiated/removed prefabs despite pressing save button
    private void MarkSceneAsDirty()
    {
        UnityEngine.SceneManagement.Scene activeScene = UnityEditor.SceneManagement.EditorSceneManager.GetActiveScene();

        UnityEditor.SceneManagement.EditorSceneManager.MarkSceneDirty(activeScene);
    }

    //Instantiate prefabs at random positions within the circle
    private void AddNewPrefabs()
    {
        //How many prefabs do we want to add
        int howManyObjects = objectManager.howManyObjects;

        //Which prefab to we want to add
        GameObject prefabGO = objectManager.prefabGO;

        for (int i = 0; i < howManyObjects; i++)
        {
            GameObject newGO = PrefabUtility.InstantiatePrefab(prefabGO) as GameObject;

            //Send it to the main script to add it at a random position within the circle
            objectManager.AddPrefab(newGO, center);
        }
    }
}

Now select the gamobject to which you attached the script. If everything is working, you should be able to do this:

Final prefabs within circle gif

If your terrain is not flat as it often is not if you are using Unity's terrain, then you just have to make another Raycast downwards from the random position to figure out where the ground is. You might also need to check if another tree is close to this position so the trees are not intersecting with each other.