Is there a way to check if an animatorcontroller has a parameter?
Asked Answered
F

12

0

With mecanim, if you try to set a parameter that doesn’t exist, it logs a warning. It doesn’t throw an exception or return null or anything useful like that. So if I set a parameter very often that doesn’t exist, a lot of CPU time is spent logging warnings. How can I tell if a parameter exists in an animatorcontroller? It would be great if there was a method like ProceduralMaterial.HasProceduralProperty (which allows you to check if the ProceduralMaterial has the property in question).

(crossposted from gamedev.stackexchange)

Farsighted answered 17/11, 2023 at 9:48 Comment(0)
K
0

Unfortunately, there doesn’t seem to be anything in place to check if a parameter exists. If you just want to turn off warnings to save CPU, set Animator.logWarnings = false (not in the scripting reference for some reason).

If you want something that will do error handling, I wrote a hacky way to do it:

void Awake()
{
	animator.SetBool("nonExistant",true);
	if(!animator.GetBool("nonExistant")){
		Debug.LogError("The parameter nonExistant does not exist! Fix it!");
	}else{
		animator.SetBool("nonExistant",false);
	}
}

Hope this helps!

Kanzu answered 17/11, 2023 at 10:39 Comment(1)

Why is this marked as a solution? There are other, much upvoted answers that actually answer thr questuon, instead of saying "no". I understand that disabling the warninga may be a solution to OP's problem, but it's NOT an answer, and others may be looking for the same question with different problems in mind

Pulverable
A
0

I know its quite an old topic, but maybe somebody is still looking for any other options of how to deal with it. Here is my solution:

public static bool HasParameter(string paramName, Animator animator)
{
   foreach (AnimatorControllerParameter param in animator.parameters)
   {
      if (param.name == paramName)
         return true;
   }
   return false;
}
Ambler answered 17/11, 2023 at 10:38 Comment(1)

it works! but produces garbage if many parameters are present in the animator controller

Dallis
T
0

A slightly shorter answer is:

public static bool HasParameter(string paramName, Animator animator)
{
  foreach (AnimatorControllerParameter param in animator.parameters)
  {
     if (param.name == paramName) return true;
  }
  return false;
}
Tellurian answered 17/11, 2023 at 9:46 Comment(1)

thanks, you're right, I've updated my answer. Sometimes I just should really fall asleep rather than code anything :D

Ambler
S
0

I found the code of @Tellurian and @Ambler realy useful. So I made an extension real quick should work. But such calls should not be done in an Update! Store the outcome during a Start() in a variable and use this variable!!!

Just add it into your projekt I use a lot of extensions.

public static bool ContainsParam(this Animator _Anim, string _ParamName)
{
    foreach (AnimatorControllerParameter param in _Anim.parameters)
    {
        if (param.name == _ParamName) return true;
    }
    return false;
}

Use like this:

void start() {
tOutcome =  myAnimator.ContainsParam("JumpCount");
}

And then you can ask for:

if( tOutcome ) {
myAnimator.SetInteger("JumpCount", JumpCount);
}
Section answered 6/6, 2023 at 2:15 Comment(2)

How do you use that extension? When I enter public static bool ContainsParam(this Animator _Anim, string _ParamName) { foreach (AnimatorControllerParameter param in _Anim.parameters) { if (param.name == _ParamName) return true; } return false; } I get this error: Extension method must be defined in a non-generic static class. Do you have a separate script that defines a public static bool ContainsParam? [137960-untitled.png|137960]

Stonewall

@Stonewall Yes, create some static class like: public static class ExtensionMethods { } and put this extension method in it: After that each Animator instance will be able to use it by simply calling animatorInstance.ContainsParam("ParamName"); That's the way how extension methods work.

Magenmagena
B
0

Very useful post, I’ve stumbled upon it 2nd time this last 6 months! Both times i used the code for assertions, and here’s my version of It.

I’ve used Linq to shorten code even more, and minimized the code nessesary for an assert to AssertAnimator.HasParameter(_runningParamId, anim);

It is also possible to work with id’s instead of strings.

using System.Linq;
using UnityEngine;
using UnityEngine.Assertions;

