Problems with stopping a loop (typewriter effect)

Hey! I’m trying to stop a loop (typewriter effect) from a different script, using a BoolValue. However, it doesn’t really work. Once you stop touching the hitreg part, it does go invisible and tweens away, but the text (typewriter effect) still keeps on going. If you then enter again, 2 or more texts (typewriter effects) collide with each other.

ModuleScript

local module = {}

function module.typeWrite(object, text, length, sound)
	local player = object.Parent.Parent.Parent.Parent
	local TypewriterDebounce = player.ConstructionFolder.TypewriterDebounce
	
	if TypewriterDebounce.Value == true then
		for i = 1, #text do
			if TypewriterDebounce.Value == true then
				object.Text = string.sub(text,1,i)
				sound:Play()
				task.wait(length)
			elseif TypewriterDebounce.Value == false then
				object.Text = ""
				sound:Stop()
				break
			end
		end
	end
end

return module

ServerScript

local dialogueDebounce = false
local progressDebounce = false

local TypewriterModule = require(script.ModuleWriter)
local writeSound = script.Typewriter

local RS = game:GetService("ReplicatedStorage")
local EffectEvent = RS:WaitForChild("ConstructionSoundBlur")

script.Parent.Touched:Connect(function(hit)
	local player = game.Players:GetPlayerFromCharacter(hit.Parent)
	if player then
		local HasAcceptedYet = player:WaitForChild("ConstructionFolder").HasAcceptedYet
		local TypewriterDebounce = player:WaitForChild("ConstructionFolder").TypewriterDebounce
		if HasAcceptedYet.Value == false then
			if dialogueDebounce == false then
				local gui = player.PlayerGui:WaitForChild("EventDialogue")
				TypewriterDebounce.Value = true
				gui.Holder.Visible = true
				gui.Holder:TweenPosition(UDim2.new(0.5, 0, 0.73, 0), "In", "Linear", 1, true, nil)
				dialogueDebounce = true
				task.wait(1)
				gui.Holder.Header.Visible = true
				gui.Holder.DialogueText.Visible = true
				task.wait(1)
				local yesButton = gui.Holder.Yes
				local noButton = gui.Holder.No
				local object = gui.Holder.DialogueText
				if TypewriterDebounce.Value == true then
					TypewriterModule.typeWrite(object, "Yo! I'm Frank, the Construction Manager.", writeSound.TimeLength, writeSound)
					wait(2)
					TypewriterModule.typeWrite(object, "We are tasked with constructing a new building, but we won't finish in time!", writeSound.TimeLength, writeSound)
					wait(3)
					TypewriterModule.typeWrite(object, "We need your help constructing the building.", writeSound.TimeLength, writeSound)
					wait(2)
					TypewriterModule.typeWrite(object, "Do you accept this offer?", writeSound.TimeLength, writeSound)
					wait(0.5)
					yesButton.Visible = true
					noButton.Visible = true
					yesButton:TweenPosition(UDim2.new(0.162, 0, 1.214, 0), "In", "Linear", 0.5, true, nil)
					noButton:TweenPosition(UDim2.new(0.562, 0, 1.214, 0), "In", "Linear", 0.5, true, nil)
				end
				wait(0.5)
				noButton.MouseButton1Click:Connect(function()
					gui.Holder.DialogueText.Text = ""
					yesButton:TweenPosition(UDim2.new(0.162, 0, 3, 0), "In", "Linear", 0.2, true, nil)
					noButton:TweenPosition(UDim2.new(0.562, 0, 3, 0), "In", "Linear", 0.2, true, nil)
					wait(0.2)
					yesButton.Visible = false 
					noButton.Visible = false
				end)
				yesButton.MouseButton1Click:Connect(function()
					yesButton:TweenPosition(UDim2.new(0.162, 0, 3, 0), "In", "Linear", 0.2, true, nil)
					noButton:TweenPosition(UDim2.new(0.562, 0, 3, 0), "In", "Linear", 0.2, true, nil)
					wait(0.2)
					yesButton.Visible = false
					noButton.Visible = false
					TypewriterModule.typeWrite(object, "Thanks for your help!", writeSound.TimeLength, writeSound)
					wait(2)
					TypewriterModule.typeWrite(object, "To manage your tasks, click on the orange star button.", writeSound.TimeLength, writeSound)
					wait(2)
					TypewriterModule.typeWrite(object, "That's all, good luck!", writeSound.TimeLength, writeSound)
				end)
			end
		elseif HasAcceptedYet.Value == true then
			if progressDebounce == false then
				local gui = player.PlayerGui:WaitForChild("ConstructionGui")
				local sound = player.PlayerGui.HUD.Sidebar.Container.Construction.LocalScript.ClickSound
				gui.Holder.Visible = true
				gui.Holder:TweenPosition(UDim2.new(0.5, 0, 0.5, 0), "In", "Elastic", 1, true, nil)
				EffectEvent:FireClient(player, 15)
				task.wait(1)
				progressDebounce = true
			end
		end
	end		
end)


