Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add interaction sounds to BaseButton (customizable via theme) #1472

Open
SoloCarryGuy opened this issue Sep 7, 2020 · 20 comments
Open

Add interaction sounds to BaseButton (customizable via theme) #1472

SoloCarryGuy opened this issue Sep 7, 2020 · 20 comments

Comments

@SoloCarryGuy
Copy link

SoloCarryGuy commented Sep 7, 2020

Describe the project you are working on:
I am working on a match 3 game

Describe the problem or limitation you are having in your project:
For all the button presses I have to manually code a sound to be played by creating a global sound manager whenever a button is pressed

Describe the feature / enhancement and how it helps to overcome the problem or limitation:
Add sound to be played on button pressed

Describe how your proposal will work, with code, pseudocode, mockups, and/or diagrams:
Sorry, I am a beginner in godot and game development, I don't know how to impplement it.

If this enhancement will not be used often, can it be worked around with a few lines of script?:
I think this is used for every button in every game made.

Is there a reason why this should be core and not an add-on in the asset library?:
Again, because there are always sounds on button presses

@Jummit
Copy link

Jummit commented Sep 7, 2020

Sorry, I am a beginner in godot and game development, I don't know how to impplement it.

https://gamedev.stackexchange.com/questions/184354/add-a-sound-to-all-the-buttons-in-a-project/184363#184363

@Calinou
Copy link
Member

Calinou commented Sep 7, 2020

See also godotengine/godot#3608.

@JOELwindows7
Copy link

JOELwindows7 commented Dec 28, 2020

Sorry, I am a beginner in godot and game development, I don't know how to impplement it.

https://gamedev.stackexchange.com/questions/184354/add-a-sound-to-all-the-buttons-in-a-project/184363#184363

Thancc. cool and good. However though, there is a disadvantage you have to beware:

  • Godot projects workflow are made in various ways. The problem using such method comes at which workflow where nodes will be instantiated and freed.
  • As you can see, everytime a button node appears, each button signal of pressed will be connected to that ButtonSound node on that audio related Singleton as a child.
  • connecting alot of signals of button pressed will gradually kill performance.
  • I think signal connection is automatically removed when the node is going to be freed, idk
  • well let's say we will have 1000 more buttons here.
  • the frame drops severely, because there are 1000 more signal connections. not just a frame drop though, it's really lagging now.
  • once it happened, you have to restart this Godot process all over again to clear application instance and its allocations.

well, I guess, maybe an AudioStreamPlayer in each Button node might be sufficient way for now.
anybody can solve the eventual lag everytime new button node appears for particularly using automatic signal connection method like @Jummit above?

@Jummit
Copy link

Jummit commented Dec 28, 2020

As long as you don't have thousands of buttons instantiated at the same time, there shouldn't be a problem, right?

@JOELwindows7
Copy link

JOELwindows7 commented Dec 28, 2020

As long as you don't have thousands of buttons instantiated at the same time, there shouldn't be a problem, right?

at least it works really well. If the workflow is simply at only one way form of a UI in this node or anything simple and not complicated, this should not be a problem.

but yeah. my game workflow is like above, New nodes that will instantiated & freed. And I have alot.

perhaps by having _on_node_freed method which contains disconnect signal would help.

@Calinou
Copy link
Member

Calinou commented Sep 19, 2021

I worked around this in godot-mdk by adding a custom class that extends Button and instantiating it in my scenes using the editor instead of using Button. Here's a more generic example:

# Save this script, then add MyButton nodes instead of Button using the Create New Node dialog.
extends Button
class_name MyButton


## Called when the button is pressed.
func _pressed():
	# This assumes you have a Sound singleton designed to play sounds with polyphony:
	# https://github.com/Calinou/godot-mdk/blob/master/autoload/sound.gd
	Sound.play(self, preload("res://button_click.wav"))

The _pressed() function is called automatically by Godot when you add it to a node that extends BaseButton. No need to connect it to a signal.

@YuriSizov
Copy link
Contributor

As the theme guy on the team I fully support that controls should have an inherent ability to produce focus and interaction sounds, and that it should be customizable with a theme. It's not too hard to work around with nodes, but it's an unnecessary complication for getting your game that polished feel.

