--// Infinite Waypoint Tween Looper (Client-sided) --// LocalScript: StarterPlayerScripts --// Add unlimited waypoints, then loop tween through them. local Players = game:GetService("Players") local TweenService = game:GetService("TweenService") local CoreGui = game:GetService("CoreGui") local player = Players.LocalPlayer -- ========================= -- CONFIG (easy to adjust) -- ========================= local GUI_NAME = "InfiniteWaypointTweenLooperGui" -- Choose ONE timing mode: local USE_SPEED_MODE = true -- true: uses STUDS_PER_SECOND, false: uses FIXED_TWEEN_TIME local STUDS_PER_SECOND = 30 -- movement speed when USE_SPEED_MODE = true local FIXED_TWEEN_TIME = 1.0 -- seconds per segment when USE_SPEED_MODE = false local MIN_TWEEN_TIME = 0.15 -- clamps duration local MAX_TWEEN_TIME = 6.0 local EASING_STYLE = Enum.EasingStyle.Sine local EASING_DIR = Enum.EasingDirection.InOut local WAIT_AT_POINT = 0.05 -- pause at each waypoint local CONTROL_AUTOROTATE = true -- smoother look while tweening -- ========================= -- cleanup old gui -- ========================= do local old = CoreGui:FindFirstChild(GUI_NAME) if old then old:Destroy() end end -- ========================= -- helpers -- ========================= local function getCharacter() return player.Character or player.CharacterAdded:Wait() end local function getRootPart(char) if not char then return nil end return char:FindFirstChild("HumanoidRootPart") or char.PrimaryPart end local function getHumanoid(char) return char and char:FindFirstChildOfClass("Humanoid") or nil end local function clamp(x, a, b) if x < a then return a end if x > b then return b end return x end local function computeTweenTime(fromPos: Vector3, toPos: Vector3) if not USE_SPEED_MODE then return clamp(FIXED_TWEEN_TIME, MIN_TWEEN_TIME, MAX_TWEEN_TIME) end local dist = (toPos - fromPos).Magnitude local t = dist / math.max(STUDS_PER_SECOND, 0.01) return clamp(t, MIN_TWEEN_TIME, MAX_TWEEN_TIME) end local function makeDraggable(frame, handle) handle = handle or frame local dragging = false local dragStart, startPos local function update(input) local delta = input.Position - dragStart frame.Position = UDim2.new( startPos.X.Scale, startPos.X.Offset + delta.X, startPos.Y.Scale, startPos.Y.Offset + delta.Y ) end handle.InputBegan:Connect(function(input) if input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch then dragging = true dragStart = input.Position startPos = frame.Position input.Changed:Connect(function() if input.UserInputState == Enum.UserInputState.End then dragging = false end end) end end) handle.InputChanged:Connect(function(input) if (input.UserInputType == Enum.UserInputType.MouseMovement or input.UserInputType == Enum.UserInputType.Touch) and dragging then update(input) end end) end -- ========================= -- GUI -- ========================= local gui = Instance.new("ScreenGui") gui.Name = GUI_NAME gui.ResetOnSpawn = false gui.Parent = CoreGui local main = Instance.new("Frame") main.Name = "Main" main.Size = UDim2.new(0, 320, 0, 320) main.Position = UDim2.new(0, 20, 0, 120) main.BackgroundColor3 = Color3.fromRGB(20, 20, 20) main.BorderSizePixel = 0 main.Parent = gui local corner = Instance.new("UICorner") corner.CornerRadius = UDim.new(0, 10) corner.Parent = main local header = Instance.new("TextButton") header.Name = "Header" header.Size = UDim2.new(1, 0, 0, 32) header.BackgroundColor3 = Color3.fromRGB(30, 30, 30) header.BorderSizePixel = 0 header.Text = "Infinite Waypoint Tween Looper" header.Font = Enum.Font.GothamBold header.TextSize = 14 header.TextColor3 = Color3.fromRGB(235, 235, 235) header.AutoButtonColor = false header.Parent = main local headerCorner = Instance.new("UICorner") headerCorner.CornerRadius = UDim.new(0, 10) headerCorner.Parent = header local headerMask = Instance.new("Frame") headerMask.Size = UDim2.new(1, 0, 0, 10) headerMask.Position = UDim2.new(0, 0, 1, -10) headerMask.BackgroundColor3 = header.BackgroundColor3 headerMask.BorderSizePixel = 0 headerMask.Parent = header local function makeBtn(name, text, posY, height) height = height or 34 local b = Instance.new("TextButton") b.Name = name b.Size = UDim2.new(1, -20, 0, height) b.Position = UDim2.new(0, 10, 0, posY) b.BackgroundColor3 = Color3.fromRGB(45, 45, 45) b.BorderSizePixel = 0 b.Text = text b.Font = Enum.Font.Gotham b.TextSize = 14 b.TextColor3 = Color3.fromRGB(240, 240, 240) b.Parent = main local c = Instance.new("UICorner") c.CornerRadius = UDim.new(0, 8) c.Parent = b return b end local btnAdd = makeBtn("Add", "Add Waypoint (Current Position)", 44) local btnRemove = makeBtn("RemoveLast", "Remove Last Waypoint", 84) local btnClear = makeBtn("ClearAll", "Clear All Waypoints", 124) local btnLoop = makeBtn("ToggleLoop", "Enable Loop: OFF", 164) local listLabel = Instance.new("TextLabel") listLabel.Size = UDim2.new(1, -20, 0, 18) listLabel.Position = UDim2.new(0, 10, 0, 206) listLabel.BackgroundTransparency = 1 listLabel.Text = "Waypoints:" listLabel.Font = Enum.Font.GothamBold listLabel.TextSize = 12 listLabel.TextColor3 = Color3.fromRGB(200, 200, 200) listLabel.TextXAlignment = Enum.TextXAlignment.Left listLabel.Parent = main local list = Instance.new("ScrollingFrame") list.Name = "WaypointList" list.Size = UDim2.new(1, -20, 1, -232) list.Position = UDim2.new(0, 10, 0, 228) list.BackgroundColor3 = Color3.fromRGB(26, 26, 26) list.BorderSizePixel = 0 list.ScrollBarThickness = 6 list.CanvasSize = UDim2.new(0, 0, 0, 0) list.Parent = main local listCorner = Instance.new("UICorner") listCorner.CornerRadius = UDim.new(0, 8) listCorner.Parent = list local layout = Instance.new("UIListLayout") layout.Padding = UDim.new(0, 6) layout.SortOrder = Enum.SortOrder.LayoutOrder layout.Parent = list local pad = Instance.new("UIPadding") pad.PaddingTop = UDim.new(0, 8) pad.PaddingBottom = UDim.new(0, 8) pad.PaddingLeft = UDim.new(0, 8) pad.PaddingRight = UDim.new(0, 8) pad.Parent = list makeDraggable(main, header) -- ========================= -- Data + UI rendering -- ========================= local waypoints = {} :: {CFrame} local loopEnabled = false local loopToken = 0 local function setLoopUi(on) btnLoop.Text = on and "Enable Loop: ON" or "Enable Loop: OFF" btnLoop.BackgroundColor3 = on and Color3.fromRGB(60, 110, 60) or Color3.fromRGB(45, 45, 45) end local function clearWaypointListUi() for _, child in ipairs(list:GetChildren()) do if child:IsA("Frame") then child:Destroy() end end end local function addWaypointRow(i: number, cf: CFrame) local row = Instance.new("Frame") row.Size = UDim2.new(1, 0, 0, 26) row.BackgroundColor3 = Color3.fromRGB(38, 38, 38) row.BorderSizePixel = 0 row.LayoutOrder = i row.Parent = list local rc = Instance.new("UICorner") rc.CornerRadius = UDim.new(0, 6) rc.Parent = row local txt = Instance.new("TextLabel") txt.BackgroundTransparency = 1 txt.Size = UDim2.new(1, -10, 1, 0) txt.Position = UDim2.new(0, 8, 0, 0) txt.Font = Enum.Font.Gotham txt.TextSize = 12 txt.TextColor3 = Color3.fromRGB(230, 230, 230) txt.TextXAlignment = Enum.TextXAlignment.Left local p = cf.Position txt.Text = string.format("#%d (%.1f, %.1f, %.1f)", i, p.X, p.Y, p.Z) txt.Parent = row end local function refreshWaypointUi() clearWaypointListUi() for i, cf in ipairs(waypoints) do addWaypointRow(i, cf) end -- update canvas size task.defer(function() local total = layout.AbsoluteContentSize.Y + 16 list.CanvasSize = UDim2.new(0, 0, 0, total) end) end -- ========================= -- Tween Loop -- ========================= local function tweenRootTo(root: BasePart, target: CFrame) local t = computeTweenTime(root.Position, target.Position) local ti = TweenInfo.new(t, EASING_STYLE, EASING_DIR) local tw = TweenService:Create(root, ti, {CFrame = target}) tw:Play() return tw end local function runLoop(myToken: number) local savedAutoRotate: boolean? = nil while loopEnabled and loopToken == myToken do -- Need at least 2 waypoints to loop if #waypoints < 2 then task.wait(0.1) continue end local char = player.Character local root = getRootPart(char) local hum = getHumanoid(char) if not (char and root) then task.wait(0.1) continue end if CONTROL_AUTOROTATE and hum then savedAutoRotate = hum.AutoRotate hum.AutoRotate = false end -- Loop through in order, forever for i = 1, #waypoints do if not (loopEnabled and loopToken == myToken) then break end -- If waypoints changed during the loop, clamp i safely if i > #waypoints then break end local target = waypoints[i] local tw = tweenRootTo(root, target) tw.Completed:Wait() if not (loopEnabled and loopToken == myToken) then break end task.wait(WAIT_AT_POINT) -- Refresh root if character changed mid-loop char = player.Character root = getRootPart(char) hum = getHumanoid(char) if not (char and root) then break end end end -- restore autorotate if possible local char = player.Character local hum = getHumanoid(char) if CONTROL_AUTOROTATE and hum and savedAutoRotate ~= nil then hum.AutoRotate = savedAutoRotate end end -- ========================= -- Button wiring -- ========================= btnAdd.MouseButton1Click:Connect(function() local char = getCharacter() local root = getRootPart(char) if not root then return end table.insert(waypoints, root.CFrame) refreshWaypointUi() end) btnRemove.MouseButton1Click:Connect(function() if #waypoints > 0 then table.remove(waypoints, #waypoints) refreshWaypointUi() end end) btnClear.MouseButton1Click:Connect(function() table.clear(waypoints) refreshWaypointUi() end) btnLoop.MouseButton1Click:Connect(function() loopEnabled = not loopEnabled setLoopUi(loopEnabled) loopToken += 1 local myToken = loopToken if loopEnabled then task.spawn(function() runLoop(myToken) end) end end) setLoopUi(false) refreshWaypointUi()