script.Parent.TouchEnded:Connect(function(hit)
	local player = game.Players:GetPlayerFromCharacter(hit.Parent)
	if player then
		if player:FindFirstChild("PlayerGui") then
			local HasAcceptedYet = player:WaitForChild("ConstructionFolder").HasAcceptedYet
			local TypewriterDebounce = player:WaitForChild("ConstructionFolder").TypewriterDebounce
			if HasAcceptedYet.Value == false then
				if dialogueDebounce == true then
					local gui = player.PlayerGui:WaitForChild("EventDialogue")
					gui.Holder.DialogueText.Text = ""
					gui.Holder.Header.Visible = false
					gui.Holder.DialogueText.Visible = false
					gui.Holder.Yes.Visible = false
					gui.Holder.No.Visible = false
					TypewriterDebounce.Value = false
					gui.Holder:TweenPosition(UDim2.new(0.5, 0, 1.5, 0), "In", "Linear", 1, true, nil)
					task.wait(1)
					gui.Holder.Visible = false
					dialogueDebounce = false
				end
			elseif HasAcceptedYet.Value == true then
				if progressDebounce == true then
					local gui = player.PlayerGui.ConstructionGui
					local sound = player.PlayerGui.HUD.Sidebar.Container.Construction.LocalScript.ClickSound
					gui.Holder:TweenPosition(UDim2.new(0.5,0,1.5,0), 'InOut', 'Elastic', 1)
					EffectEvent:FireClient(player, 0)
					task.wait(0.8)
					progressDebounce = false
				end
			end
		end
	end
end)

1 Like

hello!
try the coroutine library to cancel the typewriter code when the player leaves the hitReg part.

(server script)

local DialougeCoroutine -- Defined whenever player starts touching

script.Parent.Touched:Connect(function(hit)
	local player = game.Players:GetPlayerFromCharacter(hit.Parent)
	if player then
		DialougeCoroutine = coroutine.create(function()
			--[[ Code ]]--	
		end)
		coroutine.resume(DialougeCoroutine) -- Start the coroutine by "resuming" it
	end		
end)


script.Parent.TouchEnded:Connect(function(hit)
	local player = game.Players:GetPlayerFromCharacter(hit.Parent)
	if player then
		if DialougeCoroutine ~= nil then -- Dialouge Coroutine has been assigned
			coroutine.yield(DialougeCoroutine) -- You need to yield a coroutine before closing it
			coroutine.close(DialougeCoroutine)
		end
		--[[ Code ]]--
	end
end)
2 Likes

Was going to reply, but @VexorgIRL beat me to it. I’ll leave my original reply but just the suggestions:

Hello again! Glad you got your typewriter working! I noticed a few issues with your script.

The first is that if somebody were to enter the part, nobody else would be able to enter it until the player that first entered left (because of the two global values you define at the start of the script).
Secondly, you seem to add the TypeWriter module and sound for it inside of the part.
Third of all, it is terribly hard to read and use efficiently.
Finally, the sound you use would play globally to everyone if you use it on a server-sided script. Try control it from the client.

Inside the TypeWriter module, I would add an extra variable: player, since the object’s parent parent parent etc. is not always guaranteed to be the player.

The second issue I mentioned, the one with the TypeWriter module, it’s not going to do you any favours when you want to use the typewriter module multiple times, so I would parent the typewriter module to ReplicatedStorage. I would also parent the sound used to the typewriter. I would replace the two global values at the start of the script (dialogueDebounce and progressDebounce) as values inside ConstructionFolder.

