Advanced Usage¶
Learn advanced patterns and techniques for building sophisticated tutorial systems with SpotlightUI.
Conditional Tutorial Flows¶
Create tutorials that branch based on player choices or game state.
local SpotlightUI = require(ReplicatedStorage.SpotlightUI)
local player = game.Players.LocalPlayer
local function startCombatTutorial()
local combat = SpotlightUI.new()
combat:SetSteps({
{ UI = gui.AttackButton, Text = "Click to attack enemies", Pulse = 10 },
{ UI = gui.DefendButton, Text = "Click to block incoming attacks" },
{ Part = workspace.TrainingDummy, Text = "Practice on this dummy" }
})
combat:Start()
end
local function startBuildingTutorial()
local building = SpotlightUI.new()
building:SetSteps({
{ UI = gui.BuildMenu, Text = "Open the build menu", Pulse = 8 },
{ UI = gui.PlaceButton, Text = "Select and place structures" }
})
building:Start()
end
gui.CombatButton.Activated:Connect(startCombatTutorial)
gui.BuildingButton.Activated:Connect(startBuildingTutorial)
Multi-Phase Tutorials¶
Break complex tutorials into phases that can be triggered independently.
local TutorialManager = {}
TutorialManager.currentPhase = 0
TutorialManager.spotlights = {}
function TutorialManager:StartPhase(phase)
if self.spotlights[self.currentPhase] then
self.spotlights[self.currentPhase]:Destroy()
end
self.currentPhase = phase
local spotlight = SpotlightUI.new()
self.spotlights[phase] = spotlight
if phase == 1 then
spotlight:SetSteps({
{ UI = gui.MovementKeys, Text = "Use WASD to move" },
{ UI = gui.JumpButton, Text = "Press Space to jump" }
})
elseif phase == 2 then
spotlight:SetSteps({
{ Part = workspace.Chest, Text = "Collect items from chests" },
{ UI = gui.Inventory, Text = "View items in your inventory" }
})
elseif phase == 3 then
spotlight:SetSteps({
{ Part = workspace.QuestNPC, Text = "Talk to NPCs for quests" },
{ UI = gui.QuestLog, Text = "Track your active quests here" }
})
end
spotlight.sequenceCompleted:Connect(function()
player:SetAttribute("TutorialPhase" .. phase, true)
end)
spotlight:Start()
end
player:GetAttributeChangedSignal("Level"):Connect(function()
local level = player:GetAttribute("Level")
if level == 5 and not player:GetAttribute("TutorialPhase2") then
TutorialManager:StartPhase(2)
elseif level == 10 and not player:GetAttribute("TutorialPhase3") then
TutorialManager:StartPhase(3)
end
end)
TutorialManager:StartPhase(1)
Interactive Step Validation¶
Ensure players complete actions before advancing through tutorial steps.
local spotlight = SpotlightUI.new()
local currentStepIndex = 0
local stepValidations = {}
stepValidations[1] = function()
return gui.PlayButton.Activated:Wait()
end
stepValidations[2] = function()
return gui.InventoryButton.Activated:Wait()
end
stepValidations[3] = function()
local character = player.Character
local hrp = character and character:FindFirstChild("HumanoidRootPart")
if not hrp then return end
repeat
task.wait(0.5)
until (hrp.Position - workspace.TargetLocation.Position).Magnitude < 10
end
spotlight:SetSteps({
{ UI = gui.PlayButton, Text = "Click the play button", Shape = "Circle", Pulse = 10 },
{ UI = gui.InventoryButton, Text = "Open your inventory", Shape = "Square" },
{ Part = workspace.TargetLocation, Text = "Walk to this location", Shape = "Circle" }
})
spotlight.stepCompleted:Connect(function(stepIndex)
currentStepIndex = stepIndex
if stepValidations[stepIndex] then
task.spawn(function()
stepValidations[stepIndex]()
spotlight:Next()
end)
else
task.wait(2)
spotlight:Next()
end
end)
spotlight:Start()
Context-Aware Tutorials¶
Show different tutorials based on player experience or preferences.
local TutorialSystem = {}
TutorialSystem.hasSeenTutorial = {}
function TutorialSystem:ShouldShowTutorial(tutorialName)
if player:GetAttribute("TutorialsDisabled") then
return false
end
if self.hasSeenTutorial[tutorialName] then
return false
end
local level = player:GetAttribute("Level") or 1
if tutorialName == "Basic" and level > 5 then
return false
end
return true
end
function TutorialSystem:ShowShopTutorial()
if not self:ShouldShowTutorial("Shop") then return end
local spotlight = SpotlightUI.new()
spotlight:SetSteps({
{ UI = gui.ShopButton, Text = "Click to open the shop" },
{ UI = gui.ShopFrame.BuyButton, Text = "Purchase items here" },
{ UI = gui.ShopFrame.SellButton, Text = "Sell unwanted items" }
})
spotlight.sequenceCompleted:Connect(function()
self.hasSeenTutorial["Shop"] = true
player:SetAttribute("SeenShopTutorial", true)
end)
spotlight:Start()
end
gui.ShopButton.Activated:Connect(function()
TutorialSystem:ShowShopTutorial()
end)
Spotlight Hints with Custom Actions¶
Add buttons or interactive elements to spotlight hints.
local spotlight = SpotlightUI.new()
local hint = spotlight._hint
local skipButton = Instance.new("TextButton")
skipButton.Size = UDim2.new(0, 60, 0, 25)
skipButton.Position = UDim2.new(1, -70, 1, -30)
skipButton.Text = "Skip"
skipButton.BackgroundColor3 = Color3.fromRGB(200, 50, 50)
skipButton.TextColor3 = Color3.new(1, 1, 1)
skipButton.BorderSizePixel = 0
skipButton.Parent = hint
local corner = Instance.new("UICorner")
corner.CornerRadius = UDim.new(0, 4)
corner.Parent = skipButton
skipButton.Activated:Connect(function()
spotlight:Skip()
end)
spotlight:SetSteps({
{ UI = gui.Button1, Text = "Step 1 - Click or skip", Pulse = 10 },
{ UI = gui.Button2, Text = "Step 2 - Almost done" }
})
spotlight:Start()
Spotlight State Persistence¶
Save and restore tutorial progress across sessions.
local DataStoreService = game:GetService("DataStoreService")
local TutorialData = DataStoreService:GetDataStore("TutorialProgress")
local TutorialManager = {}
function TutorialManager:SaveProgress(tutorialName, stepIndex)
local success, err = pcall(function()
TutorialData:SetAsync(player.UserId .. "_" .. tutorialName, {
stepIndex = stepIndex,
timestamp = os.time()
})
end)
if not success then
warn("Failed to save tutorial progress:", err)
end
end
function TutorialManager:LoadProgress(tutorialName)
local success, data = pcall(function()
return TutorialData:GetAsync(player.UserId .. "_" .. tutorialName)
end)
if success and data then
return data.stepIndex or 0
end
return 0
end
function TutorialManager:StartResumableTutorial(tutorialName, steps)
local spotlight = SpotlightUI.new()
local savedStep = self:LoadProgress(tutorialName)
spotlight:SetSteps(steps)
spotlight.stepCompleted:Connect(function(stepIndex)
self:SaveProgress(tutorialName, stepIndex)
end)
spotlight.sequenceCompleted:Connect(function()
self:SaveProgress(tutorialName, #steps)
end)
spotlight:Start()
for i = 1, savedStep do
spotlight:Next()
end
end
TutorialManager:StartResumableTutorial("MainQuest", {
{ UI = gui.QuestButton, Text = "Open quests" },
{ Part = workspace.QuestGiver, Text = "Talk to the quest giver" },
{ UI = gui.ObjectiveTracker, Text = "Track your objectives" }
})
Dynamic Spotlight Positioning¶
Adjust spotlight position dynamically based on screen size or orientation.
local spotlight = SpotlightUI.new()
local camera = workspace.CurrentCamera
camera:GetPropertyChangedSignal("ViewportSize"):Connect(function()
local viewportSize = camera.ViewportSize
local isPortrait = viewportSize.Y > viewportSize.X
if isPortrait then
spotlight._hint.Size = UDim2.fromOffset(250, 80)
else
spotlight._hint.Size = UDim2.fromOffset(300, 60)
end
end)
spotlight:SetSteps({
{ UI = gui.MobileButton, Text = "Tap here on mobile" },
{ Part = workspace.Checkpoint, Text = "Navigate to this point" }
})
spotlight:Start()
Combining Multiple Spotlights¶
Layer multiple spotlights for complex scenarios (use carefully to avoid confusion).
local primarySpotlight = SpotlightUI.new()
local secondarySpotlight = SpotlightUI.new()
primarySpotlight
:FocusUI(gui.ObjectiveButton, 20, "Main objective: Click here")
:SetShape("Circle")
:EnablePulse(12)
:Show()
secondarySpotlight
:FocusUI(gui.HintButton, 10, "Optional: Click for a hint")
:SetShape("Square")
:Show()
primarySpotlight.sequenceCompleted:Connect(function()
secondarySpotlight:Destroy()
end)
Warning
Multiple simultaneous spotlights can overwhelm players. Use this pattern sparingly and ensure the primary spotlight is visually distinct.
Performance Optimization¶
Tips for maintaining performance with complex tutorials:
Reuse Spotlight Instances¶
local spotlight = SpotlightUI.new()
for i = 1, 10 do
spotlight:FocusUI(buttons[i], 10, "Click " .. i):Show()
task.wait(2)
end
spotlight:Destroy()
Limit Simultaneous Tracking¶
local spotlight = SpotlightUI.new()
for _, npc in workspace.NPCs:GetChildren() do
spotlight:FollowPart(npc, "Talk to " .. npc.Name):Show()
task.wait(5)
end
Destroy When Done¶
local spotlight = SpotlightUI.new()
spotlight.sequenceCompleted:Connect(function()
task.wait(1)
spotlight:Destroy()
end)
spotlight:SetSteps({...}):Start()
Error Handling¶
Handle edge cases gracefully in production code:
local function SafeStartTutorial(gui, steps)
local success, err = pcall(function()
local spotlight = SpotlightUI.new()
for i, step in steps do
if step.UI and not step.UI.Parent then
warn("Step", i, "references a deleted UI element")
return
end
if step.Part and not step.Part.Parent then
warn("Step", i, "references a deleted Part")
return
end
end
spotlight:SetSteps(steps)
spotlight:Start()
end)
if not success then
warn("Tutorial failed to start:", err)
end
end
SafeStartTutorial(gui, {
{ UI = gui.Button1, Text = "Click here" },
{ Part = workspace.Door, Text = "Go to door" }
})
Integration with Game Systems¶
Quest System Integration¶
local QuestSystem = {}
function QuestSystem:StartQuest(questId)
local questData = self:GetQuestData(questId)
if questData.hasSpotlight and not player:GetAttribute("Quest_" .. questId) then
local spotlight = SpotlightUI.new()
spotlight:SetSteps({
{ Part = questData.npc, Text = "Talk to " .. questData.npcName },
{ World = questData.objective, Radius = 15, Text = questData.objectiveText },
{ Part = questData.npc, Text = "Return to " .. questData.npcName }
})
spotlight.sequenceCompleted:Connect(function()
player:SetAttribute("Quest_" .. questId, true)
end)
spotlight:Start()
end
end
Achievement System¶
local AchievementSpotlight = {}
function AchievementSpotlight:ShowNewAchievement(achievementName)
local spotlight = SpotlightUI.new()
spotlight
:FocusUI(gui.AchievementPopup, 25, "You unlocked: " .. achievementName)
:SetShape("Square")
:EnablePulse(15)
:Show()
task.delay(4, function()
spotlight:Hide()
task.wait(0.5)
spotlight:Destroy()
end)
end
Best Practices Summary¶
- Destroy when finished - Always call
:Destroy()when done with a spotlight - Validate references - Check that UI elements and parts exist before creating steps
- Limit concurrent spotlights - Avoid showing multiple spotlights simultaneously
- Provide skip options - Let players who know the game skip tutorials
- Test on all platforms - Ensure spotlights work on mobile, tablet, and desktop
- Save progress - For long tutorials, save progress to DataStores
- Use appropriate shapes - Circles for focus points, squares for UI elements
- Keep text concise - Hint text should be brief and actionable