Skip to main content

Defining Phases

A phase is a plain Lua table. Pass a table of named phases to RoundKit.new() through the Phases field.

local Phases = {
Lobby = {
Name = "Lobby",
AllowedTransitions = { "Countdown" },
},
}

Transition

Name

The phase name. This should match its key in the Phases table. RoundKit uses it in events and diagnostics.

AllowedTransitions

Defines which phases this phase can transition to.

This can be either:

  • A list of phase names.
  • A function (context) -> {string} that returns the allowed phase names dynamically.
AllowedTransitions = function(context)
if context:GetGlobalMetric("OvertimeEnabled") then
return { "Overtime", "Results" }
end

return { "Results" }
end,

RoundManager:TransitionTo(phaseName) succeeds only if phaseName appears in the resolved transition list.

Duration

An optional duration, in seconds. After it elapses, RoundKit attempts a transition.

  • If CanTransitionTo is defined, Duration controls when transition checks begin.
  • Otherwise, AllowedTransitions must resolve to exactly one phase, which RoundKit enters automatically.

CanTransitionTo

CanTransitionTo = function(context, targetPhase)
if targetPhase == "Countdown" then
return context:GetReadyPlayerCount() >= context.Config.MinPlayers
end
end,

Called once per update for each candidate returned by AllowedTransitions, in order. The first target that returns true becomes the next active phase.

Lifecycle

OnEnter / OnExit

OnEnter = function(context)
print("Entered countdown phase")
end,

OnExit = function(context)
print("Leaving countdown phase")
end,

Called once after entering or leaving the phase.

Use OnEnter to initialize phase-specific state, and OnExit to clean it up.

Connections created with context:Connect(...) during OnEnter are automatically disconnected when the phase exits.

OnUpdate

OnUpdate = function(context, dt)
-- Runs every update while this phase is active.
end,

Called every update while the phase is active. Use it for continuous behavior rather than one-time setup or cleanup.

Win Conditions

EvaluateWinCondition

EvaluateWinConditionInterval

Active = {
Name = "Active",
EvaluateWinCondition = true,
EvaluateWinConditionInterval = 1, -- Default: 1 second
...
},

When EvaluateWinCondition is enabled, RoundKit evaluates the configured win condition every EvaluateWinConditionInterval seconds while the phase is active. If a winner is found, it fires OnWinConditionMet.

See Win Conditions for details.

Runtime Behavior

Error handling

Every phase callback runs inside a protected call:

  • AllowedTransitions (function form)
  • CanTransitionTo
  • OnEnter
  • OnUpdate
  • OnExit

If a callback errors, RoundKit logs the error and stack trace, then continues running. Errors inside phase callbacks do not stop the round.

Update loop

By default, RoundManager updates itself with task.wait(UpdateInterval). UpdateInterval defaults to one second.

To use a custom update source, provide a Driver signal:

RoundKit.new({
Phases = Phases,
InitialPhase = "Lobby",
WinCondition = "LastAlive",
Driver = game:GetService("RunService").Heartbeat,
})

The next section covers Win Conditions, which determine when a round ends and who wins.