I would also add variables for things used multiple times (e.g ConstructionFolder and gui.Holder).

2 Likes

I tried all the points you mentioned, though it doesn’t even make the GUI tween and make it visible now. There are no errors, plus the 3 BoolValues seem to be loaded, tested by prints.

If you want to, we can talk in PMs since the topic has (practically) been solved.

1 Like

i completely forgot you have to manually start coroutines when using coroutine.create(), my mistake!

ive edited the original post now, but all you have to add is:

coroutine.resume(DialougeCoroutine)

(under the code defining the DialougeCoroutine)

1 Like

I added the coroutine.resume part, however, it still doesn’t work. I added 3 print tests, only 2 get outprinted (1 and 2, 3 doesn’t). Any idea why is that happening?

I even tried putting :WaitForChild("HasAcceptedYet"), however, that didn’t help either. Did I set up the coroutine correctly?

local RS = game:GetService("ReplicatedStorage")
local EffectEvent = RS:WaitForChild("ConstructionSoundBlur")
local TypewriterModule = require(RS:WaitForChild("ModuleWriter"))
local writeSound = RS:WaitForChild("SoundWriter")

local DialougeCoroutine

script.Parent.Touched:Connect(function(hit)
	print("1")
	local player = game.Players:GetPlayerFromCharacter(hit.Parent)
	if player then
		print("2")
		DialougeCoroutine = coroutine.create(function()
			local HasAcceptedYet = player:WaitForChild("ConstructionFolder").HasAcceptedYet
			local TypewriterDebounce = player:WaitForChild("ConstructionFolder").TypewriterDebounce
			local dialogueDebounce = player:WaitForChild("ConstructionFolder").dialogueDebounce
			local progressDebounce = player:WaitForChild("ConstructionFolder").progressDebounce
			if HasAcceptedYet.Value == false then
				print("3")
				if dialogueDebounce.Value == false then
					local gui = player.PlayerGui:WaitForChild("EventDialogue").Holder
					TypewriterDebounce.Value = true
					gui.Visible = true
					gui:TweenPosition(UDim2.new(0.5, 0, 0.73, 0), "In", "Linear", 1, true, nil)
					dialogueDebounce.Value = true
					task.wait(1)
					gui.Header.Visible = true
					gui.DialogueText.Visible = true
					task.wait(1)
					local yesButton = gui.Yes
					local noButton = gui.No
					local object = gui.DialogueText
					TypewriterModule.typeWrite(player, object, "Yo! I'm Frank, the Construction Manager.", writeSound.TimeLength, writeSound)
					task.wait(2)
					TypewriterModule.typeWrite(player, object, "We are tasked with constructing a new building, but we won't finish in time!", writeSound.TimeLength, writeSound)
					task.wait(3)
					TypewriterModule.typeWrite(player, object, "We need your help constructing the building.", writeSound.TimeLength, writeSound)
					task.wait(2)
					TypewriterModule.typeWrite(player, object, "Do you accept this offer?", writeSound.TimeLength, writeSound)
					task.wait(0.5)
					yesButton.Visible = true
					noButton.Visible = true
					yesButton:TweenPosition(UDim2.new(0.162, 0, 1.214, 0), "In", "Linear", 0.5, true, nil)
					noButton:TweenPosition(UDim2.new(0.562, 0, 1.214, 0), "In", "Linear", 0.5, true, nil)
					task.wait(0.5)
					noButton.MouseButton1Click:Connect(function()
						gui.DialogueText.Text = ""
						yesButton:TweenPosition(UDim2.new(0.162, 0, 3, 0), "In", "Linear", 0.2, true, nil)
						noButton:TweenPosition(UDim2.new(0.562, 0, 3, 0), "In", "Linear", 0.2, true, nil)
						task.wait(0.2)
						yesButton.Visible = false 
						noButton.Visible = false
					end)
					yesButton.MouseButton1Click:Connect(function()
						yesButton:TweenPosition(UDim2.new(0.162, 0, 3, 0), "In", "Linear", 0.2, true, nil)
						noButton:TweenPosition(UDim2.new(0.562, 0, 3, 0), "In", "Linear", 0.2, true, nil)
						task.wait(0.2)
						yesButton.Visible = false
						noButton.Visible = false
						TypewriterModule.typeWrite(player, object, "Thanks for your help!", writeSound.TimeLength, writeSound)
						task.wait(2)
						TypewriterModule.typeWrite(player, object, "To manage your tasks, click on the orange star button.", writeSound.TimeLength, writeSound)
						task.wait(2)
						TypewriterModule.typeWrite(player, object, "That's all, good luck!", writeSound.TimeLength, writeSound)
					end)
				end
			elseif HasAcceptedYet.Value == true then
				if progressDebounce.Value == false then
					local gui = player.PlayerGui:WaitForChild("ConstructionGui").Holder
					local sound = player.PlayerGui.HUD.Sidebar.Container.Construction.LocalScript.ClickSound
					gui.Visible = true
					gui:TweenPosition(UDim2.new(0.5, 0, 0.5, 0), "In", "Elastic", 1, true, nil)
					EffectEvent:FireClient(player, 15)
					task.wait(1)
					progressDebounce.Value = true
				end
			end	
		end)
	end		
end)


