Selection.gameObjects returns a list of n gameobjects n times

I made a very simple code to convert terrains to mesh, but playing with the Selection class, I found that it not only returns the list of objects I selected, but it returns it n times.

Note that it is the same for Selection.objects.

[MenuItem("GameObject/Tools/Terrain To Mesh", false, 0)]
public static void CreateMaterial(MenuCommand command)
{
    Terrain terrain = Selection.activeGameObject.GetComponent<Terrain>();

    foreach (var item in Selection.gameObjects)
    {
        Debug.Log(item);
    }
}

Here is my selection and the result:

If I use Selection.activeObject, here is the result for the same selection:

image

What is going on?

Haven’t used MenuItems a lot recently, but you created a context specific menu item. AFAIK the method will be called for every object you have currently selected and the actual object will be passed in the MenuCommand context. You just additionally iterate through all objects in the selection.

If you want a general tool menu item, you probably shouldn’t group it under GameObject and remove the MenuCommand parameter from your method. If you want context specific MenuItems, you should use something like

 [MenuItem("CONTEXT/Terrain/Terrain2Mesh")]
public static void CreateMaterial(MenuCommand command)
{
    Terrain terrain = (Terrain)command.context;
    // [ ... ]

Though when you select multiple Terrain objects and execute that command, it would be executed multiple times. So it depends on what you actually want to do here. Why do you select multiple objects in the first place?

Also you may try adding a Debug.Log to the start of your method to see how many times it gets called.

2 Likes

Thanks for the reply.

I have a map made from several unity terrains. Since unity does not allow light baking with custom shaders, at least mine, I want to convert my terrain into a mesh to benefit from light baking. But I would need to implement a LOD system etc.

With the two terrains selected, it is called 2 times. So it loops through the selected objects 2 times.

I just tried the (Terrain)command.context but the cast is not working. I also tried with acommand.context as Terrain but the value is null.

Maybe Debug.Log the command.context.GetType() to see what you’re working with with.

Is your context menu still for the game object?

The command.context.GetType() returns a GameObject. That is exactly what I want.

So I tried the following line and now it works.

Terrain terrain = ((GameObject)command.context).GetComponent();

I guess you missed this particular part of Bunny’s example:

[MenuItem("CONTEXT/Terrain/Terrain2Mesh")]

Where this will mean the context menu option will only show up when right clicking the component header of a Terrain component. Makes much more sense to only show it in a more narrow context here.

I just tried with the “CONTEXT”, but I did not know that the “Terrain” was important.

Unfortunately, I made the change and still don’t have a button to click:

Here is the code:

[MenuItem("CONTEXT/Terrain/Terrain To Mesh")]
public static void CreateMeshFromTerrain(MenuCommand command)
{
    Terrain terrain = ((GameObject)command.context).GetComponent<Terrain>();

    Debug.Log(terrain);
}

Bolded for emphasis.

Oh yes, I missed that part.

But it is not what I would like to see. It is easier to select the objects within the hierarchy instead of doing it for each component.

I going to keep the previous code with the MenuItem GameObject.

Thanks for the help.

If you multi-select game objects which share one or more components, it should still draw the component header, even if the component itself doesn’t support multi-editing:

I agree, but I don’t really find that intuitive, instead of just having a tab called “Tools” in the hierarchy menu to do the same thing.

It also means that if you select a game object that isn’t a terrain, you can’t click on the component as it won’t appear.

I mean I don’t find it intuitive showing an option that doesn’t do anything if the game object doesn’t have a terrain component.

But at least write your code to pattern match the context into a GameObject, and TryGetComponent for a Terrain component so it doesn’t at least ram head-first into cast exceptions or null-reference exceptions.

I understand that point.

That’s what I did.

1 Like

I agree. That’s why I said

Adding a MenuCommand parameter makes the method context specific. Which means Unity will call it for every instance that matches the context. When you just create a MenuItem like this

 [MenuItem("Tools/Terrain2Mesh")]
public static void CreateMaterial()
{
    Terrain[] terrains = (Terrain[])Selection.GetFiltered(typeof(Terrain), SelectionMode.Unfiltered);
    // [ ... ]

Not sure if the GetFiltered would work but I guess it should. This would be a normal menu item which you can also add a keyboard shortcut to it if you want.

1 Like

I must keep the “GameObject” before “Tools”, otherwise, it does not appear in the menu.

Thanks for the solution.