Feature Highlight: Scene View Context Menu

Today, we want to highlight a feature introduced in Unity 6 Preview: The Scene View Context Menu

This update is designed to enhance your workflow and provide quicker access to essential tools and actions. In fact, the context menu in the Scene view offers customizable and context-sensitive options that adapt to your current selection.

This feature has been highly requested, and some resourceful users have already created their own custom menus in the Scene view. However, we aim to provide a simpler, standardized method for all users to customize the menu, including both pre-made menu items, through the ContextMenuUtility class, and entirely custom options, as demonstrated below.

Here’s an overview of the default Scene View context menu when a GameObject is actively selected.

Customizing the Scene view context menu with the prefix CONTEXT

It’s as easy as adding a MenuItem with the prefix CONTEXT to the menu item path.

For the default/built-in GameObjectToolContext, you can do the following:

// Add a menu item called "GameObject Tool Context Item" to the Scene view context menu when 
// the default context is active.
[MenuItem("CONTEXT/GameObjectToolContext/GameObject Tool Context Item")]
static void GameObjectContextMenuItem()
{
    Debug.Log("TestToolContext Menu Item");
}

For a custom (i.e. user-implemented) EditorToolContext, you can do the following:

// Add a menu item called "Custom Editor Tool Context Item" to the Scene view context menu when the
// EditorToolContext "TestToolContext" is engaged. 
[MenuItem("CONTEXT/TestToolContext/Custom Editor Tool Context Item")]
static void TestToolContextMenuItem()
{
    Debug.Log("TestToolContext Menu Item");
}
// Custom TestToolContext implementation for reference:
[EditorToolContext("Test Tool Context", typeof(Transform))]
public class TestToolContext : EditorToolContext
{
    protected override System.Type GetEditorToolType(Tool tool)  
    {  
        // Keep Move Tool available  
        if (tool == Tool.Move)
            return base.GetEditorToolType(tool);  

        // Hide other built-in tools 
        return null;
    }
}

In the context of an EditorTool, you can achieve this with the following:

// Add a menu item called "Custom Editor Tool Item" to the Scene view context menu when the 
// EditorTool "TestTool" is engaged.
[MenuItem("CONTEXT/TestTool/Custom Editor Tool Item")]
static void TestToolMenuItem()
{
    Debug.Log("TestTool Menu Item");
}

When working with components, you can proceed as follows:

// Add a menu item called "Custom Mesh Filter Item" to the Scene view context menu when the 
// GameObject selected has a MeshFilter component.
[MenuItem("CONTEXT/MeshFilter/Custom Mesh Filter Item")]
static void TestMeshFilterMenuItem()
{
    Debug.Log("MeshFilter Menu Item");
}

Customizing the Scene view context menu with PopulateMenu

Another way of adding custom items to the Scene view context menu is by utilizing the new PopulateMenu method in the EditorToolContext and EditorTool classes.

In fact, custom tools created using EditorToolContext can now add their own menu items to the Scene view context menu with the PopulateMenu method. This integration ensures that when you switch to a custom tool, relevant actions and settings are immediately accessible.

The following code example adds clipboard entries to the Scene view context menu when the CustomEditorToolContext is active.

[EditorToolContext("Custom Editor Tool Context")]
public class CustomEditorToolContext : EditorToolContext
{
    public override void OnToolGUI(EditorWindow _) { ... }
    protected override Type GetEditorToolType(Tool tool) { ... }

    public override void PopulateMenu(DropdownMenu menu)
    {
        ContextMenuUtility.AddClipboardEntriesTo(menu);
    }
}

Similarly, the PopulateMenu method in EditorTool allows you to extend your custom tools to support context-specific menus. This means that your tools can be more intuitive and tailored to different scenarios within the Scene View.

The following code example adds the Edit/Select All menu item to the Scene view context menu as Select/Select All when the CustomEditorTool is active.

class CustomEditorTool : EditorTool
{
    public override void OnToolGUI(EditorWindow _) { ... }

    public override void PopulateMenu(DropdownMenu menu)
    {
        ContextMenuUtility.AddMenuItem(menu, "Edit/Select All", "Select/Select All");
    }
}

Leveraging EditorAction for enhanced interactivity

The EditorAction class allows you to define custom actions that can be included in the context menu. These actions can be finely controlled and provide a direct way to trigger functionalities precisely when needed.

The code example below defines an EditorAction that spans multiple frames, enabling you to create a cube. Additionally, this EditorAction is added to the Scene view context menu for easy access.

// Add a menu item to the Scene View context menu that creates a cube where-ever the mouse clicks.
public class CreateCube : EditorAction
{
    GameObject m_GameObject;
    int m_UndoStart;

    // Prefixing the menu item path with "CONTEXT" indicates that this is a context menu item. 
    // Context menu items are not added to the application menu bar. The second section of the menu
    // path is the name of the type that this
    // menu item is applicable to. The context menu in the Scene View for example will look for context
    // menu items for each of the following types:
    // 1. The active EditorToolContext type.
    // 2. The active EditorTool type.
    // 3. All component types in the selection. Ex, Transform, MeshRenderer, BoxCollider, etc...
    // As an example, to create a context menu item that is shown when context clicking in the Scene
    // View with a GameObject selected that has a MeshFilter component, 
    // use "CONTEXT/MeshFilter/MyMenu Item".
    [MenuItem("CONTEXT/GameObjectToolContext/Create Cube")]
    static void Init()
    {
        EditorAction.Start<CreateCube>();
    }

