
KeylessUniversal

--// 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()



w