[Solved] Overriding StateMachineBehaviours?

With an AnimatorOverrideController, animations can be overriden. Is it possible to do the same for StateMachineBehaviours?

Basically, I have a “PlaySound” StateMachineBehaviour and would like to play a different sound for different AnimatorOverrideControllers.

bump

As far as I’m aware there isn’t a way to override a StateMachineBehaviour like you do the AnimationClips using an AnimatorOverrideController. However, you can have all your sound management done from a different component and then call that component from your StateMachineBehavior. Here is something that might get you close to what you are looking for:

SoundCollection Component for managing the sounds that this object needs to play via the PlayStateSound StateMachineBehavior

using UnityEngine;
/// <summary>
/// This component manages the sounds that
/// will be played based on other gameplay
/// events
/// </summary>
[RequireComponent(typeof(AudioSource))]
public class SoundCollection : MonoBehaviour
{
    /// <summary>
    /// The types of sounds that
    /// are defined in this component
    /// </summary>
    public enum SoundType
    {
        Attack,
        Block,
        Injured,
        Death
    }
    /// <summary>
    /// This method plays a one shot
    /// audio based on the type provided
    /// </summary>
    /// <param name="type">The type of sound to be played</param>
    public void PlaySoundType(SoundType type)
    {
        switch (type)
        {
            case SoundType.Attack:
                audioSource.PlayOneShot(AttackSound);
                break;
            case SoundType.Block:
                audioSource.PlayOneShot(BlockSound);
                break;
            case SoundType.Injured:
                audioSource.PlayOneShot(InjuredSound);
                break;
            case SoundType.Death:
                audioSource.PlayOneShot(DeathSound);
                break;
            default:
                break;
        }
    }
    // Each of the audio clip fields
    // corresponds to a type in the
    // SoundType enum.
    [SerializeField]
    private AudioClip AttackSound;
    [SerializeField]
    private AudioClip BlockSound;
    [SerializeField]
    private AudioClip InjuredSound;
    [SerializeField]
    private AudioClip DeathSound;
    private AudioSource audioSource;
    private void Start()
    {
        audioSource = GetComponent<AudioSource>();
    }
}

PlayStateSound StateMachineBehaviour that can be configured to play an appropriate sound. The SoundCollection provides all the sounds and can be configured on a per object basis.

using UnityEngine;
public class PlayStateSound : StateMachineBehaviour
{
    // What soundType should be played on enter
    [SerializeField]
    private SoundCollection.SoundType soundType;
    // Cached reference to the soundCollection
    // that is on this gameObject
    private SoundCollection soundCollection;
    // OnStateEnter is called when a transition starts and the state machine starts to evaluate this state
    override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        if (soundCollection == null)
        {
            soundCollection = animator.GetComponent<SoundCollection>();
            if (soundCollection == null)
            {
                // Log an error is there is no sound collection
                // available to reference
                Debug.LogError("No sound collection was found on the object '" + animator.gameObject.name + "'");
                return;
            }
        }
        soundCollection.PlaySoundType(soundType);
    }
}
code]

I wasn't sure what your use case was so I just used somewhat standard things like attack, block, injured, and death for the example. I hope that helps you out a bit.

Cheers,
TrickyHandz
1 Like

Thanks that’s a brilliant idea! I’m not a big fan of the enum but it does point me in the right direction. I think what I’ll end up doing is having a list of States & Sound. I’ll have an editor that gets all the PlaySound behaviours and what states they’re in similar to the AnimatorOverrideController.

1 Like

Glad it helped in some way. There’s a lot better ways to do this than the enum for making it scalable. You’re solution sounds great! I think I might just have to follow your lead on this one as I have need for it in an upcoming project.

Cheers,
TrickyHandz

1 Like

If I get around to finishing the code first I’ll make sure to post it here! :wink:

The easiest way would be to have an AudioSource on the same GameObject as the Animator which contains the state machine, then getting the referenece to that component through the animator, and telling it to play. If you need to select from more than one audio clip, you could store them in a seperate MonoBehaviour component and get the reference to that first, and then pass the desired audioClip to the audio source.

public class AudioPlayBehaviour : StateMachineBehaviour
{
    public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        animator.GetComponent<AudioSource>().Play();

        // Or if you want to get the audio from another object
        var audioContainer = animator.GetComponent<AudioContainer>();
        var audioClip = audioContainer.soundClips[3];
        animator.GetComponent<AudioSource>().PlayOneShot(audioClip);
    }
}