-- Fly GUI v4 by EricBerpa | Standalone client controller for Roblox R6 and R15. -- Install as a LocalScript in StarterPlayerScripts, or use the optional loader -- in a client environment that explicitly provides loadstring and game:HttpGet. local Players = game:GetService("Players") local RunService = game:GetService("RunService") local UserInputService = game:GetService("UserInputService") local Workspace = game:GetService("Workspace") local player = Players.LocalPlayer assert(player, "Fly GUI must run on the client, not the server.") local playerGui = player:WaitForChild("PlayerGui", 10) assert(playerGui, "Fly GUI could not find PlayerGui.") local CONFIG = { DefaultSpeed = 50, MinSpeed = 10, MaxSpeed = 200, SpeedStep = 10, Smoothing = 12, ToggleKey = Enum.KeyCode.F, MinimizeKey = Enum.KeyCode.RightShift, } local GUI_NAME = "FlyGuiV4" local CLOSE_EVENT = "Shutdown" -- Shut down the previous controller before replacing its UI and constraints. local previous = playerGui:FindFirstChild(GUI_NAME) if previous then local shutdown = previous:FindFirstChild(CLOSE_EVENT) if shutdown and shutdown:IsA("BindableFunction") then pcall(function() shutdown:Invoke() end) end previous:Destroy() end local palette = { Background = Color3.fromRGB(17, 23, 35), Surface = Color3.fromRGB(29, 38, 53), Border = Color3.fromRGB(53, 66, 86), Accent = Color3.fromRGB(94, 234, 212), Text = Color3.fromRGB(238, 244, 252), Muted = Color3.fromRGB(161, 177, 198), Danger = Color3.fromRGB(251, 113, 133), } local connections = {} local flight = nil local destroyed = false local minimized = false local speed = CONFIG.DefaultSpeed local keys = {} local holds = {} local drag = nil local inputFocused = true local controller = {} local function connect(signal, callback) local connection = signal:Connect(callback) table.insert(connections, connection) return connection end local function create(className, properties, parent) local instance = Instance.new(className) for key, value in pairs(properties) do instance[key] = value end instance.Parent = parent return instance end local function round(instance, radius) create("UICorner", { CornerRadius = UDim.new(0, radius or 10) }, instance) end local gui = create("ScreenGui", { Name = GUI_NAME, ResetOnSpawn = false, ZIndexBehavior = Enum.ZIndexBehavior.Sibling, DisplayOrder = 20, }, playerGui) local shutdown = create("BindableFunction", { Name = CLOSE_EVENT }, gui) local canvas = create("Frame", { Name = "Canvas", Size = UDim2.fromScale(1, 1), BackgroundTransparency = 1, BorderSizePixel = 0, Active = false, }, gui) local panel = create("Frame", { Name = "Panel", Position = UDim2.fromOffset(24, 120), Size = UDim2.fromOffset(300, 290), BackgroundColor3 = palette.Background, BorderSizePixel = 0, ClipsDescendants = true, Active = true, }, canvas) round(panel, 14) create("UIStroke", { Color = palette.Border, Thickness = 1 }, panel) local function label(parent, name, text, position, size, textSize, color) return create("TextLabel", { Name = name, Position = position, Size = size, BackgroundTransparency = 1, Font = Enum.Font.Gotham, Text = text, TextSize = textSize or 14, TextColor3 = color or palette.Text, TextXAlignment = Enum.TextXAlignment.Left, }, parent) end local function button(parent, name, text, position, size) local result = create("TextButton", { Name = name, Position = position, Size = size, BackgroundColor3 = palette.Surface, BorderSizePixel = 0, AutoButtonColor = true, Font = Enum.Font.GothamMedium, Text = text, TextSize = 14, TextColor3 = palette.Text, }, parent) round(result, 8) return result end local header = create("Frame", { Name = "Header", Size = UDim2.new(1, -90, 0, 44), BackgroundTransparency = 1, Active = true, }, panel) label(header, "Title", "FLY / EricBerpa", UDim2.fromOffset(16, 0), UDim2.new(1, -16, 1, 0), 16) local minimizeButton = button(panel, "Minimize", "-", UDim2.new(1, -78, 0, 8), UDim2.fromOffset(30, 28)) local closeButton = button(panel, "Close", "X", UDim2.new(1, -42, 0, 8), UDim2.fromOffset(30, 28)) closeButton.TextColor3 = palette.Danger local body = create("Frame", { Name = "Body", Position = UDim2.fromOffset(14, 48), Size = UDim2.new(1, -28, 0, 228), BackgroundTransparency = 1, }, panel) local statusLabel = label(body, "Status", "READY", UDim2.fromOffset(0, 0), UDim2.new(1, 0, 0, 20), 12, palette.Muted) local toggleButton = button(body, "ToggleFlight", "START FLYING [F]", UDim2.fromOffset(0, 28), UDim2.new(1, 0, 0, 42)) label(body, "SpeedTitle", "SPEED", UDim2.fromOffset(0, 82), UDim2.fromOffset(76, 16), 11, palette.Muted) local minusButton = button(body, "DecreaseSpeed", "-", UDim2.fromOffset(0, 104), UDim2.fromOffset(42, 36)) local speedLabel = label(body, "SpeedValue", "", UDim2.fromOffset(48, 104), UDim2.new(1, -96, 0, 36), 16) speedLabel.TextXAlignment = Enum.TextXAlignment.Center local plusButton = button(body, "IncreaseSpeed", "+", UDim2.new(1, -42, 0, 104), UDim2.fromOffset(42, 36)) local upButton = button(body, "Ascend", "UP / E", UDim2.fromOffset(0, 150), UDim2.new(0.5, -5, 0, 38)) local downButton = button(body, "Descend", "DOWN / Q", UDim2.new(0.5, 5, 0, 150), UDim2.new(0.5, -5, 0, 38)) local help = label(body, "Help", "WASD / joystick to move. Hold UP / DOWN.\nRight Shift: collapse. Drag the header to move.", UDim2.fromOffset(0, 199), UDim2.new(1, 0, 0, 30), 10, palette.Muted) help.TextWrapped = true local function refresh(message) if destroyed then return end statusLabel.Text = message or (flight and "FLYING" or "READY") statusLabel.TextColor3 = flight and palette.Accent or palette.Muted toggleButton.Text = flight and "STOP FLYING [F]" or "START FLYING [F]" toggleButton.BackgroundColor3 = flight and palette.Accent or palette.Surface toggleButton.TextColor3 = flight and palette.Background or palette.Text speedLabel.Text = tostring(speed) .. " studs/s" minusButton.TextTransparency = speed <= CONFIG.MinSpeed and 0.6 or 0 plusButton.TextTransparency = speed >= CONFIG.MaxSpeed and 0.6 or 0 end local function clearInput() table.clear(keys) table.clear(holds) drag = nil end local function stopFlight(message) local active = flight flight = nil clearInput() if active then for _, connection in ipairs(active.Connections) do connection:Disconnect() end for _, instance in ipairs(active.Instances) do instance:Destroy() end local humanoid = active.Humanoid if humanoid.Parent then humanoid.AutoRotate = active.AutoRotate humanoid.PlatformStand = active.PlatformStand if humanoid.Health > 0 and not active.PlatformStand then humanoid:ChangeState(Enum.HumanoidStateType.GettingUp) end end if active.Root.Parent then active.Root.AssemblyLinearVelocity = Vector3.zero active.Root.AssemblyAngularVelocity = Vector3.zero end end refresh(message) end local function flatUnit(vector, fallback) local flat = Vector3.new(vector.X, 0, vector.Z) return flat.Magnitude > 0.001 and flat.Unit or fallback end local function movementDirection(active) if not inputFocused or UserInputService:GetFocusedTextBox() then return Vector3.zero end local camera = Workspace.CurrentCamera local x = (keys[Enum.KeyCode.D] and 1 or 0) - (keys[Enum.KeyCode.A] and 1 or 0) local z = (keys[Enum.KeyCode.W] and 1 or 0) - (keys[Enum.KeyCode.S] and 1 or 0) local direction if camera and (x ~= 0 or z ~= 0) then local right = flatUnit(camera.CFrame.RightVector, Vector3.xAxis) -- RightVector stays horizontal for a normally controlled camera, -- so this remains well defined when looking straight up/down. local forward = Vector3.yAxis:Cross(right) direction = right * x + forward * z else local move = active.Humanoid.MoveDirection direction = Vector3.new(move.X, 0, move.Z) end local up = keys[Enum.KeyCode.E] or keys[Enum.KeyCode.Space] local down = keys[Enum.KeyCode.Q] or keys[Enum.KeyCode.LeftControl] for _, value in pairs(holds) do if value > 0 then up = true else down = true end end direction += Vector3.new(0, (up and 1 or 0) - (down and 1 or 0), 0) -- Preserve joystick strength, while keeping diagonal movement capped. return direction.Magnitude > 1 and direction.Unit or direction end function controller:SetFlying(enabled) if destroyed then return false end if not enabled then stopFlight() return true end if flight then return true end local character = player.Character local humanoid = character and character:FindFirstChildOfClass("Humanoid") local root = character and character:FindFirstChild("HumanoidRootPart") if not humanoid or not root or not root:IsA("BasePart") or humanoid.Health <= 0 then refresh("WAITING FOR A LIVING CHARACTER") return false end if root.Anchored or humanoid.Sit then refresh(root.Anchored and "CHARACTER IS ANCHORED" or "LEAVE YOUR SEAT FIRST") return false end clearInput() local active = { Character = character, Humanoid = humanoid, Root = root, AutoRotate = humanoid.AutoRotate, PlatformStand = humanoid.PlatformStand, Instances = {}, Connections = {}, Velocity = Vector3.zero, } flight = active local ok, err = pcall(function() -- Register every object before assigning properties so a failed setup -- can always remove the objects it created. local function owned(className) local instance = Instance.new(className) table.insert(active.Instances, instance) return instance end local attachment = owned("Attachment") attachment.Name = "FlyGuiAttachment" attachment.Parent = root local velocity = owned("LinearVelocity") velocity.Name = "FlyGuiVelocity" velocity.Attachment0 = attachment velocity.RelativeTo = Enum.ActuatorRelativeTo.World velocity.VelocityConstraintMode = Enum.VelocityConstraintMode.Vector velocity.ForceLimitsEnabled = false velocity.VectorVelocity = Vector3.zero velocity.Parent = root local orientation = owned("AlignOrientation") orientation.Name = "FlyGuiOrientation" orientation.Attachment0 = attachment orientation.Mode = Enum.OrientationAlignmentMode.OneAttachment orientation.MaxTorque = 1000000 orientation.MaxAngularVelocity = 100 orientation.Responsiveness = 25 orientation.CFrame = root.CFrame.Rotation orientation.Parent = root humanoid.AutoRotate = false humanoid.PlatformStand = true humanoid:ChangeState(Enum.HumanoidStateType.Physics) table.insert(active.Connections, humanoid.Died:Connect(function() stopFlight("RESPAWN TO FLY AGAIN") end)) -- One physics update, independent of speed and frame rate. table.insert(active.Connections, RunService.PreSimulation:Connect(function(dt) if flight ~= active then return end if player.Character ~= character or not root.Parent or not humanoid.Parent or humanoid.Health <= 0 or root.Anchored or humanoid.Sit or not attachment.Parent or not velocity.Parent or not orientation.Parent then stopFlight("FLIGHT STOPPED") return end local target = movementDirection(active) * speed local alpha = 1 - math.exp(-CONFIG.Smoothing * math.max(dt, 0)) active.Velocity = active.Velocity:Lerp(target, alpha) velocity.VectorVelocity = active.Velocity local camera = Workspace.CurrentCamera if camera then local facing = flatUnit(camera.CFrame.LookVector, flatUnit(root.CFrame.LookVector, Vector3.new(0, 0, -1))) orientation.CFrame = CFrame.lookAt(Vector3.zero, facing) end end)) end) if not ok then stopFlight("COULD NOT START FLIGHT") warn("Fly GUI: " .. tostring(err)) return false end refresh() return true end function controller:Toggle() return self:SetFlying(flight == nil) end function controller:SetSpeed(value) if destroyed or type(value) ~= "number" or value ~= value or value == math.huge or value == -math.huge then return false end speed = math.clamp(math.floor(value + 0.5), CONFIG.MinSpeed, CONFIG.MaxSpeed) refresh() return true end local function clampPanel() if destroyed then return end local available = canvas.AbsoluteSize if available.X <= 0 or available.Y <= 0 then return end local width = math.min(300, math.max(1, available.X - 16)) panel.Size = UDim2.fromOffset(width, minimized and 44 or 290) panel.Position = UDim2.fromOffset( math.clamp(panel.Position.X.Offset, 0, math.max(0, available.X - width)), math.clamp(panel.Position.Y.Offset, 0, math.max(0, available.Y - panel.Size.Y.Offset)) ) end function controller:SetMinimized(value) if destroyed then return end minimized = value == true table.clear(holds) body.Visible = not minimized minimizeButton.Text = minimized and "+" or "-" clampPanel() end function controller:GetState() return { Flying = flight ~= nil, Speed = speed, Minimized = minimized, Destroyed = destroyed } end function controller:Destroy() if destroyed then return end destroyed = true stopFlight() for _, connection in ipairs(connections) do connection:Disconnect() end table.clear(connections) shutdown.OnInvoke = nil gui:Destroy() end shutdown.OnInvoke = function() controller:Destroy() end connect(gui.Destroying, function() controller:Destroy() end) connect(player.CharacterRemoving, function() stopFlight("WAITING FOR CHARACTER") end) connect(player.CharacterAdded, function() stopFlight("READY - PRESS F TO FLY") end) connect(toggleButton.Activated, function() controller:Toggle() end) connect(minusButton.Activated, function() controller:SetSpeed(speed - CONFIG.SpeedStep) end) connect(plusButton.Activated, function() controller:SetSpeed(speed + CONFIG.SpeedStep) end) connect(closeButton.Activated, function() controller:Destroy() end) connect(minimizeButton.Activated, function() controller:SetMinimized(not minimized) end) connect(canvas:GetPropertyChangedSignal("AbsoluteSize"), clampPanel) local function isPointer(input) return input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch end local function holdButton(target, value) connect(target.InputBegan, function(input) if flight and isPointer(input) then holds[input] = value end end) connect(target.InputEnded, function(input) holds[input] = nil end) end holdButton(upButton, 1) holdButton(downButton, -1) connect(header.InputBegan, function(input) if isPointer(input) then drag = { Input = input, Start = input.Position, Position = panel.Position } end end) connect(UserInputService.InputChanged, function(input) if not drag then return end local touch = drag.Input.UserInputType == Enum.UserInputType.Touch if (touch and input == drag.Input) or (not touch and input.UserInputType == Enum.UserInputType.MouseMovement) then local delta = input.Position - drag.Start panel.Position = UDim2.fromOffset(drag.Position.X.Offset + delta.X, drag.Position.Y.Offset + delta.Y) clampPanel() end end) local movementKeys = { [Enum.KeyCode.W] = true, [Enum.KeyCode.A] = true, [Enum.KeyCode.S] = true, [Enum.KeyCode.D] = true, [Enum.KeyCode.E] = true, [Enum.KeyCode.Q] = true, [Enum.KeyCode.Space] = true, [Enum.KeyCode.LeftControl] = true, } connect(UserInputService.InputBegan, function(input, processed) if processed or not inputFocused or UserInputService:GetFocusedTextBox() then return end if input.KeyCode == CONFIG.ToggleKey then controller:Toggle() elseif input.KeyCode == CONFIG.MinimizeKey then controller:SetMinimized(not minimized) elseif movementKeys[input.KeyCode] then keys[input.KeyCode] = true end end) connect(UserInputService.InputEnded, function(input) keys[input.KeyCode] = nil holds[input] = nil if input.UserInputType == Enum.UserInputType.MouseButton1 then for pointer in pairs(holds) do if pointer.UserInputType == Enum.UserInputType.MouseButton1 then holds[pointer] = nil end end end if drag and (input == drag.Input or input.UserInputType == Enum.UserInputType.MouseButton1) then drag = nil end end) connect(UserInputService.WindowFocusReleased, function() inputFocused = false clearInput() if flight then flight.Velocity = Vector3.zero end end) connect(UserInputService.WindowFocused, function() inputFocused = true end) connect(UserInputService.TextBoxFocused, clearInput) refresh() clampPanel() return controller