Have ability for editor runtime to not permanently change objects like Materials!!

From the beginning of my time with Unity I have never understood why script references to Materials mean any runtime changes to that material end up staying on the project’s Material.
Of course at runtime this does not happen and every time you restart the app/game you will start with the same Material.
So why have this annoyance and difference in the editor?
Why not add an option to reset such objects on finishing the player in the editor?
Or you know duplicate them all at start as I have to do in script do deal with this!

Really? That’s easy!!! Unity has combined editor scripting with the game engine code scripting.

That’s what makes editor scripting so drop-dead easy in Unity: it’s all the same morass of code and assets.

Why not use source control so you instantly know when you make this mistake?

And it is a mistake. If your runtime code is manipulating assets (Prefabs, Materials, whatever), fix it.

They could fix if they wanted. If you like how it is then say so.
Why not imagine that I already use source control?!

Sure, anything can be fixed. Is that really where you want Unity spending their time and effort?

Every interesting game level editor I’ve used (and written) relies on running the game right in the editor.

Now we would need a shiny new “I am editing now” button that you have to remember to tick and untick to use those scripts.

Where would that tickbox be? Front and center on an already-crowded UI? Buried deep in a menu somewhere that nobody finds it?

And it would NOT be optional. It would 100% mandatory anytime you wanted to use ANY editor script.

And if you forgot to tick “I am editing now”, then suddenly you’ve done some careful work and it’s all gone.

We already have that when people press PLAY and edit their scenes and then realize it’s all gone.

I’m sorry this has triggered you into imagining I like or dislike it.

I really don’t have a like / dislike position on it. It just works. It just is.

Did it bite me once? Yep! Twice? Maybe. Maybe even more times. But eventually I got it and realized that care had to be exercised because of this tight bonding between editor-time and play-time in the Unity editor.

Then it should be quick and easy to fix the broken code that is manipulating the asset(s) in question.

Now, if on the other hand you’re talking about the Windows vs MacOSX floating point discrepancies, that really is an emergent bug that could only be fixed by Unity writing their own float-to-string and string-to-float routines, and that also isn’t a great place for an engine company to be spending their time and effort.

Short version: This will probably never change, but there’s a simple way to avoid having it be a problem for you.

Long Version:
This is definitely one of the initially surprising inconsistencies in Unity around what Play-mode changes are preserved versus lost when leaving play mode. I believe it simply comes down to there being relatively few project assets (compared to scene objects) that typically get modified at runtime. It’s not just materials, though. It’s the same reason Scriptable Object maintain their modified values when leaving play mode, or why Audio Mixer settings also keep their modified values: Any time you’re modifying a Project Asset, there’s a very good chance that won’t get reset when leaving play mode. So, think is not a simple change to make to Unity.

Fortunately, there’s a relatively painless way to avoid this: Any time you access a Renderer’s materials at runtime, it will create temporary copies of those materials which are destroyed when leaving play mode. My standard approach to changing material properties at runtime always begins by accessing the renderer’s materials (in Start or Awake). For example, something like this:

public Renderer EmissiveRenderer;
private Material _emissiveMaterial;

void Awake()
{
    _emissiveMaterial = EmissiveRenderer.materials[EmissiveMaterialIndex];
}

You can now modify the properties of _emissiveMaterial as much as you want without affecting the actual material in your project. Simply accessing the .materials property of the renderer created temporary copies of its materials, which you can edit without affecting the original materials. This is how you can achieve having the same material on many renderers, all with different values for various properties.

Note, however, that you need to come at this from the Renderer side of thing. The way you’re doing it (having a script with direct access to a material) isn’t the correct approach. What you’re doing is effectively the same as editing any other project asset at runtime, which will generally survive leaving play mode.

2 Likes

It’s a design decision that was likely taken because of the hardware limitations of the time. Reloading the script domain and scenes isn’t that big of a deal because comparatively speaking they’re not that big, but resetting the assets you’re working with in a project could be very heavy on resources since you’d have to track the originals.

Unity was originally only available for macOS and the base model of an Apple system of the time only had at most two cores, 512MB RAM, and a 160GB HDD. PCs were rarely much better than that. I remember my Windows XP workstation of the time having similar specs (Athlox XP 3200, 512MB, 120GB HDD, and FX 5700).

https://en.wikipedia.org/wiki/IMac_G5#Specifications
https://en.wikipedia.org/wiki/IMac_(Intel-based)#Specifications_of_Polycarbonate_iMacs

