Grab Secondary Textures From Sprite Variable

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!

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.

MaterialPropertyBlock matBlock = new MaterialPropertyBlock();
spriteRenderer.GetPropertyBlock(matBlock);
Texture2D secondaryTexture = matBlock.GetTexture("_SecondaryTex"); // whatever the secondary texture property name is

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!

    void Update()
    {
        MaterialPropertyBlock mblock = new MaterialPropertyBlock();
        spriteRenderer.GetPropertyBlock(mblock);
        Texture texture = mblock.GetTexture("_Mask");
        if (texture != null) Debug.LogWarning("SpriteRenderer:: " + texture.name);
        texture = mblock.GetTexture("_JustALongNameForTesting");
        if (texture != null) Debug.LogWarning("SpriteRenderer:: " + texture.name);
    }

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.

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.

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:

using System;
using System.Reflection;
using UnityEngine;

[ExecuteAlways]
public class SpriteSecondaryTextureReflection : MonoBehaviour
{
    [SerializeField] private SpriteRenderer _spriteRenderer;

    private void OnEnable()
    {
        var secondaryTex = _spriteRenderer.sprite.GetSecondaryTexture(0);

        if (secondaryTex != null)
        {
            Debug.Log($"Got Secondary Tex: {secondaryTex.name}");
        }
    }
}

public static class SpriteUtils
{
    private delegate Texture2D GetSecondaryTextureDelegate(Sprite sprite, int index);

    private static readonly GetSecondaryTextureDelegate GetSecondaryTextureCached =
        (GetSecondaryTextureDelegate)Delegate.CreateDelegate(
            typeof(GetSecondaryTextureDelegate),
            typeof(Sprite).GetMethod("GetSecondaryTexture", BindingFlags.NonPublic | BindingFlags.Instance) ??
            throw new Exception("Unity has changed/removed the internal method Sprite.GetSecondaryTexture"));

    public static Texture GetSecondaryTexture(this Sprite sprite, int index) => GetSecondaryTextureCached(sprite, index);
}

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.

5 Likes

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://discussions.unity.com/t/784754

i guess unity updated their api so now you can use

SpriteRenderer sr = GetComponent();
SecondarySpriteTexture[ ] texes = new SecondarySpriteTexture[sr.sprite.GetSecondaryTextureCount()];

int count = sr.sprite.GetSecondaryTextures(texes);

Then you can loop through the texes array, and just use which ever texture you want/have.
you can do

texes[0].name to find the specific key/name of the texture which you assigned in the sprite editor window

and you can use

texes[0].texture

to actually grab the texture.

this is as of 2022.3.11f1
no ide about older versions than that i have not tested it.