Tutorial funnel example
Log each step of a first-time tutorial and see exactly where new players drop off.
This example records four steps of a tutorial. The event names match the built-in Tutorial activation funnel, so you get conversion numbers in Funnels as soon as players run it.
The funnel
| Step | Event | Recorded when | Shown in the dashboard as |
|---|---|---|---|
| 1 | session_started | A player joins (automatic) | Joined game |
| 2 | stage_started | They touch the part with Step 1 | Started tutorial |
| 3 | checkpoint_reached | They touch the part with Step 2 | Reached checkpoint |
| 4 | stage_completed | They touch the part with Step 3 | Completed tutorial |
Set up the parts in Studio
- Add three Parts along your tutorial route, in the order players meet them. Turn on Anchored and turn off CanCollide.
- Open View → Tag Editor and give each part the tag
TutorialStep. - Add a number attribute called
Stepto each part: 1, 2 and 3.
The script
Create a server Script in ServerScriptService. It assumes you followed the Quick start, including the RoStructureSecrets module in ServerStorage.
local CollectionService = game:GetService("CollectionService")
local Players = game:GetService("Players")
local ServerScriptService = game:GetService("ServerScriptService")
local ServerStorage = game:GetService("ServerStorage")
local RoStructure = require(ServerScriptService.RoStructure)
local secrets = require(ServerStorage.RoStructureSecrets)
local analytics = RoStructure.init({
projectId = "my_game",
ingestionKey = secrets.ingestionKey,
buildId = "v" .. game.PlaceVersion,
endpoint = "https://api.rostructure.com",
})
local TUTORIAL_STAGE = "tutorial"
local NEXT_STAGE = "stage_1"
-- Funnel steps after "session_started", which the SDK records when a player joins.
-- The event names match the built-in "Tutorial activation" funnel.
local STEPS = {
{ event = "stage_started", label = "Started tutorial" },
{ event = "checkpoint_reached", label = "Reached checkpoint" },
{ event = "stage_completed", label = "Completed tutorial" },
}
-- The last step each player has completed. 0 means none yet.
local progress: { [Player]: number } = {}
local function playerFromHit(hit: BasePart): Player?
local model = hit:FindFirstAncestorOfClass("Model")
return model and Players:GetPlayerFromCharacter(model) or nil
end
local function complete(player: Player, step: number)
-- The server decides: a step counts once, and only after the step before it.
if progress[player] ~= step - 1 then
return
end
progress[player] = step
local definition = STEPS[step]
analytics:track(player, definition.event, {
stageId = TUTORIAL_STAGE,
position = true,
metadata = { step = step, label = definition.label },
})
if step == #STEPS then
-- Later events, including deaths, now belong to the next stage.
analytics:setStage(player, NEXT_STAGE)
end
end
local function bindStepPart(part: Instance)
if not part:IsA("BasePart") then
return
end
local step = part:GetAttribute("Step")
if type(step) ~= "number" or STEPS[step] == nil then
warn("Tutorial part " .. part:GetFullName() .. " needs a Step attribute from 1 to " .. #STEPS)
return
end
part.Touched:Connect(function(hit)
local player = playerFromHit(hit)
if player then
complete(player, step)
end
end)
end
for _, part in CollectionService:GetTagged("TutorialStep") do
bindStepPart(part)
end
CollectionService:GetInstanceAddedSignal("TutorialStep"):Connect(bindStepPart)
local function onPlayerAdded(player: Player)
progress[player] = 0
-- Deaths and quits before the tutorial is finished are attributed to it.
analytics:setStage(player, TUTORIAL_STAGE)
end
Players.PlayerAdded:Connect(onPlayerAdded)
for _, player in Players:GetPlayers() do
onPlayerAdded(player)
end
Players.PlayerRemoving:Connect(function(player)
progress[player] = nil
end)Read the funnel
- Open Funnels and choose Tutorial activation. Each card shows how many sessions reached that step and how many dropped off before the next one.
- Biggest leak names the step where the most players stop. Click any step to see conversion, the median time from the previous step and which device drops off most.
- In a step's panel choose View drop-offs spatially to open those sessions on the map. Their last recorded positions come from
position = truein the script. - A drop-off means the next step was not reached within the conversion window (30 minutes). It does not mean the player left the game.
How it works
- The server decides.
completeignores a step that was already counted or arrives out of order, so a player cannot skip ahead or count twice. - Stages.
setStageattributes a player's later events, including deaths and quits, to the tutorial until it is finished, then tostage_1. - Cleanup. Per-player state is removed on
PlayerRemoving. - Your own funnel. Keep the event names to match the preset, or use any names and build a funnel with your own steps in the Funnels page.