Replacing materials in sharedMaterials in an editor script.

I have a problem similar to http://answers.unity3d.com/questions/36659/setting-sharedmaterial-only-setting-for-one-object but the proposed solution does not satisfy my needs. I have a lot of prefabs imported into my project with multiple materials per mesh.

When I import an FBX into my project, UNity creates a set of materials for it. The thing is, there are a lot of materials on different objects that should be the same. The good example is glass material on buildings: I have a lot of different buildings with different wallsand roof materials, etc. but I want them all to use the same glass material, so I could set it up once, and every time I change it, all the windows would change.

Thing is there are tons of cross used materials (around 100) like that and a lot of prefabs (like 200 with 5-10 materials per prefab), so setting materials list manually would be a huge task.

I wrought an editor script to search for materials with certain names in objects and replace them with materials form a preset. It's working perfectly with an object, but never changes a prefab. I cannot use CopyPropertiesFromMaterial because it won't make different objects use the same material, just copy settings from my preset materials to another. Here's the code I use:

Material[] tempList = recipient.renderer.sharedMaterials;
    int changesDone = 0;
    for(int i = 0; i < tempList.Length; i++)
    {
        string replacingName = ParseMaterialName(tempList*.name);*
 *if(replacingName == "")*
 *{*
 *.....*
 *continue;*
 *}*
 *if(nativeMaterials.ContainsKey(replacingName))*
 *{*
 _tempList *= nativeMaterials[replacingName];*_
 _*changesDone++;*_
 _*......*_
 _*continue;*_
 _*}*_
 _*if(SharedMaterials.ContainsKey(replacingName))*_
 _*{*_
 <em>_tempList *= SharedMaterials[replacingName];*_</em> 
 <em>_*changesDone++;*_</em>
 <em>_*.....*_</em>
 <em>_*continue;*_</em>
 <em>_*}*_</em>
 <em>_*.....*_</em>
 <em>_*}*_</em> 
 <em>_*.....*_</em>
 <em>_*if(changesDone > 0)*_</em>
 <em>_*{*_</em>
 <em>_*recipient.renderer.sharedMaterials = tempList;*_</em>
 <em>_*return true;*_</em>
 <em>_*}*_</em>
 <em>_*......*_</em>
 <em>_*EditorUtility.SetDirty(recipient);*_</em>
 <em>_*AssetDatabase.SaveAssets();*_</em>
<em>_*```*_</em>
<em>_*<p>What am I doing wrong? Is it possible to somehow change the whole materials list of a prefab and save the changes to asset database?</p>*_</em>

I found a solution. Instead of

        EditorUtility.SetDirty(recipient);
        AssetDatabase.SaveAssets();

I should have used

        target.name = obj.name;
    UnityEngine.Object newPref = EditorUtility.CreateEmptyPrefab("Assets" + path + obj.name + ".prefab");
    EditorUtility.ReplacePrefab(target, newPref);
    AssetDatabase.Refresh();

The answer was as usual in docs all along: http://unity3d.com/support/documentation/ScriptReference/EditorUtility.ReplacePrefab.html

I have to note two peculiarities though:

  1. I have to refresh database after every object. Placing AssetDatabase.Refresh(); after all the objects were modified somehow failed to update prefabs for unknown reason.
  2. target.name = obj.name; is there because target (the object instantiated from initial prefab and being modified) has a "(clone)" part attached to it's name by default.

The problem here is that model-prefabs can't be saved because they are generated with the ModelImporter. There are different types of prefabs in Unity. Take a look at PrefabType.

Furthermore Unity is just doing what you told it ;). The modelimporter have a setting which materials it should create.

If you have many models that are updated very often you should create an AssetPostProcessor. Take a look at .OnPreprocessModel, .OnPostprocessModel and .OnAssignMaterialModel.


edit

To modify an existing prefab i always go this way:

  1. Get a prefab reference with prefab = AssetDatabase.LoadAssetAtPath.
  2. Instantiate the prefab using obj = EditorUtility.InstantiatePrefab.
  3. Apply your changes to the instantiated object.
  4. Save the changes to the prefab with EditorUtility.ReplacePrefab(obj,prefab).
  5. Use either AssetDatabase.ImportAsset or AssetDatabase.Refresh to update the asset.

I've discovered when you use `CreateEmptyPrefab` you might loose the references to this prefab because it's a new one. Just use the original prefab for ReplacePrefab it exchange the "content" and keep the prefab itself.