public static class AssertAnimator
{
    public static void HasParameter(string paramName, Animator animator)
    {
        Assert.IsTrue(HasParameterHelper(paramName, animator), 
            $"The animator on a Game Object \"{animator.gameObject.name}\" does not have the parameter {paramName}");
    }

    public static void HasParameter(int paramId, Animator animator)
    {
        Assert.IsTrue(HasParameterHelper(paramId, animator), 
            $"The animator on a Game Object \"{animator.gameObject.name}\" does not have the parameter with id {paramId}");
    }

    // pretty slow and should not be used in production code
    static bool HasParameterHelper(int paramHash, Animator animator)
    {
        return animator.parameters.Any(param => param.nameHash == paramHash);
    }

    // pretty slow and should not be used in production code
    static bool HasParameterHelper(string paramName, Animator animator)
    {
        return animator.parameters.Any(param => param.name == paramName);
    }
}
Blooming answered 26/5, 2018 at 16:45 Comment(1)

Your code is not garbage-friendly.

Supertax
H
0

I would recommend to speed up this check with a Dictionary:

Dictionary<string, bool> animNames = new Dictionary<string, bool>();
  
void Awake() {
     animNames.Add("Attack", false);
     animNames.Add("Run", false);
     animNames.Add("Walk", false);
  
     foreach (AnimatorControllerParameter param in MyAnimator.parameters) {
         if (animNames.containsKey(param.Name)) {
               animNames[param.Name] = true;         
         }
     }
  
    // To check then the known param.
    if (animNames["Attack"] == true) {
         // Attack param available, use it.
    }
}
Helix answered 17/11, 2023 at 9:45 Comment(0)
P
0

(Contains code snippet from other answer. Credit to their respective author)

Simpler version

Involve GC alloc on Animator.parameters, not cool for repeated call.

public static bool HasParameter( string paramName, Animator animator )
{
    foreach( AnimatorControllerParameter param in animator.parameters )
    {
        if( param.name == paramName )
            return true;
    }
    return false;
}
// Usage sample
public void SetFloatParamWithCheck( string paramName, float v )
{
    // animator defined elsewhere.
    Animator animator;     

    if( HasParameter( animator, paramName ) )
    {
        animator.SetFloat( paramName , v );
    }
}

Extension version

still involve GC alloc every call.

public static class AnimatorHelper
{
    public static bool ContainsParam( this Animator _Anim, string _ParamName )
    {
        foreach( AnimatorControllerParameter param in _Anim.parameters )
        {
            if( param.name == _ParamName ) return true;
        }
        return false;
    }
}
// Usage sample
public void SetFloatParamWithCheck( string paramName, float v )
{
    // animator defined elsewhere.
    Animator animator;     

    if( animator.ContainsParam( paramName ) )
    {
        animator.SetFloat( paramName , v );
    }
}

An over-engineered version

With caching and resolving parameter hash in one go. Premature optimization is evil but you will need it in (some) practice.

// Animator parameters query involve GC, cache it
// Animator.GetParameter( index ) might also internally use it, so no better.

// Assume typically working with 1 animator, could extending to collect more cache.
Animator m_LastAnimatorCache; 
// <key=paramname,value=hash>
Dictionary<string,int> m_AnimatorParamCache = new Dictionary<string,int>( ); 

private bool TryGetAnimatorParam( Animator animator, string paramName, out int hash )
{
    // Rebuild cache
    if( (m_LastAnimatorCache == null || m_LastAnimatorCache != animator) && animator != null ) 
    {
        m_LastAnimatorCache = animator;
        m_AnimatorParamCache.Clear( );
        foreach( AnimatorControllerParameter param in animator.parameters )
        {
            int paramHash = Animator.StringToHash( param.name ); // could use param.nameHash property but this is clearer
            m_AnimatorParamCache.Add( param.name, paramHash );
        }
    }

    if( m_AnimatorParamCache != null && m_AnimatorParamCache.TryGetValue( paramName, out hash ) )
    {
        return true;
    }
    else
    {
        hash = 0;
        return false;
    }
}
// Usage sample
public void SetFloatParamWithCheck( string paramName, float v )
{
    // animator defined elsewhere.
    Animator animator;
    int hash;

    if( TryGetAnimatorParam( animator, paramName, out hash ) )
    {
        animator.SetFloat( hash, v );
    }
}
Punctilio answered 17/11, 2023 at 9:45 Comment(0)
V
0

