Search Unity

Question Sprites and MaterialPropertyBlocks

Discussion in 'Scripting' started by DanielRiches, Oct 5, 2020.

  1. DanielRiches

    DanielRiches

    Joined:
    Nov 23, 2019
    Posts:
    166
    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:

    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class TestScript : MonoBehaviour
    4. {
    5.     public SpriteRenderer mySpriteRenderer;
    6.     private MaterialPropertyBlock theSprite;
    7.  
    8.     private Material spriteMaterial;
    9.  
    10.     public int spriteBaseColorID;
    11.  
    12.     public int spriteMaterialElement;
    13.  
    14.     [SerializeField, ColorUsage(true, true)] private Color startingSpriteColor;
    15.  
    16.     void Start()
    17.     {
    18.         if (!TryGetComponent(out mySpriteRenderer)){}
    19.  
    20.         if (mySpriteRenderer != null)
    21.         {
    22.             // Set up property block for sprite material.
    23.             theSprite = new MaterialPropertyBlock();
    24.  
    25.             // If you accidentally choose a non-existant element.
    26.             if (spriteMaterialElement > mySpriteRenderer.materials.Length - 1)
    27.             {
    28.                 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");
    29.                 spriteMaterialElement = 0;
    30.             }
    31.  
    32.             // Cache mesh material to copy default values.
    33.             spriteMaterial = mySpriteRenderer.materials[spriteMaterialElement];
    34.  
    35.             // Get chosen element properties from the Sprite Renderer and assign it to theSprite MaterialPropertyBlock.
    36.             mySpriteRenderer.GetPropertyBlock(theSprite, spriteMaterialElement);
    37.  
    38.             // Set sprite default color, this will detect which property the sprite uses for it's base color and sets starting color field accordingly
    39.             if (spriteMaterial.HasProperty("_BaseColor"))
    40.             {
    41.                 spriteBaseColorID = Shader.PropertyToID("_BaseColor");
    42.  
    43.                 // Pre-cache the sprites starting color
    44.                 startingSpriteColor = spriteMaterial.GetVector(spriteBaseColorID);
    45.  
    46.                 // Apply it to the block
    47.                 theSprite.SetVector(spriteBaseColorID, startingSpriteColor);
    48.             }
    49.         }
    50.  
    51.         // Set the property block
    52.         if (mySpriteRenderer != null)
    53.         {
    54.             mySpriteRenderer.SetPropertyBlock(theSprite, spriteMaterialElement);
    55.         }
    56.     }
    57.  
    58.  
    59.     void Update()
    60.     {
    61.         // Get the block at the start of each frame
    62.         if (mySpriteRenderer != null)
    63.         {
    64.             mySpriteRenderer.GetPropertyBlock(theSprite, spriteMaterialElement);
    65.         }
    66.  
    67.  
    68.  
    69.         // Set the block at the end of each frame
    70.         if (mySpriteRenderer != null)
    71.         {
    72.             mySpriteRenderer.SetPropertyBlock(theSprite, spriteMaterialElement);
    73.         }
    74.     }
    75. }
    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:






    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.
     
    Last edited: Oct 6, 2020
  2. DanielRiches

    DanielRiches

    Joined:
    Nov 23, 2019
    Posts:
    166
    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:


    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class TestScript : MonoBehaviour
    4. {
    5.     public SpriteRenderer mySpriteRenderer;
    6.     private MaterialPropertyBlock theSprite;
    7.  
    8.     public Material spriteMaterial;
    9.     public Texture currentSpriteBaseTexture;
    10.  
    11.     public int spriteBaseColorID;
    12.     public int spriteMainTextureID;
    13.  
    14.     public int spriteMaterialElement;
    15.  
    16.     [SerializeField, ColorUsage(true, true)] private Color startingSpriteColor;
    17.  
    18.     void Start()
    19.     {
    20.         if (!TryGetComponent(out mySpriteRenderer)){}
    21.  
    22.         if (mySpriteRenderer != null)
    23.         {
    24.             // Set up property block for sprite material.
    25.             theSprite = new MaterialPropertyBlock();
    26.  
    27.             // If you accidentally choose a non-existant element.
    28.             if (spriteMaterialElement > mySpriteRenderer.materials.Length - 1)
    29.             {
    30.                 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");
    31.                 spriteMaterialElement = 0;
    32.             }
    33.  
    34.             // Cache mesh material to copy default values.
    35.             spriteMaterial = mySpriteRenderer.materials[spriteMaterialElement];
    36.  
    37.             // Get chosen element properties from the Sprite Renderer and assign it to theSprite MaterialPropertyBlock.
    38.             mySpriteRenderer.GetPropertyBlock(theSprite, spriteMaterialElement);
    39.  
    40.             // Set an ID for Sprite's main texture (required for sprite animation)
    41.             spriteMainTextureID = Shader.PropertyToID("_MainTex");
    42.  
    43.             // Pre cache the first sprite in the sprite field
    44.             theSprite.SetTexture(spriteMainTextureID, mySpriteRenderer.sprite.texture);
    45.  
    46.             // Set starting sprite
    47.             currentSpriteBaseTexture = mySpriteRenderer.sprite.texture;
    48.  
    49.             // Set sprite default color, this will detect which property the sprite uses for it's base color and sets starting color field accordingly
    50.             if (spriteMaterial.HasProperty("_Color"))
    51.             {
    52.                 spriteBaseColorID = Shader.PropertyToID("_Color");
    53.  
    54.                 // Pre-cache the sprites starting color
    55.                 startingSpriteColor = spriteMaterial.GetVector(spriteBaseColorID);
    56.  
    57.                 // Apply it to the block
    58.                 theSprite.SetVector(spriteBaseColorID, startingSpriteColor);
    59.             }
    60.         }
    61.  
    62.         // Set the property block
    63.         if (mySpriteRenderer != null)
    64.         {
    65.             mySpriteRenderer.SetPropertyBlock(theSprite, spriteMaterialElement);
    66.         }
    67.     }
    68.  
    69.  
    70.     void Update()
    71.     {
    72.         // Get the block at the start of each frame
    73.         if (mySpriteRenderer != null)
    74.         {
    75.             // If current texture is not the same as the sprite
    76.             if (currentSpriteBaseTexture != mySpriteRenderer.sprite.texture)
    77.             {
    78.                 // Set current texture to match
    79.                 currentSpriteBaseTexture = mySpriteRenderer.sprite.texture;
    80.  
    81.                 // Set property block to match
    82.                 theSprite.SetTexture(spriteMainTextureID, currentSpriteBaseTexture);
    83.  
    84.                 // Set the block to apply the texture before getting the block again.
    85.                 mySpriteRenderer.SetPropertyBlock(theSprite, spriteMaterialElement);
    86.             }
    87.          
    88.             // Get the block
    89.             mySpriteRenderer.GetPropertyBlock(theSprite, spriteMaterialElement);
    90.         }
    91.  
    92.  
    93.  
    94.  
    95.  
    96.         // Set the block at the end of each frame
    97.         if (mySpriteRenderer != null)
    98.         {
    99.             mySpriteRenderer.SetPropertyBlock(theSprite, spriteMaterialElement);
    100.         }
    101.     }
    102. }
    Hopefully this helps anyone who wants to use MaterialPropertyBlocks with animating sprites!
     
    Last edited: Oct 6, 2020