    public CreateCube()
    {
        // Show a preview cube at the cursor.
        m_GameObject = GameObject.CreatePrimitive(PrimitiveType.Cube);
        // If the action is cancelled, we'll clean up the unused resource by calling Undo.PerformUndo().
        Undo.RegisterCreatedObjectUndo(m_GameObject, "Create Cube");
        // To avoid an unsightly "jump" when the cursor first moves, disable the preview until we have a
        // valid intersection point to place the object.
        m_GameObject.SetActive(false);
        m_UndoStart = Undo.GetCurrentGroup();
    }

    public override void OnSceneGUI(SceneView view)
    {
        var evt = Event.current;
        var id = GUIUtility.GetControlID(FocusType.Passive);

        if (evt.type == EventType.MouseMove)
        {
            HandleUtility.AddControl(id, 0);
            // Disable preview object so that we don't intersect with object placement ray.
            m_GameObject.SetActive(false);
            var intersected = HandleUtility.PlaceObject(evt.mousePosition, out var position, out var normal);
            m_GameObject.SetActive(true);
            if (intersected)
            {
                Undo.RecordObject(m_GameObject, "Create Cube");
                m_GameObject.transform.position = position;
                m_GameObject.transform.rotation = Quaternion.LookRotation(normal);
            }
        }

        // By checking that no mouse modifiers are active, we can allow for camera movement without 
        // breaking the action.
        if (evt.type == EventType.MouseDown && evt.modifiers == EventModifiers.None)
        {
            GUIUtility.hotControl = id;
            evt.Use();
        }

        if (GUIUtility.hotControl == id && evt.type == EventType.MouseUp)
        {
            evt.Use();
            GUIUtility.hotControl = 0;
            Finish(EditorActionResult.Success);
        }
    }

    // Since the object we want to instantiate is already in the scene, there is nothing more to do in the 
    // OnFinish function if the action exits successfully. If the action is cancelled however, we'll remove
    // the instantiated object from the scene by calling undo.
    protected override void OnFinish(EditorActionResult result)
    {
        if (result == EditorActionResult.Canceled)
        {
            Undo.PerformUndo();
            return;
        }

        Selection.activeObject = m_GameObject;
        // Merge the selection change and GameObject creation/placement to a single undo entry.
        Undo.CollapseUndoOperations(m_UndoStart);
    }
}

Probuilder 6

A prime example that leverages this new feature is ProBuilder 6!

The image below displays the Scene view context menu within the GameObject tool context for a ProBuilder Mesh.

The image below illustrates the Scene view context menu in the ProBuilder tool context for a ProBuilder Mesh face.

6 Likes

Though I probably won’t see it in action for quite some time (runtime fee), that looks like a great essential feature (same thoughts for Piercing Selection Menu)! Will it be possible to add a button to all context menus, regardless of the active tool?

1 Like

Lovely! :slight_smile:

Can’t wait to try this out now that I saw it!

Any chance you implement scene objects pipette in future ?

Hi @yasirkula :slightly_smiling_face:

You can add a menu item to the context menu regardless of which tool is active, as long as the tool falls under the GameObjectToolContext. Here’s how you can do it:

[MenuItem("CONTEXT/GameObjectToolContext/Custom GO Menu Item")]
static void TestGOMenuItem()
{
    Debug.Log("Custom GO Menu Item.");
}

If the active tool falls under a different EditorToolContext, you can achieve the same by replacing the context type in the MenuItem path.

2 Likes

The runtime fee is canceled, you should be able to enjoy this new feature! :smile: (You can read about it in this blog post: Unity is Canceling the Runtime Fee)

1 Like

I can’t seem to get this to work. Maybe I’m not in the right context. In the example pictures, there is a tool selected at the top of the tool panel that looks like a wrench (and something else I can’t identify):

I don’t have that tool in in my tool panel. What is it and how do I make it visible?

Hi @Stevens-R-Miller, the screenshot you’re referring to is from an example that adds context menu item for a specific custom (i.e. user implemented) tool context TestToolContext. Unfortunately, it doesn’t seem its implementation has been shared on the post which would have made things more clear.

To clarify, users can add custom context menu items for the following:

  • The default/built-in GameObject tool context (active if no custom context is activated):
    [MenuItem(“CONTEXT/GameObjectToolContext/GameObject Editor Tool Context Item”)]

  • A custom (i.e. user implemented) tool context (for example, if you have a TestToolContext implementation in your project and it’s activated):
    [MenuItem(“CONTEXT/TestToolContext/Custom Editor Tool Context Item”)]

  • A built-in Editor Tool (for example, when built-in MoveTool is active):
    [MenuItem(“CONTEXT/MoveTool/Move Tool Context Item”)]

  • A custom tool (for example, if you have a TestTool editor tool implementation in your project and it’s activated):
    [MenuItem(“CONTEXT/TestTool/Custom Editor Tool Context Item”)]

  • For a specific component for when it becomes part of selection (MeshFilter, for example):
    [MenuItem(“CONTEXT/MeshFilter/Mesh Filter Context Item”)]

I’d recommend to first try adding a custom context item to the built-in GameObjectToolContext as I shown in the first point.

Also, below is a TestToolContext implementation to get the original example you referred to working:

using UnityEditor;
using UnityEditor.EditorTools;
using UnityEngine;

[EditorToolContext("Test Tool Context", typeof(Transform))]
public class TestToolContext : EditorToolContext
{
    protected override System.Type GetEditorToolType(Tool tool)  
    {  
        // Keep Move Tool available  
        if (tool == Tool.Move)
            return base.GetEditorToolType(tool);  

        // Hide other built-in tools 
        return null;
    }
}

I’ve updated the post so that it doesn’t jump straight into custom context and explains the most-common (built-in GO context) case first.

Hey, thanks loads for making that clear.

Not to take advantage of you, but that same issue is on the only documentation page I can find on this as well: Unity - Scripting API: MenuItem

Might need to add the same clarification there.

Cheers!

1 Like