Search code examples
unity-game-engineunity-editor

How can I create an object at the mouse point in scene view by clicking not dragging the object?


Generally, most objects are placed in the scene view by dragging or something. I want to right click the mouse (without dragging an object) to create an object in the scene view. I know this would require some editor coding but I’m not sure how to go about it.

UPDATE

After giving it some thought I realised that using a MenuItem would be quite appropriate for me. Here is my code below:

SLMenuItems:

public class SLMenuItems : MonoBehaviour {

public bool canClickSceneViewToCreatePath = false;

void Start()
{

}

[MenuItem("Component/Create Custom Object")]
static void CreateObject()  {
   Debug.Log("menu item selected");
    canClickSceneViewToCreatePath = true;
} 
}

SLMenuItemsEditor:

    [CustomEditor(typeof(SLMenuItems))]
public class SLMenuItemsEditor : Editor {
    SLMenuItems slMenuItems;


    void OnEnable()
    {
        slMenuItems = (SLMenuItems)target;

    }


    void OnSceneGUI()
        {
            if (slMenuItems.canClickSceneViewToCreatePath)  {
                Vector3 pointsPos = HandleUtility.GUIPointToWorldRay(Event.current.mousePosition).origin;

                if (Event.current.type == EventType.MouseDown && Event.current.button == 0)
                {

                    // create object here at pointsPos

                    slMenuItems.canClickSceneViewToCreatePath = false;
                }
            }

        }
    }

I keep getting the following error:

Assets/SLMenuItems.cs(23,9): error CS0120: An object reference is required to access non-static member `SLMenuItems.canClickSceneViewToCreatePath'

pointing to the line:

canClickSceneViewToCreatePath = true;

in SLMenuItems.


Solution

  • Your CreateObject method is static but your canClickSceneViewToCreatePath value is not.

    It has nothing to do with the editor script but with your class SlMenuItems itself.

    A static method is not instanced or with other words it is kind of shared between all instances of that component type while the non-static value might be different for each component.

    So how should a static method - which is the same for all instances - "know", which of the instances values it should access?

    So either make the method non-static or the variable static. Depending on what your further need is.

    Since the MenuItem needs a static method make the variable static as well.


    I would suggest you make that class not inherit from MonoBehaviour at all since it doesn't have any behaviour for a GameObject. It only brings some editor features so rather make it a static class that can "live" in the Assets without needing to be instanced.

    Than you can use SceneView.onSceneGUIDelegate to register a callback for OnSceneGUI without implementing an editor script for that:

    private static GameObject lastCreated;
    
    private static bool isCreating;
    
    public static class SLMenuItems
    {   
        [MenuItem("Component/Create Custom Object")]
        private static void CreateObject() 
        {
            Debug.Log("menu item selected");
    
            isCreating = true;
            lastCreated = null;
    
            // Add a callback for SceneView update
            SceneView.onSceneGUIDelegate -= UpdateSceneView;
            SceneView.onSceneGUIDelegate += UpdateSceneView;
        }
    
        private static void UpdateSceneView(SceneView sceneView)
        {
            if(lastCreated)
            {
                // Keep lastCreated focused
                Selection.activeGameObject = lastCreated;
            }
    
            if(isCreating)
            {  
                if (Event.current.type == EventType.MouseDown && Event.current.button == 0)
                {
                    Vector3 pointsPos = HandleUtility.GUIPointToWorldRay(Event.current.mousePosition).origin;
    
                    //Todo create object here at pointsPos
                    lastCreated = Instantiate(somePrefab);
    
                    // Avoid the current event being propagated
                    // I'm not sure which of both works better here
                    Event.current.Use();
                    Event.current = null;
    
                    // Keep the created object in focus
                    Selection.activeGameObject = lastCreated;
    
                    // exit creation mode
                    isCreating = false;
                }
            } 
            else
            {
                // Skip if event is Layout or Repaint
                if(e.type == EventType.Layout || e.type == EventType.Repaint)
                {
                    Selection.activeGameObject = lastCreated;
                    return;
                }
    
                // Prevent Propagation
                Event.current.Use();
                Event.current = null;
                Selection.activeGameObject = lastCreated;
                lastCreated = null;
    
                // Remove the callback
                SceneView.onSceneGUIDelegate -= UpdateSceneView;
            }      
        }
    }
    

    But I suggest you change your questions title since this is actually not the solution to the "task" you describe before.