Please don't tell me I have to use coroutines

function startRoundGuardMove(whichGuard, guardBlock)
	whichGuard.Humanoid:MoveTo(game.Workspace.firstDestination.Position)
	whichGuard.Humanoid.MoveToFinished:Wait()
	whichGuard.Humanoid:MoveTo(guardBlock.Position)
end
--moving the gunmen and players (call a function for each of the gunmen and then just do a for loop for the players to folow suite)
			startRoundGuardMove(game.Workspace.gunmen1, game.Workspace.guardBlock1)
			--ADD CFRAME.LOOKAT FOR THIS
			wait(1)
			startRoundGuardMove(game.Workspace.gunmen2, game.Workspace.guardBlock2)
			wait(1)

What I was HOPING would happen is that the functions would run as they should, but instead it seems that the function waits for it to complete itself before running the script after it is called…

Did I make a mistake? Am I wrong about this?
If not, how do I make these all run as how I previously thought they would? I’ve heard about coroutines, but they seem complicated. Do I have to use them? Is there a simpler way?

You can use task.spawn() if it’s too complicated for you.

Coroutines aren’t all that complicated. If you just want coroutines to run things parallel, something like this works.

local coro = coroutine.wrap(function(arg1,arg2,etc) -- this acts and is called like a function
    -- code here
end)
coro() -- how you run coroutine.wrap

--------------------------------------------------------------------------------

-- you can also run a corutine.wrap by adding the () right after declaring it like this
coroutine.wrap(function()

end)() -- notice the 2 parenthesis, this will run it immediately after making the coroutine

Or if you want slightly more complicated this works too:

local coro = coroutine.create(function(arg1,arg2,etc) -- creates a coroutine just like wrap but is called differently
    -- code here
end)

local boolean_value = false
local string_value = "this is a string"
local object_in_workspace = workspace.object

coroutine.resume(coro,boolean_value,string_value,object_in_workspace)
-- when calling resume, the coroutine created always comes first, then the arguments after

--------------------------------------------------------------------------------

-- and as the same as wrap, you can call it without a variable (we call these anonymous coroutines/functions)
coroutine.resume(coroutine.create(function()
     -- code here
end))

There’s a page on the DevHub about coroutines here.

6 Likes

and how do i use task.spawn()…?

so if i were to put this kinda thing in a for loop, looping through players, it would make a new coroutine for every player and therefore make them all walk together instead of waiting for the first player to reach their goal?

also, does this mean that if i set it up to be a coroutine like this, whenever i call it, is it LITERALLY just a function that doesnt have to be waited on for the rest of the script to run?

If you wanted to, you could simply use Connect instead of Wait and disconnect it afterwards, removing the need for a coroutine.

function startRoundGuardMove(whichGuard, guardBlock)
	local MoveConn = nil
	whichGuard.Humanoid:MoveTo(game.Workspace.firstDestination.Position)
	
	MoveConn = whichGuard.Humanoid.MoveToFinished:Connect(function()
		MoveConn:Disconnect()
		whichGuard.Humanoid:MoveTo(guardBlock.Position)
    end)
end

Some clarifications:

  1. coroutines do not run things in parallel, it is still single threaded.

  2. Coroutines have been replaced in favour of the task library. This is because task has full debugging information compared to coroutines which do not. Also unlike spawn task library has the ability to not have a delay when booting up a new routine. Such as task.spawn

