Search Unity

Feedback Checking if Animator parameter exists should be easier / more efficient

Discussion in 'Editor & General Support' started by lucvdp-com, Jan 7, 2020.

  1. lucvdp-com

    lucvdp-com

    Joined:
    Mar 11, 2015
    Posts:
    11
    Referencing a non-existent parameter on an Animator gives a warning message, "Parameter 'Whatever' does not exist." However, there is no efficient way to check in advance whether a parameter exists or not.

    There are times where you'll need to reference non-existent Animator parameters. For instance, in my project, I have C# scripts that I re-use on multiple GameObjects that try to access certain Animator parameters, but will succeed or fail depending on what GameObjects they're attached to, because not all my GameObjects use the same Animator Controller. This would be perfectly fine behaviour if it wasn't for the countless warning messages clogging up my debug log.

    Currently, the only way I can find to prevent these warnings is by looping through the Animator.parameters array and comparing the name of each parameter to the name I'm trying to access. (And only run the code if a parameter of that name is found). So something like:

    Code (CSharp):
    1. bool containsName = false;
    2. string nameToCheck = "Whatever"
    3.  
    4. for (int i = 0; i < myAtor.parameters; i++) {
    5.  
    6.    if (myAtor.parameters[i].name == nameToCheck) {
    7.  
    8.       containsName = true;
    9.       break;
    10.  
    11.    }
    12.  
    13. }
    14.  
    15. if (containsName) {
    16.  
    17.    myAtor.SetFloat(nameToCheck, 5.0f);  //This line will now only run if the parameter is valid, thus avoiding warning messages
    18.  
    19. }
    The problem is that I'm now doing a string comparison for every parameter on the Animator, every single time I want to check if a parameter exists. This is very inefficient, isn't it? (If someone knows a better method, please tell me, but I don't think there is one.)

    I would suggest:
    A) Removing the warning message. (However, there will be instances where you would want the warning to pop up, so this wouldn't be an ideal solution.)
    B) Adding an option to turn off this warning.
    C) Adding an easier/efficient way to check if an Animator parameter is valid (probably the best solution, I'd say)

    Let me know what you think or whether I'm missing something here. Thanks!
     
  2. Rotary-Heart

    Rotary-Heart

    Joined:
    Dec 18, 2012
    Posts:
    813
    You should probably be using another approach instead of using the same script that is trying to use those non existent parameters. But, you could store the parameters on a HashSet on Start/Awake and your check for key would be faster.
     
  3. lucvdp-com

    lucvdp-com

    Joined:
    Mar 11, 2015
    Posts:
    11
    Well, my project uses a lot of inheritance. Not using the same script (or an inherited class) is exactly what I would like to avoid, because it would mean I'd have to copy a lot of stuff to multiple scripts. Or use the same script but with a lot of if statements.

    Hmmm, comparing hashsets (integers) is indeed a lot faster, though it would mean that I'd need to store a bunch of stuff. Something like this?:

    Code (CSharp):
    1. Animator myAtor;
    2. int parameterOne = Animator.StringToHash("One");
    3. int parameterTwo = Animator.StringToHash("Two");
    4. int parameterThree = Animator.StringToHash("Three");
    5.  
    6. List<int> validParameters = new List<int>();
    7.  
    8. Start() {
    9.  
    10.    myAtor = GetComponent<Animator>();
    11.  
    12.    for(int i =0; i < myAtor.parameters.Length; i++) {
    13.  
    14.       validParameters.Add(myAtor.parameters[i].nameHash)
    15.  
    16.    }  
    17.  
    18. }
    19.  
    20. void DoAnimatorStuff() {
    21.  
    22.    if (validParameters.Contains(parameterOne)) { //Does int comparison instead of string
    23.  
    24.       myAtor.SetFloat(parameterOne, 5.0f); //Will only run if parameter exists
    25.  
    26.    }
    27.  
    28. }
    I still feel like storing all valid parameters is a bit of a hassle and that there should just be a way to allow using something like Animator.SetFloat() with an invalid parameter without triggering a warning, but I guess this will do. Thanks for the help!
     
  4. Rotary-Heart

    Rotary-Heart

    Joined:
    Dec 18, 2012
    Posts:
    813
    Yeah, the system should have a way to check if it contains the parameter. I agree with that. As for your code I meant to use an actual HashSet instead of a list.
    HashSet<int> validParameters = new HashSet<int>();
    That way your check for key will be faster than with a list.
     
  5. lucvdp-com

    lucvdp-com

    Joined:
    Mar 11, 2015
    Posts:
    11
    Oh, I didn't know HashSet was a thing, (thought you were referring to StringToHash() ), I'll look into that!
     
  6. hasanbayat

    hasanbayat

    Joined:
    Oct 18, 2016
    Posts:
    630
    I have shared this on both Unity discussions and Gamedev exchange, but here it is for users coming through forums.

    This is a simple animator utility class that allows you to check if the animator has a parameter or not, or safely set parameters without any warnings, it is quite fast as it uses a HashSet and Dictionary for caching the parameters and name hashes.

    Download
    1. Get the script from this gist
    2. Import the script into your Unity project
    Features
    • Get or Set animator parameter safely (checks whether the animator parameter exists before setting or getting it)
    • Provide a default value for Get methods in case animator doesn't have them
    • Check whether the animator has a parameter
    • Get animator parameters without multiple GC allocs
    • Uses Parameter's name hash instead of the name to improve performance (the name hashes are cached too)
    • Caches the animator and their parameters using a Dictionary and HashSet for optimal performance in cases of calling in Update or LateUpdate
    • Release unused caches and animators by calling AddAnimatorUsage when you first initialize and use the animator, then call RemoveAnimatorUsage when you no longer use the animator
    • You can explicitly call ClearCaches to remove all caches
    • Includes code comments for all methods
    Example
    Code (CSharp):
    1. using Bayat.Games.Animation.Utilities;
    2.  
    3. public class GameCharacter : MonoBehaviour {
    4.  
    5.   [Header("Animation")]
    6.   [SerializeField]
    7.   protected Animator animator;
    8.   [SerializeField]
    9.   protected string speedParameter = "Speed";
    10.  
    11.   [Header("Physics")]
    12.   [SerializeField]
    13.   protected new Rigidbody2D rigidbody2D;
    14.  
    15.   protected void OnEnable() {
    16.  
    17.     // Allocate caches and resources for this animator (only the first usage allocates the caches, the rest use the same caches for this animator)
    18.     animator.AddAnimatorUsage();
    19.   }
    20.  
    21.   protected void OnDisable() {
    22.  
    23.     // Release caches and unused resources
    24.     animator.RemoveAnimatorUsage();
    25.   }
    26.  
    27.   proteved void LateUpdate() {
    28.  
    29.     float speed = Mathf.Abs(this.rigidbody2D.velocity.magnitude);
    30.  
    31.     // Set a parameter safely (it won't log warnings)
    32.     animator.SetFloatSafe(this.speedParameter, speed);
    33.  
    34.     // Or manually check if the parameter exists
    35.     if (animator.HasParameter(this.speedParameter)) {
    36.       animator.SetFloat(this.speedParameter, speed);
    37.     }
    38.  
    39.     // Get a parameter's value safely with a default value
    40.     // The "Height" parameter here is missing in the animator and the default height value is returned instead
    41.     float defaultHeight = 10f;
    42.     float height = animator.GetFloatSafe("Height", defaultHeight);
    43.   }
    44.  
    45. }
    API
    • AddAnimatorUsage: Adds an animator usage and allocates caches and resources for it
    • RemoveAnimatorUsage: Removes an animator usage and releases caches and unused resources
    • GetParameterNameHash: Gets the parameter's name hash. It's cached for consecutive calls.
    • GetParameters: Gets the animator parameters as a HashSet of their name hashes.
    • ClearCaches: Clears all caches
    • HasParameter: Checks whether the animator has a parameter
    • ResetTriggerSafe: Resets the value of the given trigger parameter safely.
    • SetTriggerSafe: Sets the value of the given trigger parameter safely.
    • GetBoolSafe: Gets the value of the given boolean parameter safely.
    • SetBoolSafe: Sets the value of the given boolean parameter safely.
    • GetIntegerSafe: Gets the value of the given integer parameter safely.
    • SetIntegerSafe: Sets the value of the given integer parameter safely.
    • GetFloatSafe: Gets the value of the given float parameter safely.
    • SetFloatSafe: Sets the value of the given float parameter safely.
    License
    MIT License
     
  7. Johnney1337

    Johnney1337

    Joined:
    Oct 10, 2018
    Posts:
    3
    @hasanbayat
    How can I import this script correctly? My other class throw a error because it can not find the right namespace of this script :(