I will gladly work on this.

@YuriSizov YuriSizov changed the title Add a sound property to buttons Add interaction sounds to Button customizable via theme Sep 19, 2021
@Calinou Calinou changed the title Add interaction sounds to Button customizable via theme Add interaction sounds to BaseButton (customizable via theme) Sep 19, 2021
@dalexeev
Copy link
Member

One more option (for 4.0-dev):

extends Button
class_name MyButton

var _allow_focus_sfx := true

func _init() -> void:
    focus_entered.connect(_on_focus_entered)

func _input(event: InputEvent) -> void:
    if !is_visible_in_tree() || !has_focus():
        return

    if event.is_action_pressed('ui_accept'):
        if disabled:
            SFX.play('gui_error.wav')
        else:
            SFX.play('gui_accept.wav')

func grab_focus_no_sfx() -> void:
    _allow_focus_sfx = false
    grab_focus()
    _allow_focus_sfx = true

func _on_focus_entered() -> void:
    if _allow_focus_sfx:
        SFX.play('gui_focus.wav')

@Shadowblitz16
Copy link

Shadowblitz16 commented Apr 18, 2023

@dalexeev that doesn't work for mouse right clicks
infact that doesn't seem to work for alot of things

@Calinou
Copy link
Member

Calinou commented Apr 19, 2023

Note that this will probably require something analogous to SceneTreeTimer/SceneTreeTween to be implemented. A SceneTreeAudioStreamPlayer should allow polyphonic audio playback without creating any nodes, so that using things like get_tree().change_scene_to_packed() does not stop sound playback.

(This is also useful in game logic if you want to avoid the issue where freeing a node stops its audio playback. However, we'd need to add positional variants in this case as well, and you would most likely not be able to change their position after creating them.)

@dalexeev that doesn't work for mouse right clicks infact that doesn't seem to work for alot of things

It should work with right-clicks if the button has the appropriate click mask. Buttons should not play a sound if clicked with a button that doesn't actually press them, so it's expected that by default, right-clicking shouldn't play a sound.

@Shadowblitz16
Copy link

Shadowblitz16 commented Apr 19, 2023

@Calinou it doesn't.
it seems like it has to do with the mouse clicks that don't have a mask that happen after it's already focused.

it only tends to play the focus sound with the mouse.
I also tried changing action mode to press it doesn't change anything

try it

extends Button
class_name MyButton

var _allow_focus_sfx := true

@export var sound_error  : AudioStream
@export var sound_focus  : AudioStream
@export var sound_accept : AudioStream

func _init() -> void:
	focus_entered.connect(_on_focus_entered)

func _input(event: InputEvent) -> void:
	if !is_visible_in_tree() || !has_focus():
		return

	if event.is_action_pressed('ui_accept'):
		if    disabled && sound_error : Sound.play(self, sound_error)
		elif !disabled && sound_accept: Sound.play(self, sound_accept)

func grab_focus_no_sfx() -> void:
	_allow_focus_sfx = false
	grab_focus()
	_allow_focus_sfx = true

func _on_focus_entered() -> void:
	if _allow_focus_sfx && sound_focus:
		Sound.play(self, sound_focus)

@dalexeev
Copy link
Member

@Shadowblitz16 Sorry, I forgot to mention that this class was only implemented for keyboard control. Mouse support requires other changes. For example like this:

Code
extends Button
class_name MyButton

var _allow_focus_sfx := true

func _init() -> void:
    focus_entered.connect(_on_focus_entered)
    button_down.connect(_on_button_down)
    pressed.connect(_on_pressed)

func _gui_input(event: InputEvent) -> void:
    if disabled:
        if event.is_action_pressed("ui_accept") or (
                event is InputEventMouseButton
                        and event.button_index == MOUSE_BUTTON_LEFT
                        and event.is_pressed()):
            SFX.play("gui_error.wav")

func grab_focus_no_sfx() -> void:
    _allow_focus_sfx = false
    grab_focus()
    _allow_focus_sfx = true

func _on_focus_entered() -> void:
    if _allow_focus_sfx and not Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT):
        SFX.play("gui_focus.wav")

