Modifying material modifies the actual .mat file!

Ok, I’ve encountered some very weird behavior, and wondering if it’s designed like this or a bug, and if anyone has ideas of workarounds.

I’m trying to change the texture of a material, that will affect all the renderers that use this material. What I did, was assign the Material property of my script through the editor

public Material someMat;

And then within the game I use:

someMat.mainTexture = otherTexture;

Now, everything is fine, the new material is used, and then I stop the game. Lo and behold, in the editor, after stopping the game, I still see all the renderers using the updated material! When I check in my file system, I see that the script actually changed the .mat file, not just for runtime, but permanently.

Is this the planned behavior or a bug? I thought only editor scripts modify actual resource files. Can anyone suggest a way to do this other than going through all the renderers and modifying them one by one?

When you create a Material in your project it will be an asset saved to the .mat file. This asset can be used on as much renderers as you like. All those renderers will use the material as sharedMaterial. That means all renderers just reference the material you assigned to them which is in your case the material asset inside your project folder.

Accessing the .material property in game will create a duplicate of the used material only for this renderer. This actually disconnects the renderer from the shared material.

What might confuses you is the fact that changes to the material are preserved when you leave playmode. That’s because only things that are saved to the scene is reverted when leaving playmode. Unity saves the scene to disk when you enter playmode and restore it afterwards. All assets you reference directly are changed directly. This is also true for prefab-assets. Just try it. Create a prefab of a cube, reference the prefab in a script of yours and change the position of the prefab. When you leave playmode you will notice that the prefab will have the new position you’ve just set in-game.

That’s why you should avoid changing assets directly. In a built game that’s not a real problem since all assets are stored in your build and loaded into memory when the game starts. Changes to assets will be lost once you restart your game.

However in the editor you can change any asset at any time. The only difference between editor scripts and ingame script is that editor scripts have an additional API set (the UnityEditor namespace) which is only available inside the editor. In general all script can do the same thing since they all run in the same mono environment. In-game script could also use editor stuff, but they are compiled without the UnityEditor reference so all the stuff in there is not known at compile time. Also it wouldn’t make sense to do something like that since you can’t build your game when you use UnityEditor stuff.

Ok back to the topic: What you can do is find all renderers which are using the material in question, create a duplicate in memory and assign this duplicated Material to all renderers sharedMaterial property.

Something like that:

// C#
public Material matAsset;

void Start()
    var allRenderers = (Renderer[])FindObjectsOfType(typeof(Renderer));
    var matInstance = new Material(matAsset);
    foreach (Renderer R in allRenderers)
        if (R.sharedMaterial == matAsset)
            R.sharedMaterial = matInstance;