0
\$\begingroup\$

I have a lot of duplicated objects, and I need in editor, not in the game, remove specific (18) material slot. Remove not material in slot 18, but remove slot 18. Manually, by hands, it can be done using this button:

enter image description here

All objects that I need to remove slot from has the next mutuals:

  1. Contain "_lod" in the name;
  2. Have the same mesh, with the same GUID

I did not find how to get GUID of the given object, so the search, probably need to go by name.

I have never scripted Unity, I even did not write in C# (although I write in C++).

What I done so far: I created an empty game object and assign cs script below to it as a component. DestroyImmediate(myMaterials[18]), works almost that I need, but it removes not the slot, but the material on it (and automatically creates default material). I need to remove exactly the slot.

LookAtPoint.cs:

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

using UnityEditor;
[ExecuteInEditMode]

public class LookAtPoint : MonoBehaviour
{
    void Iter(GameObject gameObject) // Itterate children
    {
        for (int j = 0; j < gameObject.transform.childCount; j++) 
        {
            GameObject a = (GameObject)gameObject.transform.GetChild(j).gameObject;
            MeshFilter viewedModelFilter = (MeshFilter)a.GetComponent("MeshFilter");

            if(viewedModelFilter && viewedModelFilter.mesh && a.name.IndexOf("_lod") != -1) // check if object has mesh and it's name contains "_lod"
            {
                Material[] myMaterials = a.GetComponent<Renderer>().materials;
                if(myMaterials[18].name.IndexOf("Подоконник")!=-1) // not neccesary check if 18s slot has "Подоконник" name (actually now it is already "Default-Material")
                {
                    DestroyImmediate(myMaterials[18]);
                }
            }
            Iter(a);
        }
    }

    void Start()
    {
        List<GameObject> rootObjects = new List<GameObject>();
        Scene scene = SceneManager.GetActiveScene();
        scene.GetRootGameObjects( rootObjects );
        
        for (int i = 0; i < rootObjects.Count; ++i)
        {
            GameObject gameObject = rootObjects[ i ]; // get root objects

            for (int j = 0; j < gameObject.transform.childCount; j++) // enumarate them
            {
                Iter(gameObject); // iteratevely process the children 
            }
        }
    }
}
\$\endgroup\$
11
  • \$\begingroup\$ When you expect to do that more often, then it would be easier to create all those objects as instances of the same prefab. That way you can make a change on the prefab and it automatically gets applied to all the instances. If you want multiple different prefabs which share some properties, then you can use prefab variants They allow you to have prefabs inheritance. \$\endgroup\$
    – Philipp
    Mar 21, 2022 at 20:55
  • \$\begingroup\$ "So, seems that I have to enumerate all game objects..." sounds like a reasonable plan. How have you tried implementing this plan so far, and what specific step do you need help with? \$\endgroup\$
    – DMGregory
    Mar 22, 2022 at 3:22
  • \$\begingroup\$ @Philipp, yeah, but I need to solve current issue \$\endgroup\$ Mar 22, 2022 at 11:56
  • \$\begingroup\$ @DMGregory, well, I have success, I get material of the object I need by Material[] myMaterials = object.GetComponent<Renderer>().materials;, but I can't remove it. Destroy does not compile, DestroyImmediate removes material but not the slot. I need exactly remove the slot (object have few materials slots). Probably rewrite my question to specify this \$\endgroup\$ Mar 22, 2022 at 12:59
  • 1
    \$\begingroup\$ @Stdugnd4ikbd When you already have a working solution, then it would be easier if you posted an own answer to your question. Answering your own question is encouraged on Stack Exchange. \$\endgroup\$
    – Philipp
    Mar 22, 2022 at 14:17

1 Answer 1

0
\$\begingroup\$

So, as @DMGregory pointed in comments, instead of destroying material, the element of the materials array with needed material should be removed, and new, resized array should be set as component:

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

using UnityEditor;
[ExecuteInEditMode] // for running in editor

public class LookAtPoint : MonoBehaviour
{
    void Iter(GameObject gameObject) // Itterate children
    {
        for (int j = 0; j < gameObject.transform.childCount; j++) 
        {
            GameObject a = (GameObject)gameObject.transform.GetChild(j).gameObject;
            MeshFilter viewedModelFilter = (MeshFilter)a.GetComponent("MeshFilter");

            if(viewedModelFilter && viewedModelFilter.mesh && a.name.IndexOf("_lod") != -1) // check if object has mesh and it's name contains "_lod"
            {
                Material[] myMaterials = a.GetComponent<Renderer>().materials;
                if(myMaterials[18].name.IndexOf("Подоконник")!=-1) // not neccesary check if 18s slot has "Подоконник" name (actually now it is already "Default-Material")
                {
                   Material[] aa = new Material[24]; // new array that will be assign. 24 because current length is 25

                   for(int i = 0; i < 24; i++) // fill new array by existing, except 18' element 
                   if(i < 18)
                     aa[i] = myMaterials[i];
                   else if(i >= 18)
                     aa[i] = myMaterials[i + 1];

                   a.GetComponent<Renderer>().materials = aa; // set new array
                }
            }
            Iter(a);
        }
    }

    void Start()
    {
        List<GameObject> rootObjects = new List<GameObject>();
        Scene scene = SceneManager.GetActiveScene();
        scene.GetRootGameObjects( rootObjects );
        
        for (int i = 0; i < rootObjects.Count; ++i)
        {
            GameObject gameObject = rootObjects[ i ]; // get root objects

            for (int j = 0; j < gameObject.transform.childCount; j++) // enumarate them
            {
                Iter(gameObject); // iteratevely process the children 
            }
        }
    }
}

Summarizing for newcomers like me:

  1. In assets, create C# script with code above (notice, that the file name and class name should be the same);
  2. Choose any game object on the scene, and open its properties window;
  3. In properties window click Add Component at the bottom (scroll might be required);
  4. Find and choose the ealier created script by file name;
  5. By adding the component, the Start method will be called, so that's it;
\$\endgroup\$
3
  • \$\begingroup\$ Rather than using [ExecuteInEditMode] and invoking this behaviour in Start, you might want to make a function with a [ContextMenu("Remove Material")] attribute. Then you can choose when to invoke the method by selecting it from the three-dot menu in the top-right corner of the component inspector. \$\endgroup\$
    – DMGregory
    Mar 23, 2022 at 14:34
  • \$\begingroup\$ @DMGregory, I think, if to talk about the UI, then it would be better to just add some button \$\endgroup\$ Mar 23, 2022 at 15:15
  • \$\begingroup\$ Adding a button is probably best. The advantage of the context menu attribute is that you don't have to write a custom editor script. So it's good for quick & dirty / debug-y stuff like this where you just want it to work without getting in your way or spending more than a bare minimum of engineering time & energy / complexity on it. \$\endgroup\$
    – DMGregory
    Mar 23, 2022 at 15:19

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .