--[[ ButtonTourTeleport (LocalScript) Place in StarterGui or StarterPlayerScripts. Creates a small GUI with a "Start Tour" button. When pressed, it teleports the player's character to parts named _Button1, _Button2, ..., _Button20 (waiting 1 second between each), then finally teleports to a part named ExitRegion. Parts are searched for in workspace. Adjust FindPart() below if your button parts live in a specific folder/model instead of directly in workspace. ]] local Players = game:GetService("Players") local Workspace = game:GetService("Workspace") local player = Players.LocalPlayer local playerGui = player:WaitForChild("PlayerGui") ---------------------------------------------------------------- -- CONFIG ---------------------------------------------------------------- local NUM_BUTTONS = 20 local WAIT_TIME = 1 -- seconds between each teleport local EXIT_PART_NAME = "ExitRegion" ---------------------------------------------------------------- -- GUI CREATION ---------------------------------------------------------------- local screenGui = Instance.new("ScreenGui") screenGui.Name = "ButtonTourGui" screenGui.ResetOnSpawn = false screenGui.Parent = playerGui local frame = Instance.new("Frame") frame.Name = "MainFrame" frame.Size = UDim2.new(0, 220, 0, 130) frame.Position = UDim2.new(0, 20, 0.5, -65) frame.BackgroundColor3 = Color3.fromRGB(30, 30, 30) frame.BorderSizePixel = 0 frame.Parent = screenGui local corner = Instance.new("UICorner") corner.CornerRadius = UDim.new(0, 8) corner.Parent = frame local statusLabel = Instance.new("TextLabel") statusLabel.Name = "StatusLabel" statusLabel.Size = UDim2.new(1, -20, 0, 24) statusLabel.Position = UDim2.new(0, 10, 0, 6) statusLabel.BackgroundTransparency = 1 statusLabel.Text = "Ready" statusLabel.TextColor3 = Color3.fromRGB(255, 255, 255) statusLabel.Font = Enum.Font.GothamBold statusLabel.TextSize = 16 statusLabel.Parent = frame local startButton = Instance.new("TextButton") startButton.Name = "StartButton" startButton.Size = UDim2.new(1, -20, 0, 40) startButton.Position = UDim2.new(0, 10, 0, 36) startButton.BackgroundColor3 = Color3.fromRGB(0, 140, 255) startButton.Text = "Start Tour" startButton.TextColor3 = Color3.fromRGB(255, 255, 255) startButton.Font = Enum.Font.GothamBold startButton.TextSize = 18 startButton.AutoButtonColor = true startButton.Parent = frame local buttonCorner = Instance.new("UICorner") buttonCorner.CornerRadius = UDim.new(0, 6) buttonCorner.Parent = startButton local restartButton = Instance.new("TextButton") restartButton.Name = "RestartButton" restartButton.Size = UDim2.new(1, -20, 0, 40) restartButton.Position = UDim2.new(0, 10, 0, 82) restartButton.BackgroundColor3 = Color3.fromRGB(255, 90, 40) restartButton.Text = "Restart" restartButton.TextColor3 = Color3.fromRGB(255, 255, 255) restartButton.Font = Enum.Font.GothamBold restartButton.TextSize = 18 restartButton.AutoButtonColor = true restartButton.Parent = frame local restartButtonCorner = Instance.new("UICorner") restartButtonCorner.CornerRadius = UDim.new(0, 6) restartButtonCorner.Parent = restartButton ---------------------------------------------------------------- -- HELPERS ---------------------------------------------------------------- local currentRunId = 0 -- incremented each time a (re)start happens; used to cancel stale runs local function findPart(name) -- Searches the whole workspace for a part/model with this name. local inst = Workspace:FindFirstChild(name, true) return inst end local function findHitbox(buttonInst) -- Given a _ButtonN instance (likely a Model), find its "Hitbox" child part. -- Falls back to the instance itself if it has no Hitbox child. if not buttonInst then return nil end local hitbox = buttonInst:FindFirstChild("Hitbox", true) if hitbox and hitbox:IsA("BasePart") then return hitbox end return buttonInst end local function teleportCharacterTo(inst) local character = player.Character or player.CharacterAdded:Wait() local hrp = character:FindFirstChild("HumanoidRootPart") if not hrp then return false end if not inst or not inst:IsA("BasePart") then return false end -- Position on the part's top face, then nudge 0.5 studs down into it -- (in the part's own local space, so this works even if it's rotated). local topOffset = (inst.Size.Y / 2) - 0.5 local targetCFrame = inst.CFrame * CFrame.new(0, topOffset, 0) hrp.CFrame = targetCFrame return true end ---------------------------------------------------------------- -- MAIN TOUR LOGIC ---------------------------------------------------------------- local function runTour() currentRunId = currentRunId + 1 local myRunId = currentRunId startButton.Active = false startButton.AutoButtonColor = false startButton.BackgroundColor3 = Color3.fromRGB(90, 90, 90) for i = 1, NUM_BUTTONS do if myRunId ~= currentRunId then return -- a newer run (restart) has taken over; stop this one end local partName = "_Button" .. i statusLabel.Text = "Going to " .. partName local part = findPart(partName) if part then local hitbox = findHitbox(part) teleportCharacterTo(hitbox) else warn("[ButtonTourTeleport] Could not find part named: " .. partName) statusLabel.Text = "Missing: " .. partName end task.wait(WAIT_TIME) end if myRunId ~= currentRunId then return end statusLabel.Text = "Going to " .. EXIT_PART_NAME local exitPart = findPart(EXIT_PART_NAME) if exitPart then local exitTarget = exitPart if not exitPart:IsA("BasePart") then -- ExitRegion might be a Model; grab its PrimaryPart or first BasePart exitTarget = exitPart.PrimaryPart or exitPart:FindFirstChildWhichIsA("BasePart", true) end teleportCharacterTo(exitTarget) else warn("[ButtonTourTeleport] Could not find part named: " .. EXIT_PART_NAME) statusLabel.Text = "Missing: " .. EXIT_PART_NAME end if myRunId ~= currentRunId then return end statusLabel.Text = "Done!" task.wait(1) if myRunId == currentRunId then statusLabel.Text = "Ready" startButton.Active = true startButton.AutoButtonColor = true startButton.BackgroundColor3 = Color3.fromRGB(0, 140, 255) end end startButton.MouseButton1Click:Connect(function() task.spawn(runTour) end) restartButton.MouseButton1Click:Connect(function() -- Bumping currentRunId cancels any in-progress run on its next check, -- then we immediately kick off a fresh one. task.spawn(runTour) end)