script.Parent.TouchEnded:Connect(function(hit)
	local player = game.Players:GetPlayerFromCharacter(hit.Parent)
	if player then
		if DialougeCoroutine ~= nil then -- Dialouge Coroutine has been assigned
			coroutine.yield(DialougeCoroutine) -- You need to yield a coroutine before closing it
			coroutine.close(DialougeCoroutine)
		end
		coroutine.resume(DialougeCoroutine)
		local dialogueDebounce = player:WaitForChild("ConstructionFolder").dialogueDebounce
		local progressDebounce = player:WaitForChild("ConstructionFolder").progressDebounce
		local HasAcceptedYet = player:WaitForChild("ConstructionFolder").HasAcceptedYet
		local TypewriterDebounce = player:WaitForChild("ConstructionFolder").TypewriterDebounce
		if HasAcceptedYet.Value == false then
			if dialogueDebounce.Value == true then
				local gui = player.PlayerGui:WaitForChild("EventDialogue")
				gui.Holder.DialogueText.Text = ""
				gui.Holder.Header.Visible = false
				gui.Holder.DialogueText.Visible = false
				gui.Holder.Yes.Visible = false
				gui.Holder.No.Visible = false
				TypewriterDebounce.Value = false
				gui.Holder:TweenPosition(UDim2.new(0.5, 0, 1.5, 0), "In", "Linear", 1, true, nil)
				task.wait(1)
				gui.Holder.Visible = false
				dialogueDebounce.Value = false
			end
		elseif HasAcceptedYet.Value == true then
			if progressDebounce.Value == true then
				local gui = player.PlayerGui.ConstructionGui
				local sound = player.PlayerGui.HUD.Sidebar.Container.Construction.LocalScript.ClickSound
				gui.Holder:TweenPosition(UDim2.new(0.5,0,1.5,0), 'InOut', 'Elastic', 1)
				EffectEvent:FireClient(player, 0)
				task.wait(0.8)
				progressDebounce.Value = false
			end
		end
	end
end)

1 Like

this is a mistake on my end as i (for some reason?) added the coroutine.resume() line in the TouchEnded event in my example, sorry about that! :sick:

the reason why it isn’t working is because the coroutine is “killed” (stopped) when the hitReg part is no-longer being touched, and then attempt to be continue the coroutine afterwards.

add the coroutine.resume() line here instead:

script.Parent.Touched:Connect(function(hit)
	local player = game.Players:GetPlayerFromCharacter(hit.Parent)
	if player then
		DialougeCoroutine = coroutine.create(function()
			--[[ Code ]]--	
		end)
		coroutine.resume(DialougeCoroutine) -- Corountine is being resumed here instead
	end		
end)
1 Like

Lovely! It does tween now, however, when I leave the hitreg (TouchEnded), it doesn’t stop and tween away. Any idea why that happens?

you don’t seem to be changing the DialogueDebounce’s value when the dialogue starts, not allowing the tween or anything else to run because of this:

local dialogueDebounce = player:WaitForChild("ConstructionFolder").dialogueDebounce
	if HasAcceptedYet.Value == false then
		if dialogueDebounce.Value == true then -- HERE: Dialogue Debounce is never changed
			local gui = player.PlayerGui:WaitForChild("EventDialogue")
			gui.Holder.DialogueText.Text = ""
			gui.Holder.Header.Visible = false
			gui.Holder.DialogueText.Visible = false
			gui.Holder.Yes.Visible = false
			...

try adding

dialogueDebounce.Value = true

somewhere in the DialougeCoroutine

I do set the value to true.

1 Like

Any idea why it is not working fully? Because as I already replied, I do set the debounce value back to true.

1 Like

hello again! apologies for some of the previous responses…

does the dialogue box not tween away specifically, or does that entire section not run?

if its the dialogue that doesn’t tween away, try using TweenService instead or use Enums instead of strings (Enum.EasingDirection and Enum.EasingStyle).

if the entire section doesn’t run, would you mind adding some print tests under TouchEnded, specifically under every if/elseif statement?

2 Likes

It seems to be the entire section, or well, most of it. In the TouchEnded function, a breakpoint on the “if player” and “if DialogueCoroutine” checks works, however, it doesn’t work on any others.

Try using ZonePlus. What’s probably happening is one of your player parts exit (performing the TouchEnded function) while all your other body is in the part. Because of that, it will run the Touch and TouchEnded multiple times, probably causing issues. ZonePlus simply checks if a player entered an area and goes from there. The documentation is here, if you want to learn more.

I replaced your code with ZonePlus. Tell me if anything changes or if you have any new issues. If you have more issues, I’m sure @VexorgIRL will help out!

IMPORTANT EDIT: This is the code you started the thread with. Things may have been modified.

local dialogueDebounce = false
local progressDebounce = false

local TypewriterModule = require(script.ModuleWriter)
local writeSound = script.Typewriter

local RS = game:GetService("ReplicatedStorage")
local EffectEvent = RS:WaitForChild("ConstructionSoundBlur")