I don’t know if it’s even possible to change it at this point in time, and it’s not like it can’t be avoided easily as was pointed out in the post above.

Was thinking the same thing as well. And this is probably historically why the Renderer.material property returns an instance rather than the actual material to safeguard against this.

There’s a lot of legacy bits of Unity that are probably because computers were potatoes comparatively at the time. Such as not being able to select components from among your assets with the object picker (now solved by the advanced search).

And I’m thinking, realistically, how do you determine whether an asset has been changed? The only way I can think of is backing up all assets to restore them after. But doing this just in case one or two may have changed seems way too overbearing and would greatly increase the applications memory footpring. It would also massively increase entering play mode times, something which is already too long for most people.

If there would be such a feature it would have to 100% be on an opt-in basis. Preferably configurable to specific asset types, like Material, as per the focus of this thread.

(As an aside, it’s fairly simple to implement this yourself with editor tooling. I have my own system used to reset certain assets after leaving play mode, though only scriptable object derived types at the moment)

Yes, despite dealing with this for years I never did a Material wrapper to handle it in a simple way so in Editor they could be instanced but not bother in standalone! better late than never

Yes agreed, but you can also just duplicate (instance) the Material in Awake if you do not have the renderer to hand (there are many ways to use a material in script). I do handle it, but as you said - it is one of the surprising inconsistencies. Even after years of using it I get reminded of it and wonder why!

Also you don’t have to instance the renderer materials, that is what sharedMaterial(s) is for, it depends on your needs as instancing too much may cost you

It’s different during runtime and editor because the asset database is frozen at runtime. This avoids accidental changes corrupting the game, and allows build size optimizations.

You can edit assets in the editor because many people want to control assets with the same scripts that they use during runtime, to allow easy generation of assets.

The solution is simple. Use OnDestroy to reset material properties that were changed at runtime. To avoid saving any modified assets, Use EditorUtility.ClearDirty.

You get a result like this

using System.Collections.Generic;
using UnityEditor;
using UnityEngine;

namespace SpriteUtilityComponents
{
public class CPingMaterialAnimator: GameComponent
{
    [SerializeField] private List<Material> m_PingMaterials;
    [SerializeField] private float m_Speed = 1f;
    private static readonly int ShineLocation = Shader.PropertyToID("_ShineLocation");

    private void Update()
    {
        foreach (Material material in m_PingMaterials)
        {
            if (material)
            {
                material.SetFloat(ShineLocation, Mathf.Repeat(Time.time * m_Speed, 1f));
                #if UNITY_EDITOR
                EditorUtility.ClearDirty(material);
                #endif
            }
        }
    }

    private void OnDestroy()
    {
        foreach (Material material in m_PingMaterials)
        {
            if (material)
            {
                material.SetFloat(ShineLocation, 0f);
                // Ensure reset is saved in the editor
#if UNITY_EDITOR
                EditorUtility.SetDirty(material);
#endif
            }
        }
    }
}
}
1 Like

This is a bit of an old issue. Runtime and runtime-editor should act the same if you ask me…
But I really dislike such specific code to deal with the fact that assets are not frozen during an editor run!
You should not have to write a Destroy function or record specific variables to get around this.

Until then instance materials during Awake to avoid messing them up - because what if editor crashes?

Frozen assets during runtime would break so many things. It would be impossible to adjust materials, impossible to change render texture settings, impossible to change scriptable objects. It’s a compromise that has benefits and drawbacks.

In the case of an editor crash, just reset the asset using source control. It’s only a mild annoyance. If you have used ClearDirty, then the asset will have never been saved in the first place, and there won’t be any issue at all.

2 Likes

OK agreed, but still no reason for such a level of code to backup shader properties on a material.
You must agree that such specific editor-only code to deal with this is not good?

Is it not simpler to loop the materials in Awake and do material[i] = new Material(material[i]); if in editor so you are dealing with instances of original unless you specifically need someone to mess with other material attributes during runtime and keep them as-is?

However in your very specific case - I can see you can make use of Destroy and the dirty flag, not sure more commonly a benefit

In my case, this material was used for many different sprites. So it’s much easier to edit the asset.

You can avoid needing any #ifdef simply by wrapping those SetDirty and ClearDirty in an utility method

In other cases, yes, instantiating materials at runtime is better.

Most people would just use the _Time global variable in the shader, and not even need to modify the asset. Very often, a material can be easily animated this way.