Sprites and MaterialPropertyBlocks

Hi all!

Currently i’m trying to set up a MaterialPropertyBlock for a sprite thats animating, but it’s not behaving as it should, below is the script i’m using:

using UnityEngine;

public class TestScript : MonoBehaviour
{
    public SpriteRenderer mySpriteRenderer;
    private MaterialPropertyBlock theSprite;

    private Material spriteMaterial;

    public int spriteBaseColorID;

    public int spriteMaterialElement;

    [SerializeField, ColorUsage(true, true)] private Color startingSpriteColor;

    void Start()
    {
        if (!TryGetComponent(out mySpriteRenderer)){}

        if (mySpriteRenderer != null)
        {
            // Set up property block for sprite material.
            theSprite = new MaterialPropertyBlock();

            // If you accidentally choose a non-existant element.
            if (spriteMaterialElement > mySpriteRenderer.materials.Length - 1)
            {
                Debug.Log("<color=orange>Warning</color> : Selected a non-existant material on <color=green>" + this.gameObject.name + "</color>, Element <color=green>0</color> selected as default");
                spriteMaterialElement = 0;
            }

            // Cache mesh material to copy default values.
            spriteMaterial = mySpriteRenderer.materials[spriteMaterialElement];

            // Get chosen element properties from the Sprite Renderer and assign it to theSprite MaterialPropertyBlock.
            mySpriteRenderer.GetPropertyBlock(theSprite, spriteMaterialElement);

            // Set sprite default color, this will detect which property the sprite uses for it's base color and sets starting color field accordingly
            if (spriteMaterial.HasProperty("_BaseColor"))
            {
                spriteBaseColorID = Shader.PropertyToID("_BaseColor");

                // Pre-cache the sprites starting color
                startingSpriteColor = spriteMaterial.GetVector(spriteBaseColorID);

                // Apply it to the block
                theSprite.SetVector(spriteBaseColorID, startingSpriteColor);
            }
        }

        // Set the property block
        if (mySpriteRenderer != null)
        {
            mySpriteRenderer.SetPropertyBlock(theSprite, spriteMaterialElement);
        }
    }


    void Update()
    {
        // Get the block at the start of each frame
        if (mySpriteRenderer != null)
        {
            mySpriteRenderer.GetPropertyBlock(theSprite, spriteMaterialElement);
        }



        // Set the block at the end of each frame
        if (mySpriteRenderer != null)
        {
            mySpriteRenderer.SetPropertyBlock(theSprite, spriteMaterialElement);
        }
    }
}

The issue is, the moment I hit play and the block applies it seems to tint the sprite white, despite looking perfect in editor mode:

Before Play:

After Play:

6386247--711660--upload_2020-10-5_23-29-59.png

I’m not sure if the property block is somehow double-stacking the color or something? any help would be greatly appreciated!

Many thanks in advance.

SOLVED:

It was indeed the MaterialPropertyBlock, below is the working code with comments for ease of understanding.

Effectively, you need to check if the texture in the block matches the SpriteRenderer’s texture, if it doesnt set the block with the new texture BEFORE getting it at the beginning of the frame:

using UnityEngine;

public class TestScript : MonoBehaviour
{
    public SpriteRenderer mySpriteRenderer;
    private MaterialPropertyBlock theSprite;

    public Material spriteMaterial;
    public Texture currentSpriteBaseTexture;

    public int spriteBaseColorID;
    public int spriteMainTextureID;

    public int spriteMaterialElement;

    [SerializeField, ColorUsage(true, true)] private Color startingSpriteColor;

    void Start()
    {
        if (!TryGetComponent(out mySpriteRenderer)){}

        if (mySpriteRenderer != null)
        {
            // Set up property block for sprite material.
            theSprite = new MaterialPropertyBlock();

            // If you accidentally choose a non-existant element.
            if (spriteMaterialElement > mySpriteRenderer.materials.Length - 1)
            {
                Debug.Log("<color=orange>Warning</color> : Selected a non-existant material on <color=green>" + this.gameObject.name + "</color>, Element <color=green>0</color> selected as default");
                spriteMaterialElement = 0;
            }

            // Cache mesh material to copy default values.
            spriteMaterial = mySpriteRenderer.materials[spriteMaterialElement];

            // Get chosen element properties from the Sprite Renderer and assign it to theSprite MaterialPropertyBlock.
            mySpriteRenderer.GetPropertyBlock(theSprite, spriteMaterialElement);

            // Set an ID for Sprite's main texture (required for sprite animation)
            spriteMainTextureID = Shader.PropertyToID("_MainTex");

            // Pre cache the first sprite in the sprite field
            theSprite.SetTexture(spriteMainTextureID, mySpriteRenderer.sprite.texture);

            // Set starting sprite
            currentSpriteBaseTexture = mySpriteRenderer.sprite.texture;

            // Set sprite default color, this will detect which property the sprite uses for it's base color and sets starting color field accordingly
            if (spriteMaterial.HasProperty("_Color"))
            {
                spriteBaseColorID = Shader.PropertyToID("_Color");

                // Pre-cache the sprites starting color
                startingSpriteColor = spriteMaterial.GetVector(spriteBaseColorID);

                // Apply it to the block
                theSprite.SetVector(spriteBaseColorID, startingSpriteColor);
            }
        }

        // Set the property block
        if (mySpriteRenderer != null)
        {
            mySpriteRenderer.SetPropertyBlock(theSprite, spriteMaterialElement);
        }
    }


    void Update()
    {
        // Get the block at the start of each frame
        if (mySpriteRenderer != null)
        {
            // If current texture is not the same as the sprite
            if (currentSpriteBaseTexture != mySpriteRenderer.sprite.texture)
            {
                // Set current texture to match
                currentSpriteBaseTexture = mySpriteRenderer.sprite.texture;

                // Set property block to match
                theSprite.SetTexture(spriteMainTextureID, currentSpriteBaseTexture);

                // Set the block to apply the texture before getting the block again.
                mySpriteRenderer.SetPropertyBlock(theSprite, spriteMaterialElement);
            }
          
            // Get the block
            mySpriteRenderer.GetPropertyBlock(theSprite, spriteMaterialElement);
        }





        // Set the block at the end of each frame
        if (mySpriteRenderer != null)
        {
            mySpriteRenderer.SetPropertyBlock(theSprite, spriteMaterialElement);
        }
    }
}

Hopefully this helps anyone who wants to use MaterialPropertyBlocks with animating sprites!