2

When creating a Shader in Unity, you can add a Property to it that can be called from inside of a different script. To do this, one could use the following line to call on and set a Vector3 Property names _vector3:

materialName.SetVector("_vector3", new Vector3(1, 1, 1));

This is great and all, but is there really not a way to avoid using 'magic' Strings? Seems like an awfully messy and error-prone way to do this. I get that maybe you could define it at the top so it is less of a 'magic' String, but it is still a string var that will need to be carried through from the Shader to the script when making any changes.

So pretty basic question here: Is there a way to avoid using 'magic' Strings when calling on variables in Unity?

Peter O.
  • 32,158
  • 14
  • 82
  • 96
Jee
  • 969
  • 1
  • 7
  • 26
  • 1
    It appears there's an overload that takes an ID instead: https://docs.unity3d.com/ScriptReference/Material.SetVector.html You still need to pass in a string to `Shader.PropertyToID` to resolve the name to id, but only once, and it appears this is the preferred method, per documentation. The ID can be saved to a variable, such as `vector3_id`, etc. – CoolBots Nov 09 '20 at 03:58
  • @CoolBots Pretty sure this is just for optimization purposes. According to everything I found, you can use that at the top of your script to avoid the conversion happening multiple times. You still need to provide the string name for the initial conversion though. So it doesn't really help solve my complaint about the strings, or am I missing something? – Jee Nov 09 '20 at 04:10
  • 1
    @Jee nope it is for optimization purpsoes u are right ^^ – Menyus Nov 09 '20 at 11:27
  • 1
    There is no way to avoid these magic strings, however you can define them as const so they wont be magic ^^ – Menyus Nov 09 '20 at 11:30

3 Answers3

3

Sadly there is no way to avoid this. There is an elegant solution tho with IDs in mind for performance

public static class MyShader
{
    public static readonly int _MyPropertyName = 
        Shader.PropertyToID(nameof(_MyPropertyName));

}

Usage

void myShaderMethod()
{
    materialName.SetVector(MyShader._MyPropertyName, new Vector3(1, 1, 1));
}
Menyus
  • 6,633
  • 4
  • 16
  • 36
2

No, there is no way of getting rid of the string identifiers. Of course you can add some helper class containing your shader property identifiers in one place increasing maintainability.

public static class ShaderProps
{
    public static class MyShaderA
    {
        public const string SomeProperty = "_vector3";
    }
}

// Usage
public class MyBehaviour : MonoBehaviour
{
    private void Update 
    {
        material.SetVector(ShaderProps.MyShaderA.SomeProperty, ...);
    }
}

You should use Shader.PropertyToID(string name) and cache the result though, since it's more efficient. Unity calls it under the hood every time you use a string identifier making repeated calls with strings (e.g. in Update) unfavorable.

Thomas
  • 1,225
  • 7
  • 16
  • 1
    I tend to use ```enum``` instead of strings, but same same. – Immersive Nov 10 '20 at 01:22
  • @Immersive Is there a reason you would suggest enums instead of strings? – Jee Nov 11 '20 at 07:38
  • @Jee Nothing concrete. I would be caching the ID in a dictionary and prefer enum (int) over string for keys if I can help it. It's mostly a nod to efficiency, comparing an int over a string. There's an argument for using const strings for maintainability, though. – Immersive Nov 11 '20 at 15:06
1

You can simply use Shader.PropertyToID:

private static readonly int shaderProperty;

void Start(){
    shaderProperty = Shader.PropertyToID("propertyName");
}

And then

materialName.SetVector(shaderProperty, new Vector3(1, 1, 1));

This will be way more efficient.

  • More efficient, true, but does it solve the magic strings issue? – Jee Nov 11 '20 at 07:39
  • Well, in the shader parameters you insert a string as name. This will be hashed to an integer value and stored. Shader.PropertyToID does the same, takes a string and hash it to an integer value. And I suppose there is no "magic" things behind it, because string is **required**, not optional. So, no. You can't avoid inserting the string. How would you match the parameter name otherwise? – Domenico Di Stefano Nov 12 '20 at 09:09