Changing duplicated object color/texture

I have a project where certain objects can be edited by the user. Between the options are changing the color, changing the texture and duplicating the object.

My issue is that when changing an object color or texture, all of the duplicates are also affected by the change.

The color and texture change are done via the lines:

gameObject.GetComponentInChildren<Renderer>().material.SetTexture("_MainTex", m_TextureList[colorIndex - 1]);```

While the duplication is done with:

```GameObject newModel = Instantiate(m_ActiveObject, newPosition, m_ActiveObject.transform.rotation);```

Am I forgetting an argument or something when Instantiating the duplicate?

You need to duplicate the material as well.

So I do the duplication, instantiate a copy of the material, and set it as the object material?
I have the same material used on other objects as well (to all of them initially), but those are not affected by the color/texture change,

Weirdly enough, it is now working as intend, without changes…

Instead of duplicating materials you could also use Material Property Blocks:

Here is a nice writeup on what they do:

The advantage with Material Property Blocks is that you can change the appearance without the need for extra materials.

Quote from the docs:
“Unity’s terrain engine uses MaterialPropertyBlock to draw trees; all of them use the same material, but each tree has different color, scale & wind factor.”

Here is a component which I use to handle those (though it’s work in progress):

using UnityEngine;
using System.Collections.Generic;
#if UNITY_EDITOR
using UnityEditor;
#endif

namespace Kamgam.Helpers
{
    public class MaterialPropertyBlockSetter : MonoBehaviour
    {
        public Renderer Renderer;
        public int MaterialIndex = 0;

        public class MaterialProperty<T>
        {
            public string PropertyName;
            public T Value;

            public MaterialProperty(string propertyName, T value)
            {
                PropertyName = propertyName;
                Value = value;
            }
        }

        protected List<MaterialProperty<Color>> cachedColors;
        protected List<MaterialProperty<Texture>> cachedTextures;

        protected MaterialPropertyBlock propertyBlock;

        /// <summary>
        /// Are there some scheduled changes?
        /// </summary>
        /// <returns></returns>
        public bool HasScheduledChanges()
        {
            return (cachedColors != null && cachedColors.Count > 0) || (cachedTextures != null && cachedTextures.Count > 0);
        }

        /// <summary>
        /// Schedules a color change. Call Flush() or Apply() to apply it.

        /// Notice that only the changes within one Schedule(..) -> Flush() cycle are applied, all other properties are reset.
        /// </summary>
        /// <param name="colorPropertyName">An often used name is "_BaseColor".</param>
        /// <param name="color"></param>
        public void ScheduleColorChange(string colorPropertyName, Color color)
        {
            if(cachedColors == null)
                cachedColors = new List<MaterialProperty<Color>>();

            addOrUpdateScheduled(cachedColors, colorPropertyName, color);
        }

        /// <summary>
        /// Schedules a texture change. Call Flush() or Apply() to apply it.

        /// Notice that only the changes within one Schedule(..) -> Flush() cycle are applied, all other properties are reset.
        /// </summary>
        /// <param name="colorPropertyName">An often used name is "_MainTex".</param>
        /// <param name="texture"></param>
        public void ScheduleTextureChange(string colorPropertyName, Texture texture)
        {
            if (cachedTextures == null)
                cachedTextures = new List<MaterialProperty<Texture>>();

            addOrUpdateScheduled(cachedTextures, colorPropertyName, texture);
        }

        protected void addOrUpdateScheduled<T>(IList<MaterialProperty<T>> source, string propertyName, T propertyValue)
        {
            int index = -1;
            for (int i = 0; i < source.Count; i++)
            {
                if (source[i].PropertyName == propertyName)
                {
                    index = i;
                    break;
                }
            }

            if (index >= 0)
            {
                source[index].Value = propertyValue;
            }
            else
            {
                source.Add(new MaterialProperty<T>(propertyName, propertyValue));
            }
        }

        /// <summary>
        /// Applies all scheduled changes and clears the cache of scheduled changes.
        /// </summary>
        public void Flush()
        {
            Apply();
            ClearScheduled();
        }

        /// <summary>
        /// Applies all scheduled changes but does NOT clear the cache.
        /// </summary>
        public void Apply()
        {
            if (propertyBlock == null)
                propertyBlock = new MaterialPropertyBlock();

            if (Renderer == null)
                throw new System.Exception("Renderer is null.");

            // get block
            Renderer.GetPropertyBlock(propertyBlock, MaterialIndex);

            // colors
            if (cachedColors != null)
            {
                foreach (var colorProp in cachedColors)
                {
                    propertyBlock.SetColor(colorProp.PropertyName, colorProp.Value);
                }
            }

            // textures
            if (cachedTextures != null)
            {
                foreach (var textureProp in cachedTextures)
                {
                    propertyBlock.SetTexture(textureProp.PropertyName, textureProp.Value);
                }
            }

            // set block
            Renderer.SetPropertyBlock(propertyBlock, MaterialIndex);
        }

        /// <summary>
        /// Clears the cache which holds the scheduled changes.
        /// </summary>
        public void ClearScheduled()
        {
            if (cachedColors != null) cachedColors.Clear();
            if (cachedTextures != null) cachedTextures.Clear();
        }

        /// <summary>
        /// Resets the properties.

        /// Does not affect the scheduled changes. It simply creates a new empty property block and applies that. All scheduled changes will still be in the cache afterwards.
        /// </summary>
        public void ResetProperties()
        {
            if (Renderer == null)
                throw new System.Exception("Renderer is null.");

            propertyBlock = new MaterialPropertyBlock();
            Renderer.SetPropertyBlock(propertyBlock, MaterialIndex);
        }
    }

#if UNITY_EDITOR
    [CustomEditor(typeof(MaterialPropertyBlockSetter))]
    public class MaterialPropertyBlockSetterEditor : Editor
    {
        public override void OnInspectorGUI()
        {
            base.OnInspectorGUI();

            var setter = target as MaterialPropertyBlockSetter;

            if (GUILayout.Button("Reset Properties"))
            {
                setter.ResetProperties();
            }

            if (GUILayout.Button("Clear Scheduled Changes"))
            {
                setter.ClearScheduled();
            }

            if (GUILayout.Button("Test: Flush random color to _BaseColor"))
            {
                setter.ScheduleColorChange("_BaseColor", new Color(Random.value, Random.value, Random.value, Random.value));
                setter.Flush();
            }

            if (GUILayout.Button("Test: Apply random color to _BaseColor"))
            {
                setter.ScheduleColorChange("_BaseColor", new Color(Random.value, Random.value, Random.value, Random.value));
                setter.Apply();
            }
        }
    }
#endif
}
1 Like