local ZonePlus = require(RS:WaitForChild("ZonePlus") -- you dont need to have the module in ReplicatedStorage of course, it's just an example

local zone = Zone.new(script.Parent)

zone.playerEntered:Connect(function(player)
	local HasAcceptedYet = player:WaitForChild("ConstructionFolder").HasAcceptedYet
	local TypewriterDebounce = player:WaitForChild("ConstructionFolder").TypewriterDebounce
	if HasAcceptedYet.Value == false then
		if dialogueDebounce == false then
			local gui = player.PlayerGui:WaitForChild("EventDialogue")
			TypewriterDebounce.Value = true
			gui.Holder.Visible = true
			gui.Holder:TweenPosition(UDim2.new(0.5, 0, 0.73, 0), "In", "Linear", 1, true, nil)
			dialogueDebounce = true
			task.wait(1)
			gui.Holder.Header.Visible = true
			gui.Holder.DialogueText.Visible = true
			task.wait(1)
			local yesButton = gui.Holder.Yes
			local noButton = gui.Holder.No
			local object = gui.Holder.DialogueText
			if TypewriterDebounce.Value == true then
				TypewriterModule.typeWrite(object, "Yo! I'm Frank, the Construction Manager.", writeSound.TimeLength, writeSound)
				wait(2)
				TypewriterModule.typeWrite(object, "We are tasked with constructing a new building, but we won't finish in time!", writeSound.TimeLength, writeSound)
				wait(3)
				TypewriterModule.typeWrite(object, "We need your help constructing the building.", writeSound.TimeLength, writeSound)
				wait(2)
				TypewriterModule.typeWrite(object, "Do you accept this offer?", writeSound.TimeLength, writeSound)
				wait(0.5)
				yesButton.Visible = true
				noButton.Visible = true
				yesButton:TweenPosition(UDim2.new(0.162, 0, 1.214, 0), "In", "Linear", 0.5, true, nil)
				noButton:TweenPosition(UDim2.new(0.562, 0, 1.214, 0), "In", "Linear", 0.5, true, nil)
			end
			wait(0.5)
			noButton.MouseButton1Click:Connect(function()
				gui.Holder.DialogueText.Text = ""
				yesButton:TweenPosition(UDim2.new(0.162, 0, 3, 0), "In", "Linear", 0.2, true, nil)
				noButton:TweenPosition(UDim2.new(0.562, 0, 3, 0), "In", "Linear", 0.2, true, nil)
				wait(0.2)
				yesButton.Visible = false 
				noButton.Visible = false
			end)
			yesButton.MouseButton1Click:Connect(function()
				yesButton:TweenPosition(UDim2.new(0.162, 0, 3, 0), "In", "Linear", 0.2, true, nil)
				noButton:TweenPosition(UDim2.new(0.562, 0, 3, 0), "In", "Linear", 0.2, true, nil)
				wait(0.2)
				yesButton.Visible = false
				noButton.Visible = false
				TypewriterModule.typeWrite(object, "Thanks for your help!", writeSound.TimeLength, writeSound)
				wait(2)
				TypewriterModule.typeWrite(object, "To manage your tasks, click on the orange star button.", writeSound.TimeLength, writeSound)
				wait(2)
				TypewriterModule.typeWrite(object, "That's all, good luck!", writeSound.TimeLength, writeSound)
			end)
		end
	elseif HasAcceptedYet.Value == true then
		if progressDebounce == false then
			local gui = player.PlayerGui:WaitForChild("ConstructionGui")
			local sound = player.PlayerGui.HUD.Sidebar.Container.Construction.LocalScript.ClickSound
			gui.Holder.Visible = true
			gui.Holder:TweenPosition(UDim2.new(0.5, 0, 0.5, 0), "In", "Elastic", 1, true, nil)
			EffectEvent:FireClient(player, 15)
			task.wait(1)
			progressDebounce = true
		end
	end
end)


zone.playerExited:Connect(function(player)
	if player:FindFirstChild("PlayerGui") then
		local HasAcceptedYet = player:WaitForChild("ConstructionFolder").HasAcceptedYet
		local TypewriterDebounce = player:WaitForChild("ConstructionFolder").TypewriterDebounce
		if HasAcceptedYet.Value == false then
			if dialogueDebounce == true then
				local gui = player.PlayerGui:WaitForChild("EventDialogue")
				gui.Holder.DialogueText.Text = ""
				gui.Holder.Header.Visible = false
				gui.Holder.DialogueText.Visible = false
				gui.Holder.Yes.Visible = false
				gui.Holder.No.Visible = false
				TypewriterDebounce.Value = false
				gui.Holder:TweenPosition(UDim2.new(0.5, 0, 1.5, 0), "In", "Linear", 1, true, nil)
				task.wait(1)
				gui.Holder.Visible = false
				dialogueDebounce = false
			end
		elseif HasAcceptedYet.Value == true then
			if progressDebounce == true then
				local gui = player.PlayerGui.ConstructionGui
				local sound = player.PlayerGui.HUD.Sidebar.Container.Construction.LocalScript.ClickSound
				gui.Holder:TweenPosition(UDim2.new(0.5,0,1.5,0), 'InOut', 'Elastic', 1)
				EffectEvent:FireClient(player, 0)
				task.wait(0.8)
				progressDebounce = false
			end
		end
	end
end)

(I didn’t test any of this code. Too much of a hassle to recreate! Hopefully it works.)

2 Likes

Thank you! It overall made the system smoother and fixed tweening of the ConstructionGUI (progressDebounce, HasAcceptedYet value true).

However, it still didn’t fix the EventDialogue (dialogueDebounce, HasAcceptedYet value false). It still just doesn’t tween back when you leave the hitReg part.

Though, I think that the coroutine has to do something with that. Because when I removed the coroutine function in the .TouchEnded (.playerExited) event, it successfully tweened it back, but it logically broke the coroutine (didn’t stop the loop as how it did with the coroutine). @VexorgIRL sorry that I keep bothering you about this, but any idea why does that happen? Maybe one of the coroutine functions being placed incorrectly? I don’t really have experience with the coroutine library, so that’s why I can’t fix it on my own.

Nvm actually even the ConstructionGUI doesn’t tween back. So yeah, the coroutine I assume messes up the whole .TouchEnded (.playerExited) event.

I finally managed to fix this. @VexorgIRL I just had to remove the coroutine.yield() function and only keep the coroutine.close() function there.

@VexorgIRL @VerySillySausages Thank you so much for all the help!

2 Likes

So glad you finally got the solution! Only took two weeks xD

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.