How do I play a sound once with GetMarkerReachedSignal?

Suppose I have a tool, whenever a player clicks it, it plays an animation. When the animation reaches a Marker in animation, it will play a sound.

local a = char.Humanoid:LoadAnimation(script.Parent.Animation)

script.Parent.Activated:Connect(function()
	a:Play()
	a:GetMarkerReachedSignal("Test"):Connect(function()
		print("Play sound!")
	end)
end)

However, it fires multiple times and it keep stacking up, is there any way to only fire once when it reaches the signal?
https://gyazo.com/31185a9002ae9d7f2faa4330d8331b9e

2 Likes

Because you connected the event in the Activated event, it’ll keep on making new connections every time you click with the tool equipped. Try taking the event out of the Activated event and

script.Parent.Activated:Connect(function() 
     a:Play() 
end) 

a:GetMarkerReachedSignal("Test"):Connect(function()
    print("Test") 
end) 

What if a is defined after the tool is being activated?

Well, it wouldn’t work but it should be defined before anything else. An alternative would be that you can still use your code, but you’ll have to disconnect the event when it is fired.

-- in the activated event
local con 
con = a:GetMarkerReachedSignal:Connect(function() 
     print("Play sound") 
     con:Disconnect() 
end) 

You can alternatively use the :Wait() method of the event to yield until the marker is reached, and then play the sound.

4 Likes

XY problem. Sounds more like you need to fix the root cause of this problem which is GetMarkerReachedSignal firing multiple times which is caused by your implementation.

I have never, to date, had any problem with GetMarkerReachedSignal or it firing off multiple times. This is an error on your behalf in which you are using GetMarkerReachedSignal every time Activated plays. Marker reached signals are acting on AnimationTrack objects, so you should always connect them independently of any process.

Every time your tool is activated, a new RBXScriptConnection is made by you using GetMarkerReachedSignal and having a function listen to the returned signal. Previous connections are not disconnected so this just ends up stacking.

My personal recommendation is that if you load an animation at the top of your script, then you should always connect your signals afterward. This was already mentioned earlier in the post.

local anim = char.Humanoid:LoadAnimation(script.Parent.Animation)

anim:GetMarkerReachedSignal("Test"):Connect(function ()
    print("Play sound")
end)

script.Parent.Activated:Connect(function ()
    anim:Play()
end

Any worries beyond that are just implementation details on your side that you can easily fix with some testing or application of common sense. You can’t work with undefined variables, so naturally you cannot use GetMarkerReachedSignal on a nonexistent AnimationTrack. Simply move it after.

2 Likes

In my scenario, my LoadAnimation has to be defined after activate/equip because I have multiple tools that use different animations, and I have one local script to handle it. Thank you for the suggestions!

I actually never thought of using :Wait(), it actually works, thanks!

I’m not too sure I understand what you mean by this: you are still able to separate the events regardless or create a handler that can resolve this problem for you. I feel like the repro in the OP is highly inaccurate to your case, so providing something better that’s more realistic to your situation would be more helpful.

In this specific case, as far as can be seen, you can easily separate the events.

Wait may not work quite right either because the previous invocation of the tool will fire the sound for the next invocation of the tool early. Depends exactly how you wrote the code.

The most sustainable way to do this if you want no debounce but no bad behavior when you spam click the right way is to have explicit cancellation code in your tool that knows how to correctly cancel the previous animation and all of the stuff hooked up to it.

local currentSoundConnection;
function cancelAnimation()
    if currentSoundConnection then
        currentSoundConnection:Disconnect()
        currentSoundConnection = nil
    end
end
tool.Activated:connect(function()
    cancelAnimation() -- make sure nothing's playing
    runNewAnimation()
end)
2 Likes

Doesn’t work at all for me. Please reply with another answer.