func _on_button_down() -> void:
    SFX.play("gui_focus.wav")

func _on_pressed() -> void:
    if toggle_mode:
        if button_pressed:
            SFX.play("gui_enabled.wav")
        else:
            SFX.play("gui_disabled.wav")
    else:
        SFX.play("gui_accept.wav")

@Calinou
Copy link
Member

Calinou commented Jul 23, 2023

@YuriSizov How do you envision the audio theme items in terms of structure? I'm asking because I wonder how we could expose a way to set generic "hover", "focus", "pressed" sounds that work across the entire theme, without requiring users to override them on individual classes. However, if users wish to override the interaction sounds on a specific control, they should be able to do so.

Could this work in a manner similar to the Default Font top-level Theme property?

@YuriSizov
Copy link
Contributor

@Calinou I'm not a fan of this idea. The default font works because we can decide to use it as a fallback based on the resource type. What you propose is that we add special resolution logic based on the resource type AND the name of a theme item. We don't really have a very consistent theme item naming convention and it would make the whole thing string-reliant.

So for now I'd focus on implementing the core system, having audio as a theme item type, making it work, figuring out how it must work. The hard part is figuring out how the audio stuff is going to be configured. Should we allow for positional audio or should it be position-less? How do we configure the bus? Would it be a part of some theme audio resource or a setting on the theme resource? Or a global setting per project?

Having some kind of fallback system can be added later, if we truly need it. Way after we ship the initial feature.

@Calinou
Copy link
Member

Calinou commented Jul 25, 2023

7.5 years after originally proposing this feature, I have a working proof of concept: https://github.com/Calinou/godot/tree/add-theme-audio-items
See commit message for technical details.

Testing project: control_gallery.zip
You need to compile the above branch for that project to play audio. Audio might be quite loud by default depending on your setup, so consider turning down your volume before interacting with the UI.

I couldn't figure out how to play audio without creating nodes1, so I added a helper method to SceneTree that creates a temporary top-level internal node (similar to what godotengine/godot#79599 does). The node is automatically freed once audio playback is finished. Multiple nodes may be created at once, which implicitly allows for polyphony (including with different, unrelated AudioStreams).

Using an AudioStreamRandomizer resource should work too, if you want to add some variation to UI sounds. From a performance standpoint, creating nodes is probably not too bad since you're unlikely to have more than 5 active UI audio sources at a given time. (This is especially the case if you stick to WAV, as recommended for short audio files.)

Footnotes

  1. Some commented out code remains in the branch if you'd like to take a look.

@YuriSizov
Copy link
Contributor

Amazing work!

I couldn't figure out how to play audio without creating nodes

Well, that's a bummer. I think this should be resolved first, we need to be able to do the playback directly with the server, at least for the non-positional audio (which is what the UI sfx would likely be). Maybe @ellenhp can help us figure out how feasible it would be to implement?

@Calinou
Copy link
Member

Calinou commented Aug 7, 2023

I made further progress in my branch: https://github.com/Calinou/godot/tree/add-theme-audio-items

simplescreenrecorder-2023-08-07_11.34.57.mp4

Sounds from https://github.com/redeclipse/sounds.

There are still some things to iron out (specifically regarding the inspector and --doctool not "seeing" audio theme items for some reason), but it's getting there.

@conde2
Copy link

conde2 commented Feb 26, 2024

Is there a pull request for this in godot master? This is a must feature.
@Calinou amazing work!

@Calinou
Copy link
Member

Calinou commented Feb 26, 2024

Is there a pull request for this in godot master? This is a must feature.

Not yet, as my branch still has some work left to do (check the TODO part of the commit message). This is too late for 4.3 either way, which is entering feature freeze soon.

@conde2
Copy link

conde2 commented Feb 27, 2024

That's sad to hear. Maybe this comes to the 4.4 then? For the video it was looking very promising, keep the good work.

I will leave it out here as I don't have much to contribute to the proposal, but imo audio theme UI is brilliant suggestion that I strongly support.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants