Play sound when UI Toolkit button is clicked

Using Unity UI Toolkit and the builder, how would I play a sound when a button is clicked?

I don’t think there is a built in solution for that just yet. You would need to do some scripting for that.
You can add global click listener that checks if clicked element is a Button, e.g.

        private void OnEnable()
        {
            _root = uiDocument.rootVisualElement.Q<VisualElement>(ROOT_Q);

            _root.RegisterCallback<ClickEvent>(OnPress, TrickleDown.TrickleDown);
        }

        private void OnDisable()
        {
            _root.UnregisterCallback<ClickEvent>(OnPress, TrickleDown.TrickleDown);
        }

        private void OnPress(EventBase evt)
        {
            if (evt.target is Button)
            {
                pressSound.Play();
            }
        }
4 Likes

Is this still valid in Unity 6? I’m new to UI Toolkit and trying to wrap my head around on how to play sound on various UI events.

EDIT: This works well enough:

using System.Collections.Generic;
using Redacted.Core;
using UnityEngine;
using UnityEngine.UIElements;
using Random = UnityEngine.Random;

namespace Redacted.Audio
{
    public class ButtonSound : MonoBehaviour
    {
        [SerializeField] private UIDocument _uiDocument;
        [SerializeField] private AudioClip _hoverSound;
        [SerializeField] private AudioClip _clickSound;

        private AudioManager _audioManager;
        private VisualElement _root;
        private readonly List<Button> _buttons = new();

        private void OnEnable()
        {
            _root = _uiDocument.rootVisualElement;

            // Find all Button elements and register the ClickEvent and MouseEnterEvent callbacks
            _buttons.AddRange(_root.Query<Button>().ToList());

            foreach (Button button in _buttons)
            {
                button.RegisterCallback<ClickEvent>(OnPress, TrickleDown.TrickleDown);
                button.RegisterCallback<MouseEnterEvent>(OnHover, TrickleDown.TrickleDown);
            }
        }

        private void OnDisable()
        {
            foreach (Button button in _buttons)
            {
                button.UnregisterCallback<ClickEvent>(OnPress, TrickleDown.TrickleDown);
                button.UnregisterCallback<MouseEnterEvent>(OnHover, TrickleDown.TrickleDown);
            }

            _buttons.Clear();
        }
        
        private void Start()
        {
            _audioManager = ComponentLocator.Get<AudioManager>();
        }
        
        private void OnPress(ClickEvent evt)
        {
            var clickedButton = (Button)evt.target;
            Debug.Log($"Button '{clickedButton.name}' pressed!");
        
            PlayClickSound();;
        }
        
        private void OnHover(MouseEnterEvent evt)
        {
            var hoveredButton = (Button)evt.target;
            Debug.Log($"Button '{hoveredButton.name}' hovered!");
        
            PlayHoverSound();
        }
        private void PlayHoverSound()
        {
            if (_hoverSound != null && _audioManager != null)
            {
                PlayRandomizedPitchSfx(_hoverSound);
            }
        }

        private void PlayClickSound()
        {
            if (_clickSound != null && _audioManager != null)
            {
                PlayRandomizedPitchSfx(_clickSound);
            }
        }

        private void PlayRandomizedPitchSfx(AudioClip clip)
        {
            float randomPitch = Eerp(0.8f, 1.1f, Random.value);
            _audioManager.SfxSource.pitch = randomPitch;
            _audioManager.PlaySfx(clip);
        }

        private float Eerp(float a, float b, float t)
        {
            return a * Mathf.Exp(t * Mathf.Log(b / a));
        }
    }
}

We have an alternative way to add behaviours to elements such as buttons in the UxmlObject example here under Button behaviour

2 Likes

Wow, this is indeed game changer!

Any feature that worth to know? Would be nice if we have list of feature UI Toolkit. Right now, foundations.unity.com is documentation that I always worth visiting.

1 Like

These show how to do something on click and expose properties to uxml file inspector in the UI builder. I could also initialize say a localized button text with it, which is great. But what about hover/mouse enter states? I want to play a different sound when the button is hovered with mouse pointer and [UxmlObject] fields are not exposed to USS pseudo-class :hover styling options and there is no callback in the Button class for hover/mouse enter as far as I can tell looking over the code. Would it be correct to assume this is a current limitation of this approach?

You could use the MousOverEvent. Then use that instead of clicked

1 Like

This worked, thanks. I hope RegisterCallback<MouseOverEvent>(OnHover) was the right approach in this case.

using Redacted.Core;
using UnityEngine;
using UnityEngine.UIElements;

[UxmlElement]
public partial class ButtonWithSound : Button
{
    [UxmlObjectReference("clicked")] 
    public ButtonBehaviour ButtonBehaviourWithSound { get; set; }

    public ButtonWithSound()
    {
        clicked += OnClick;
        RegisterCallback<MouseOverEvent>(OnHover);
    }

    void OnClick()
    {
        ButtonBehaviourWithSound?.OnClicked(this);
    }

    void OnHover(MouseOverEvent evt)
    {
        ButtonBehaviourWithSound?.OnHovered(this);
    }
}

[UxmlObject]
public abstract partial class ButtonBehaviour
{
    public abstract void OnClicked(Button button);
    public abstract void OnHovered(Button button);
}

[UxmlObject]
public partial class PlaySoundButtonBehaviour : ButtonBehaviour
{
    [UxmlAttribute]
    public AudioClip ClickSound { get; set; }
    [UxmlAttribute]
    public AudioClip HoverSound { get; set; }
    
    public override void OnClicked(Button button)
    {
        var audioManager = ComponentLocator.Get<AudioManager>();
        audioManager.PlaySfx(ClickSound, true);
    }
    public override void OnHovered(Button button)
    {
        var audioManager = ComponentLocator.Get<AudioManager>();
        audioManager.PlaySfx(HoverSound, true);
    }
}

And a simpler way to approach this without having to figure out behaviours:

using UnityEngine;
using UnityEngine.UIElements;

[UxmlElement]
public partial class ButtonWithSound : Button
{
    [UxmlAttribute] public AudioClip ClickSound { get; set; }
    [UxmlAttribute] public AudioClip HoverSound { get; set; }

    //subscribe to relevant button events in the button's constructor
    public ButtonWithSound()
    {
        clicked += OnClick;
        RegisterCallback<MouseOverEvent>(OnHover);
    }

    void OnClick()
    {
       //play sound
    }

    void OnHover(MouseOverEvent evt)
    {
       //play sound
    }
}
1 Like

It’s really a shame that in 2025 we need to create custom classes or UXML elements to have something like a sound on button click. Something that… pretty much every button in every game wants to have.

Please, Unity. It’s not 1985. If you make something called “UI Toolkit”, you need to see audio as part of User Interface.

I mean it takes 5 seconds to implement it yourself. And you would have to implement it yourself anyway if you’re using your own or a 3rd party audio API, which I imagine most people are.

And realistically they have more important things to focus on like actually achieving feature parity with UGUI. I don’t care about having to implement my own button click. I do care that they still don’t support shaders.

1 Like

Sure I can implement it myself.

The point is not that I can’t do that. The point is that Unity still only thinks about graphics when they say “UI” and that’s simply not true. Audio is constantly being treated as the adopted child nobody really wants, and is far, far behind anything else inside Unity.

The point is that UI Toolkit has more pressing matters to be addressed first, such as shader and world space UI support.

We can add the easy stuff ourselves in the mean time.

And like I said, even if Unity had audio support for buttons, or perhaps a hypothetical AudioButton, there’s a chance most of us would still need to implement our own to interface with another audio API.

1 Like