if you find coroutines too hard then you can start off with spawn(function(), even though its really bad, it just makes it so you can run code at the same time as other code within the script

spawn(function()
-- loop or something
end)
-- loop here

both loops will run

Basically, the first parameter is a function. task.spawn runs the function without yielding. You can add some arguments to the first parameter’s function with the next parameters. For example, in your case:

task.spawn(startRoundGuardMove, game.Workspace.gunmen1, game.Workspace.guardBlock1)

People also often like to use it like this:

task.spawn(function()
    startRoundGuardMove(game.Workspace.gunmen1, game.Workspace.guardBlock1)
end)

The reason your code does this is because it “yields”. This means your code doesn’t run basically instantly, because you have something that waits. In your case, RBXScriptSignal:Wait() yields.


If you’re ever wondering how to use a function, the API on DevHub is an amazing resource :+1:

  1. I’m aware of that, but it’s still a good way to run 2 things simultaneously (such as 2 loops without the second one being stopped because of the first one)

  2. I still use coroutines since I don’t need such information but this is good to know I guess.

You can just use spawn() instead

I fail to understand why people keep recommending X coroutine, when the solution I gave earlier removes the need for one completely. You should avoid coroutines when they’re not needed – this’s an example of such.

Unless your code’s written in a way that it requires a coroutine, the need for one isn’t necessary, such as with the Connect/Disconnect code I supplied.

An example off the top of my head for where one would be required would be with the player’s PlayerGui/when displaying a message to multiple users-

local function AlertPlayer(NewPlayer, Message)
    local PlayerGui = NewPlayer:WaitForChild("PlayerGui")
    local NewMessage = Instance.new("Message", PlayerGui) -- I'm aware that having it be in the Workspace has the same result, but for the sake of argument I'm using the `PlayerGui`
    wait(#Message/19+2.5)
    NewMessage:Destroy()
end

game.Players.PlayedAdded:Connect(function(NewPlayer)
    AlertPlayer(NewPlayer, "Welcome to the game!")
end)
for _, Player in ipairs(game.Players:GetPlayers()) do
    coroutine.wrap(AlertPlayer)(Player, "Welcome to the game! This script has just began running.")
end

can you explain what disconnecting and connecting a function even does

this is it
t h i s i s i t
this is the “there has to be a simpler way”
thank you kind sir you have just made my day
take your solution good man

1 Like

When you connect a function to an event, the value(s) provided by the event (i.e. PlayerAdded provides the player that joined) are passed to the function connected to it.

game.Players.PlayerAdded:Connect(function(NewPlayer)
    print(NewPlayer.Name, "joined the game!")
end)

When an event is Connected, it returns a RBXScriptConnection (roblox.com). A RBXScriptConnection has a Connected boolean property to it and a Disconnect method for it. The former determines whether the connection is still Connected whilst the latter Disconnects the connection.

local TouchedConn = nil
TouchedConn = BasePart.Touched:Connect(function(Hit)
    print("Touched connection is still connected:", TouchedConn.Connected) -- touched connection is still connected: true
    TouchedConn:Disconnect()
    print("Touched connection is still connected:", TouchedConn.Connected) -- touched connection is still connected: false
    print("The last thing to step on me was", Hit.Name, "!")
    print("I only outputted once!")
end)

The solution I supplied earlier utilizes this. Unlike Connection:Wait() which yields the thread, Connect does not and allows the flow of the thread to continue. In the code I supplied, after MoveToFinished fires, it Disconnects it and runs the MoveTo code, allows the code to do what you want yet having the flow of the entire script to continue.

Using Connection:Wait():

local function NewPart()
    local Part = Instance.new("Part", game.Workspace)
    Part.Touched:Wait()
    print("I outputted only once!")
end

NewPart()
*player steps on*
-- I outputted only once!
NewPart()
-- etc
NewPart()
-- etc

-- And to have it output all at once:
coroutine.wrap(NewPart)()
coroutine.wrap(NewPart)()
coroutine.wrap(NewPart)()
-- I outputted only once!
-- I outputted only once!
-- I outputted only once!

Utilizing Connection:Disconnect:

local function NewPart()
    local TouchedConn = nil
    local Part = Instance.new("Part", game.Workspace)
    TouchedConn = Part.Touched:Connect(function()
        TouchedConn:Disconnect()
        print("I outputted only once!")
    end)
end

NewPart()
NewPart()
NewPart()
-- I outputted only once!
-- I outputted only once!
-- I outputted only once!

-- Doesn't require the need of a coroutine since they aren't yielding the thread/script

Needless to say, the latter is the solution I recommended and what would happen.

1 Like