Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Grab Secondary Textures From Sprite Variable

Discussion in 'General Graphics' started by fmdpcr, Aug 14, 2020.

  1. fmdpcr

    fmdpcr

    Joined:
    Apr 29, 2020
    Posts:
    4
    Hi Forum

    I am currently working on a 3D game that uses sprites for rendering enemies and one of the features we would like to implement is some way of detecting a particular limb (head, body, etc...) when a Ray-cast hits a sprite pixel. The current solution we have is a MonoBehaviour script that:

    - Grabs the default texture from the SpriteRenderer
    - Converts a RaycastHit to a pixel coordinate on the texture
    - Compares the alpha channel on the pixel (alpha != 0.0 then a collision has occured)

    One of the new features that unity introduced recently on the sprite editor is secondary textures, and our artists have been using this to make particular effects by referencing those in their shaders. This is quite handy since we are constantly changing sprites during runtime. We have created another secondary texture with different values on each channel to detect if the pixel is a head or body part, but the issue is:

    - How can I grab a specific secondary texture from the SpriteRenderer in a MonoBehaviour C# Script?

    From my understanding, the SpriteRenderer is somehow grabbing those secondary textures from a Sprite variable and passing them on to the shader. I tried grabbing it from the material but it does not seem to work.

    Thank you in advance!
     
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,238
    Annoyingly it looks like they didn't think to include any way for c# to access the secondary textures of a sprite asset. You might be able to get the texture asset from the material property block on the sprite renderer component.
    Code (csharp):
    1. MaterialPropertyBlock matBlock = new MaterialPropertyBlock();
    2. spriteRenderer.GetPropertyBlock(matBlock);
    3. Texture2D secondaryTexture = matBlock.GetTexture("_SecondaryTex"); // whatever the secondary texture property name is
     
  3. fmdpcr

    fmdpcr

    Joined:
    Apr 29, 2020
    Posts:
    4
    Hi bgolus

    Thank you for you quick reply!

    I have tried your solution and added it to my get pixel function but it kept saying the texture was null.
    I though it had something to do with not storing the texture on a MonoBehaviour function so I tried Update

    Unfortunately it is not printing or gives me a null reference exception.
    I tried as well creating another secondary texture but no luck.

    I did notice that the meta tag of the main sprite has the 2 secondary textures referenced in it. Would there be a way to find the texture in resources since all of them have "_Mask" on theirs names? It would be fantastic if I did not have to load textures everytime a projectile is shot on the scene.

    Thank you once again!

    Code (CSharp):
    1.     void Update()
    2.     {
    3.         MaterialPropertyBlock mblock = new MaterialPropertyBlock();
    4.         spriteRenderer.GetPropertyBlock(mblock);
    5.         Texture texture = mblock.GetTexture("_Mask");
    6.         if (texture != null) Debug.LogWarning("SpriteRenderer:: " + texture.name);
    7.         texture = mblock.GetTexture("_JustALongNameForTesting");
    8.         if (texture != null) Debug.LogWarning("SpriteRenderer:: " + texture.name);
    9.     }
    upload_2020-8-18_9-40-14.png

    upload_2020-8-18_9-41-43.png
     
  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,238
    That's unfortunate. I really have no idea how to get access to those textures then. The data seems to only exist in the asset's serialized data and isn't ever exposed to the c#. The property block was a shot in the dark. I believe you can get the main texture that way, which is why I was hoping the secondary textures would be a accessible too. Unfortunately the answer may be you have to create components to directly reference the secondary textures from script manually. I know there are tricks in the editor to crawl the serialized data to get information not officially exposed to the c# interfaces, but it's not something I'm terribly well versed in, and AFAIK they only work in the editor so it's only useful as a way to not have to manually assign values in a custom component. The scripting subforum may be able to help you there.

    You might also try reporting it as a bug.
     
  5. fmdpcr

    fmdpcr

    Joined:
    Apr 29, 2020
    Posts:
    4
    Ok, thank you for your help bgolus!

    Yes you can get the main texture, just not sure how the sprite renderer knows how to search for the secondary textures since we are changing the sprites at different angles.

    I currently am just storing the mask textures in an array and using a dictionary to map the main texture name to an index.

    Possibly I will come back to this thread since It would be nice to use a import script or custom inspector editor that can search the assets by reading some data from the meta files.
     
  6. MakinStuffLookGood

    MakinStuffLookGood

    Joined:
    Nov 20, 2012
    Posts:
    19
    As of Unity 2020.3, accessing Secondary Textures on a Sprite in C# isn't exposed, but doable via reflection. Here's an example with a cached strongly typed delegate and extension method which hopefully creates the same experience/performance as if the method were just public:

    Code (CSharp):
    1. using System;
    2. using System.Reflection;
    3. using UnityEngine;
    4.  
    5. [ExecuteAlways]
    6. public class SpriteSecondaryTextureReflection : MonoBehaviour
    7. {
    8.     [SerializeField] private SpriteRenderer _spriteRenderer;
    9.  
    10.     private void OnEnable()
    11.     {
    12.         var secondaryTex = _spriteRenderer.sprite.GetSecondaryTexture(0);
    13.  
    14.         if (secondaryTex != null)
    15.         {
    16.             Debug.Log($"Got Secondary Tex: {secondaryTex.name}");
    17.         }
    18.     }
    19. }
    20.  
    21. public static class SpriteUtils
    22. {
    23.     private delegate Texture2D GetSecondaryTextureDelegate(Sprite sprite, int index);
    24.  
    25.     private static readonly GetSecondaryTextureDelegate GetSecondaryTextureCached =
    26.         (GetSecondaryTextureDelegate)Delegate.CreateDelegate(
    27.             typeof(GetSecondaryTextureDelegate),
    28.             typeof(Sprite).GetMethod("GetSecondaryTexture", BindingFlags.NonPublic | BindingFlags.Instance) ??
    29.             throw new Exception("Unity has changed/removed the internal method Sprite.GetSecondaryTexture"));
    30.  
    31.     public static Texture GetSecondaryTexture(this Sprite sprite, int index) => GetSecondaryTextureCached(sprite, index);
    32. }
    33.  
    Notably, the method doesn't seem to throw an array out of bound exception if you request an index that isn't defined in the import settings. Instead, the method will just return null.
     
  7. theforgot3n1

    theforgot3n1

    Joined:
    Sep 26, 2018
    Posts:
    188
    This really got my hopes up that there might also be a method for setting the secondary texture.

    I am writing a script to add normal maps to all my textures. But I can't set the secondary textures., so it looks like I am forced to do that step manually. (I have easily over 1000 textures Q_Q)

    EDIT: Looks like it is possible, but in the editor. This thread saved me. https://forum.unity.com/threads/set-secondary-textures-to-sprites.864184/
     
    Last edited: Oct 31, 2021