Extension way of doing it:

namespace Util
{
    public static class AnimatorExtensions
    {
        /// <summary>
        /// Caches previous queries.
        /// </summary>
        private static readonly Dictionary<Animator, Dictionary<string, bool>> AnimatorParameterCache = 
            new Dictionary<Animator, Dictionary<string, bool>>();
         
        /// <summary>
        /// Returns true if an animator has a parameter.
        /// </summary>
        /// <param name="animator"></param>
        /// <param name="paramName"></param>
        /// <returns></returns>
        public static bool HasParameter(this Animator animator, string paramName)
        {
            //Check, cache and return
            if (AnimatorParameterCache.ContainsKey(animator) && AnimatorParameterCache[animator].ContainsKey(paramName))
                return AnimatorParameterCache[animator][paramName];
            
            //Create the dictionary if we need to
            if (AnimatorParameterCache[animator] == null)
            {
                AnimatorParameterCache[animator] = new Dictionary<string, bool>();
            }
                
            AnimatorParameterCache[animator][paramName] = 
                animator.parameters.Any(param => param.name == paramName);

            return AnimatorParameterCache[animator][paramName];
        }
    }
}
Vestpocket answered 15/12, 2019 at 20:23 Comment(0)
C
0

Found the following very short solution using System.Array working for me:

public static bool HasParameter(Animator animator, string paramName)
{
    return System.Array.Exists(animator.parameters, p => p.name == paramName);
}
Conviction answered 17/11, 2023 at 9:47 Comment(0)
S
0

This is what I use, as it’s two lines.
But remember, like previous posts have stated, it’s not the most efficient way:

foreach (var param in animator.parameters) if (param.name == SPEED_MULT)
     animator.SetFloat(SPEED_MULT, Random.Range(0.9f, 1.1f));

Not sure of the GC hit, but my code only runs this as the start of a scene to sort of desynchronize animations.

Stolon answered 17/11, 2023 at 9:47 Comment(1)

Great tip! This worked for me! Simple and efficient. Like you, it's best run at Start(). I went from 300ms lag spikes to almost nothing! This is how I got it working. Animator _anim; bool _triggerExists ; void CheckAnimatorParameters() { foreach (var _param in _anim.parameters) { if (_param.name == "TriggerAttack") { _triggerExists = true; } } } Then you can do what you wish with that bool's value.

Postlude
T
0

An even shorter solution would be using Linq as such-

if (anim.parameters.Any(x => x.name == "Die")) anim.ResetTrigger(Die);

dont forget to use Linq and specify your animator component.

using System.Linq;
public Animator anim;
Thirza answered 6/6, 2022 at 18:36 Comment(0)
N
0

I have posted this on the Gamedev exchange too, but in case of future users coming across this.

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 the animator parameter safely (checks whether the animator parameter exists before setting or getting it)
  • Provide a default value for Get methods in case the 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

Usage

using Bayat.Games.Animation.Utilities;

public class GameCharacter : MonoBehaviour {

  [Header("Animation")]
  [SerializeField]
  protected Animator animator;
  [SerializeField]
  protected string speedParameter = "Speed";
  
  [Header("Physics")]
  [SerializeField]
  protected new Rigidbody2D rigidbody2D;

  protected void OnEnable() {
  
    // Allocate caches and resources for this animator (only the first usage allocates the caches, the rest use the same caches for this animator)
    animator.AddAnimatorUsage();
  }
  
  protected void OnDisable() {
  
    // Release caches and unused resources
    animator.RemoveAnimatorUsage();
  }
  
  proteved void LateUpdate() {
  
    float speed = Mathf.Abs(this.rigidbody2D.velocity.magnitude);
  
    // Set a parameter safely (it won't log warnings)
    animator.SetFloatSafe(this.speedParameter, speed);
    
    // Or manually check if the parameter exists
    if (animator.HasParameter(this.speedParameter)) {
      animator.SetFloat(this.speedParameter, speed);
    }
    
    // Get a parameter's value safely with a default value
    // The "Height" parameter here is missing in the animator and the default height value is returned instead
    float defaultHeight = 10f;
    float height = animator.GetFloatSafe("Height", defaultHeight);
  }

}

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

Nonagon answered 17/11, 2023 at 8:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.