Spiderman UI
Key RequiredUniversal

Spiderman UI

Universal ScriptKuttibagoKuttibago 1.1K views 8K executesUpdated Sep 9, 2026
0.0/ 5(1 votes)
010% Positive
Lua Script•822 lines (29647 chars)
--[[
	SpiderWebSwing (LocalScript)

	PLACEMENT: StarterPlayer > StarterPlayerScripts
	(It needs to live here because it has its own persistent UI/settings that
	 should survive dying and respawning. The character is re-acquired
	 automatically every time you respawn.)

	FEATURES:
	  - Only ONE web at a time: shooting a new one instantly removes the old one.
	  - The string visually shoots out from your hand to the target over a
	    fraction of a second (speed scales with distance, WEB_TRAVEL_SPEED)
	    instead of just appearing at full length. The real physics rope only
	    attaches once it "lands".
	  - Speed boost: your current speed is amplified when you shoot a web while
	    already moving, and holding WASD while swinging accelerates you further
	    (up to a cap, MAX_SWING_SPEED).
	  - Real swing feel: while a web is attached, the Humanoid is put into
	    "Physics" state so Roblox's normal walking control never takes over and
	    zeroes your speed when you're near the ground at the bottom of the arc.
	  - Release with high speed (see SLIDE_MIN_SPEED) and you fly off, then
	    gradually slow down (SLIDE_FRICTION) instead of stopping dead.
	  - Loose/"ragdoll" feel WITHOUT looking ragdolled: the web attaches
	    slightly above your center of mass (SWING_HAND_HEIGHT), which gives a
	    natural lean during swings instead of perfectly rigid motion, and
	    AutoRotate is turned off so you're not forced to face your direction
	    of travel. A weak AlignOrientation (ORIENTATION_MAX_TORQUE /
	    ORIENTATION_RESPONSIVENESS) only stops you from ending up permanently
	    upside-down - your body always looks completely normal (no loose
	    limbs, no broken joints). This loose feel starts the moment you shoot
	    a web and lasts through the whole time you're airborne - including
	    after you release it - and only ends the instant you actually touch
	    the ground. While a web is still attached it NEVER ends early (even
	    if you brush the ground mid-swing), so the pendulum can't get "stuck"
	    at the bottom of a low arc.
	  - A small settings UI that's open by default. Drag the header
	    ("SPIDER WEB") to move the panel, click "–" to collapse it to just the
	    header, or click "X" to fully close it (this also automatically turns
	    the whole mechanic OFF). Click the spider tab on the left edge to
	    reopen it. In the panel you can:
	      * Turn the whole mechanic ON/OFF.
	      * Click "Change" next to an action, then press any key or mouse
	        button to rebind it - or press Esc while it's listening to make
	        that action fully unbound.

	DEFAULT CONTROLS (changeable in the UI; click "Change" then press any
	key/mouse button, or press Esc while it's listening to unbind it):
	  Left Click   -> Shoot web
	  F            -> Release web
	  Hold E       -> Climb (reel in)
	  Hold Q       -> Let out more rope
]]

local Players = game:GetService("Players")
local UserInputService = game:GetService("UserInputService")
local RunService = game:GetService("RunService")

-- ===================== SETTINGS =====================
local MAX_WEB_DISTANCE = 350   -- how far you can shoot a web (studs)
local MIN_TARGET_SIZE  = 3     -- ignore tiny junk smaller than this (studs, largest side)
local ROPE_THICKNESS   = 0.06
local ROPE_SLACK       = 1.05  -- MUST be >= 1. Below 1 makes the rope "shorter" than the
                                -- actual distance right from the start, which makes the
                                -- physics engine yank you hard toward the anchor instantly
                                -- (feels like a teleport). 1.0 = taut immediately, higher
                                -- gives a bit of slack/drop before the swing kicks in.
local MIN_ROPE_LENGTH  = 4
local REEL_IN_SPEED    = 18    -- studs/second while climbing (E)
local LET_OUT_SPEED    = 14    -- studs/second while letting out rope (Q)

local LAUNCH_BOOST_MULTIPLIER = 1.4  -- amplifies your current speed when the web is shot
local SWING_THRUST     = 110         -- extra force (studs/s^2) in the direction you press while swinging
local MAX_SWING_SPEED  = 160         -- cap on horizontal swing speed

local SLIDE_MIN_SPEED    = 15   -- release with at least this much speed (studs/s) to slide instead of stopping dead
local SLIDE_FRICTION     = 2.5  -- how fast the slide slows down (higher = stops sooner)
local SLIDE_MAX_DURATION = 1.5  -- safety cap in seconds for how long the slide can last

local SWING_HAND_HEIGHT          = 1.6  -- height (studs) above your center where the web
                                         -- attaches to YOU. Keeps the rope's pull from going
                                         -- straight through your center of mass, giving a
                                         -- natural lean during swings instead of perfectly
                                         -- rigid motion (the "ragdoll feel" without looking
                                         -- ragdolled).
local ORIENTATION_MAX_TORQUE     = 6000 -- how hard you're corrected if you lean/tip too far.
                                         -- Lower = looser/more "ragdoll", higher = stiffer.
local ORIENTATION_RESPONSIVENESS = 3    -- how fast that correction happens (lower = softer/slower).

local WEB_TRAVEL_SPEED    = 300  -- studs/second the visual string travels while "shooting out"
local WEB_TRAVEL_MIN_TIME = 0.05 -- seconds, floor so very close shots don't look instant/jumpy
local WEB_TRAVEL_MAX_TIME = 0.3  -- seconds, cap so very far shots don't feel sluggish

local GROUND_CHECK_DISTANCE = 3.5 -- studs below your feet checked to detect landing
-- ======================================================

local player = Players.LocalPlayer
local mouse = player:GetMouse()

-- ===================== ACTIONS / KEYBINDS =====================
local actions = {
	Shoot   = {name = "Shoot web",        input = Enum.UserInputType.MouseButton1},
	Release = {name = "Release web",      input = Enum.KeyCode.F},
	ReelIn  = {name = "Climb (reel in)",  input = Enum.KeyCode.E},
	LetOut  = {name = "Let out",          input = Enum.KeyCode.Q},
}

local scriptEnabled = true

-- ===================== CHARACTER =====================
local character, humanoid, rootPart
local activeWebs = {} -- list of {rope, attachment0, attachment1} - just the visual/physical rope
local reeling, lettingOut = false, false
local sliding = false
local slideVelocity = Vector3.new()
local slideEndTime = 0

-- The "ragdoll" rig (loose orientation assist) is tracked separately from
-- the rope itself, because it now needs to keep existing after you release
-- the web - it only goes away once you actually touch the ground.
local ragdollOrientationAttachment = nil
local ragdollAlignOrientation = nil

-- Tracks the visual "string shooting out" animation so a newer shot, a
-- release, or a respawn can cleanly cancel an in-flight one.
local travelToken = 0
local currentTravelPart = nil

local function cancelWebTravel()
	travelToken = travelToken + 1
	if currentTravelPart then
		currentTravelPart:Destroy()
		currentTravelPart = nil
	end
end

local function destroyWebEntry(entry)
	if entry.rope then entry.rope:Destroy() end
	if entry.attachment0 then entry.attachment0:Destroy() end
	if entry.attachment1 then entry.attachment1:Destroy() end
end

local function ensureRagdollRig()
	if ragdollAlignOrientation and ragdollAlignOrientation.Parent then
		return -- already active (e.g. chaining a new web before landing)
	end
	if not rootPart then return end

	local orientationAttachment = Instance.new("Attachment")
	orientationAttachment.Name = "SwingOrientationAttachment"
	orientationAttachment.Parent = rootPart

	local alignOrientation = Instance.new("AlignOrientation")
	alignOrientation.Name = "SwingUprightAssist"
	alignOrientation.Mode = Enum.OrientationAlignmentMode.OneAttachment
	alignOrientation.Attachment0 = orientationAttachment
	alignOrientation.RigidityEnabled = false
	alignOrientation.MaxTorque = ORIENTATION_MAX_TORQUE
	alignOrientation.Responsiveness = ORIENTATION_RESPONSIVENESS
	alignOrientation.CFrame = rootPart.CFrame
	alignOrientation.Parent = rootPart

	ragdollOrientationAttachment = orientationAttachment
	ragdollAlignOrientation = alignOrientation

	if humanoid then
		humanoid.AutoRotate = false
	end
end

local function endRagdoll()
	if ragdollAlignOrientation then ragdollAlignOrientation:Destroy() end
	if ragdollOrientationAttachment then ragdollOrientationAttachment:Destroy() end
	ragdollAlignOrientation = nil
	ragdollOrientationAttachment = nil
	if humanoid then
		humanoid.AutoRotate = true
	end
end

local function destroyAllWebs()
	for _, entry in ipairs(activeWebs) do
		destroyWebEntry(entry)
	end
	activeWebs = {}
end

local function onCharacterAdded(newCharacter)
	activeWebs = {} -- the old instances belonged to the previous body and are already gone
	sliding = false
	ragdollOrientationAttachment = nil
	ragdollAlignOrientation = nil
	cancelWebTravel()
	character = newCharacter
	humanoid = character:WaitForChild("Humanoid")
	rootPart = character:WaitForChild("HumanoidRootPart")
	humanoid.Died:Connect(destroyAllWebs)
end

onCharacterAdded(player.Character or player.CharacterAdded:Wait())
player.CharacterAdded:Connect(onCharacterAdded)

-- ===================== WEB LOGIC =====================

local function isValidTarget(hitPart, hitPosition)
	if not hitPart or not hitPart:IsA("BasePart") then
		return false
	end
	if hitPart:IsDescendantOf(character) then
		return false
	end
	if not hitPart.Anchored then
		return false -- only grab fixed/static things (buildings, ground, etc.)
	end

	local ancestorModel = hitPart:FindFirstAncestorOfClass("Model")
	if ancestorModel and Players:GetPlayerFromCharacter(ancestorModel) then
		return false -- don't let players attach a web to each other
	end

	local size = hitPart.Size
	if math.max(size.X, size.Y, size.Z) < MIN_TARGET_SIZE then
		return false
	end

	if (hitPosition - rootPart.Position).Magnitude > MAX_WEB_DISTANCE then
		return false
	end

	return true
end

local function applyLaunchBoost()
	local vel = rootPart.AssemblyLinearVelocity
	local horizontal = Vector3.new(vel.X, 0, vel.Z)
	if horizontal.Magnitude > 2 then
		local boosted = horizontal * LAUNCH_BOOST_MULTIPLIER
		if boosted.Magnitude > MAX_SWING_SPEED then
			boosted = boosted.Unit * MAX_SWING_SPEED
		end
		rootPart.AssemblyLinearVelocity = Vector3.new(boosted.X, vel.Y, boosted.Z)
	end
end

-- Actually attaches the real physics rope once the visual string has
-- finished travelling to its target.
local function attachWeb(hitPart, hitPosition)
	if not scriptEnabled then return end
	if not rootPart or not rootPart.Parent then return end

	local anchorAttachment = Instance.new("Attachment")
	anchorAttachment.Name = "WebAnchor"
	anchorAttachment.Parent = hitPart      -- Parent MUST be set before WorldPosition,
	anchorAttachment.WorldPosition = hitPosition -- otherwise the position is computed wrong
	                                              -- and the anchor can end up far away
	                                              -- (this caused the old teleport bug).

	local playerAttachment = Instance.new("Attachment")
	playerAttachment.Name = "WebHand"
	playerAttachment.Position = Vector3.new(0, SWING_HAND_HEIGHT, 0) -- not at the center of mass -> natural lean
	playerAttachment.Parent = rootPart

	local distance = (hitPosition - rootPart.Position).Magnitude

	local rope = Instance.new("RopeConstraint")
	rope.Name = "SpiderWebRope"
	rope.Attachment0 = playerAttachment
	rope.Attachment1 = anchorAttachment
	rope.Length = math.max(distance * ROPE_SLACK, MIN_ROPE_LENGTH)
	rope.Restitution = 0
	rope.Visible = true
	rope.Color = BrickColor.new("White")
	rope.Thickness = ROPE_THICKNESS
	rope.Parent = rootPart

	table.insert(activeWebs, {
		rope = rope,
		attachment0 = playerAttachment,
		attachment1 = anchorAttachment,
	})

	applyLaunchBoost()
end

local function shootWeb()
	if not scriptEnabled then return end
	if not rootPart or not rootPart.Parent then return end

	local hitPart = mouse.Target
	local hit = mouse.Hit
	if not hitPart or not hit then return end

	local hitPosition = hit.Position
	if not isValidTarget(hitPart, hitPosition) then return end

	-- only one web at a time: the old one (attached, or still shooting out) disappears immediately
	destroyAllWebs()
	cancelWebTravel()
	sliding = false

	-- weak "keep me upright" force: only stops you from ending up permanently
	-- upside-down, but is soft enough to let you lean/sway naturally with the
	-- swing's motion (your body still looks completely normal - just looser
	-- and more physics-driven). Starts right away (before the string even
	-- lands) so shooting feels immediately responsive, and stays active
	-- until you actually touch the ground.
	ensureRagdollRig()

	if humanoid then
		-- Physics state = the Humanoid completely stops managing horizontal
		-- speed, so the rope's pendulum physics can take over without
		-- "sticking" when you're close to the ground at the bottom of the arc.
		humanoid:ChangeState(Enum.HumanoidStateType.Physics)
	end

	-- Shoot the string out visually instead of having it just appear at full
	-- length: a thin glowing line travels from your hand to the target at a
	-- fixed speed, then the real physics rope attaches once it "lands".
	travelToken = travelToken + 1
	local myToken = travelToken

	local startPosition = rootPart.Position + Vector3.new(0, SWING_HAND_HEIGHT, 0)
	local travelDistance = (hitPosition - startPosition).Magnitude
	local travelTime = math.clamp(travelDistance / WEB_TRAVEL_SPEED, WEB_TRAVEL_MIN_TIME, WEB_TRAVEL_MAX_TIME)

	local travelPart = Instance.new("Part")
	travelPart.Name = "WebTravelVisual"
	travelPart.Anchored = true
	travelPart.CanCollide = false
	travelPart.CanQuery = false
	travelPart.CanTouch = false
	travelPart.CastShadow = false
	travelPart.Material = Enum.Material.Neon
	travelPart.Color = Color3.new(1, 1, 1)
	travelPart.Size = Vector3.new(ROPE_THICKNESS, ROPE_THICKNESS, 0.05)
	travelPart.CFrame = CFrame.new(startPosition, hitPosition)
	travelPart.Parent = workspace
	currentTravelPart = travelPart

	task.spawn(function()
		local elapsed = 0
		while elapsed < travelTime do
			if travelToken ~= myToken then
				return -- cancelled: replaced by a new shot, released, disabled, or respawned
			end
			elapsed = elapsed + RunService.Heartbeat:Wait()
			local alpha = math.clamp(elapsed / travelTime, 0, 1)
			local tip = startPosition:Lerp(hitPosition, alpha)
			local length = math.max((tip - startPosition).Magnitude, 0.05)
			travelPart.Size = Vector3.new(ROPE_THICKNESS, ROPE_THICKNESS, length)
			travelPart.CFrame = CFrame.new(startPosition, tip) * CFrame.new(0, 0, -length / 2)
		end

		if travelToken ~= myToken then return end
		travelPart:Destroy()
		currentTravelPart = nil
		attachWeb(hitPart, hitPosition)
	end)
end

-- Releases the web on purpose (kept separate from destroyAllWebs, which is
-- also used internally when a new web replaces an old one, or on death/disable).
local function releaseWebs()
	local hadWeb = #activeWebs > 0
	local vel = (rootPart and rootPart.AssemblyLinearVelocity) or Vector3.new()
	local horizontal = Vector3.new(vel.X, 0, vel.Z)

	destroyAllWebs()
	cancelWebTravel()

	if not hadWeb then return end

	if horizontal.Magnitude >= SLIDE_MIN_SPEED then
		-- fly off with the speed you had, then gradually slow down instead
		-- of stopping dead when you land
		sliding = true
		slideVelocity = horizontal
		slideEndTime = tick() + SLIDE_MAX_DURATION
	else
		sliding = false
	end

	-- Deliberately NOT restoring AutoRotate/Humanoid state here anymore:
	-- you stay loose/"ragdolled" for as long as you're still in the air,
	-- and only snap back to normal control once you actually touch the
	-- ground (handled in the Heartbeat loop below).
end

local actionHandlers = {
	Shoot = shootWeb,
	Release = releaseWebs,
	ReelIn = function() reeling = true end,
	LetOut = function() lettingOut = true end,
}

local actionHandlersEnded = {
	ReelIn = function() reeling = false end,
	LetOut = function() lettingOut = false end,
}

-- ===================== PHYSICS LOOP (climbing + speed) =====================

RunService.Heartbeat:Connect(function(dt)
	if not scriptEnabled then return end
	if not humanoid or not rootPart then return end

	-- reel in / let out
	if reeling or lettingOut then
		for _, entry in ipairs(activeWebs) do
			local rope = entry.rope
			if rope and rope.Parent then
				if reeling then
					rope.Length = math.max(MIN_ROPE_LENGTH, rope.Length - REEL_IN_SPEED * dt)
				elseif lettingOut then
					rope.Length = math.min(MAX_WEB_DISTANCE, rope.Length + LET_OUT_SPEED * dt)
				end
			end
		end
	end

	-- extra speed while swinging (hold WASD)
	if #activeWebs > 0 then
		local moveDir = humanoid.MoveDirection
		if moveDir.Magnitude > 0.05 then
			local vel = rootPart.AssemblyLinearVelocity
			local newVel = vel + moveDir * SWING_THRUST * dt
			local horizontal = Vector3.new(newVel.X, 0, newVel.Z)
			if horizontal.Magnitude > MAX_SWING_SPEED then
				horizontal = horizontal.Unit * MAX_SWING_SPEED
				newVel = Vector3.new(horizontal.X, newVel.Y, horizontal.Z)
			end
			rootPart.AssemblyLinearVelocity = newVel
		end
	end

	-- ragdoll upkeep: stays active the whole time you're airborne - from the
	-- moment you shoot a web, through releasing it, until you actually touch
	-- the ground. While a web is still attached we NEVER end it here (even if
	-- you brush the ground mid-swing), so the pendulum can't get "stuck" at
	-- the bottom of a low arc.
	if ragdollAlignOrientation then
		if #activeWebs == 0 and humanoid.FloorMaterial ~= Enum.Material.Air then
			-- touched the ground after releasing: stop being ragdolled
			endRagdoll()
			if humanoid:GetState() == Enum.HumanoidStateType.Physics then
				humanoid:ChangeState(Enum.HumanoidStateType.Landed)
			end
		else
			if humanoid:GetState() ~= Enum.HumanoidStateType.Physics then
				humanoid:ChangeState(Enum.HumanoidStateType.Physics)
			end

			-- update the "keep upright" target: same yaw as right now, but
			-- with no tilt/roll. Lets you lean/sway freely with the motion in
			-- the short term, but you'll never end up permanently upside-down.
			local look = rootPart.CFrame.LookVector
			local flatLook = Vector3.new(look.X, 0, look.Z)
			if flatLook.Magnitude > 0.001 then
				ragdollAlignOrientation.CFrame = CFrame.lookAt(rootPart.Position, rootPart.Position + flatLook)
			end
		end
	end

	-- slide after releasing the web: gradually brake (like friction) instead
	-- of letting the Humanoid zero your speed the instant you land.
	-- Deliberately does NOT touch the Humanoid's state - you stay a fully
	-- normal, animated character the whole time, since no web exists anymore.
	if sliding then
		if tick() > slideEndTime or slideVelocity.Magnitude < 2 then
			sliding = false
		else
			slideVelocity = slideVelocity * math.max(0, 1 - SLIDE_FRICTION * dt)
			local vel = rootPart.AssemblyLinearVelocity
			rootPart.AssemblyLinearVelocity = Vector3.new(slideVelocity.X, vel.Y, slideVelocity.Z)
		end
	end
end)

-- ===================== UI =====================

local playerGui = player:WaitForChild("PlayerGui")

local screenGui = Instance.new("ScreenGui")
screenGui.Name = "SpiderWebGUI"
screenGui.ResetOnSpawn = false
screenGui.IgnoreGuiInset = true
screenGui.Parent = playerGui

local panel = Instance.new("Frame")
panel.Name = "Panel"
panel.AutomaticSize = Enum.AutomaticSize.Y
panel.Size = UDim2.new(0, 260, 0, 0)
panel.Position = UDim2.new(0, 10, 0.5, 0)
panel.AnchorPoint = Vector2.new(0, 0.5)
panel.BackgroundColor3 = Color3.fromRGB(20, 20, 24)
panel.BackgroundTransparency = 0.08
panel.BorderSizePixel = 0
panel.Visible = true
panel.Parent = screenGui
Instance.new("UICorner", panel).CornerRadius = UDim.new(0, 10)

local stroke = Instance.new("UIStroke")
stroke.Color = Color3.fromRGB(255, 255, 255)
stroke.Transparency = 0.85
stroke.Parent = panel

local outerLayout = Instance.new("UIListLayout")
outerLayout.SortOrder = Enum.SortOrder.LayoutOrder
outerLayout.Padding = UDim.new(0, 8)
outerLayout.Parent = panel

local padding = Instance.new("UIPadding")
padding.PaddingTop = UDim.new(0, 10)
padding.PaddingBottom = UDim.new(0, 10)
padding.PaddingLeft = UDim.new(0, 12)
padding.PaddingRight = UDim.new(0, 12)
padding.Parent = panel

-- Header row (drag this to move the panel)
local header = Instance.new("Frame")
header.Name = "Header"
header.Size = UDim2.new(1, 0, 0, 22)
header.BackgroundTransparency = 1
header.LayoutOrder = 0
header.Parent = panel

local title = Instance.new("TextLabel")
title.Size = UDim2.new(1, -52, 1, 0)
title.BackgroundTransparency = 1
title.Text = "SPIDER WEB"
title.TextColor3 = Color3.fromRGB(255, 255, 255)
title.Font = Enum.Font.GothamBold
title.TextSize = 15
title.TextXAlignment = Enum.TextXAlignment.Left
title.Parent = header

local minimizeButton = Instance.new("TextButton")
minimizeButton.Name = "MinimizeButton"
minimizeButton.Size = UDim2.new(0, 22, 0, 22)
minimizeButton.Position = UDim2.new(1, -50, 0, 0)
minimizeButton.BackgroundColor3 = Color3.fromRGB(45, 45, 52)
minimizeButton.BorderSizePixel = 0
minimizeButton.Text = "–"
minimizeButton.TextColor3 = Color3.fromRGB(255, 255, 255)
minimizeButton.Font = Enum.Font.GothamBold
minimizeButton.TextSize = 16
minimizeButton.Parent = header
Instance.new("UICorner", minimizeButton).CornerRadius = UDim.new(0, 6)

local closeButton = Instance.new("TextButton")
closeButton.Name = "CloseButton"
closeButton.Size = UDim2.new(0, 22, 0, 22)
closeButton.Position = UDim2.new(1, -24, 0, 0)
closeButton.BackgroundColor3 = Color3.fromRGB(70, 40, 40)
closeButton.BorderSizePixel = 0
closeButton.Text = "X"
closeButton.TextColor3 = Color3.fromRGB(255, 255, 255)
closeButton.Font = Enum.Font.GothamBold
closeButton.TextSize = 14
closeButton.Parent = header
Instance.new("UICorner", closeButton).CornerRadius = UDim.new(0, 6)

-- Content (hidden entirely when minimized, panel shrinks automatically)
local content = Instance.new("Frame")
content.Name = "Content"
content.AutomaticSize = Enum.AutomaticSize.Y
content.Size = UDim2.new(1, 0, 0, 0)
content.BackgroundTransparency = 1
content.LayoutOrder = 1
content.Parent = panel

local contentLayout = Instance.new("UIListLayout")
contentLayout.SortOrder = Enum.SortOrder.LayoutOrder
contentLayout.Padding = UDim.new(0, 6)
contentLayout.Parent = content

-- Enable/disable row
local toggleRow = Instance.new("Frame")
toggleRow.Size = UDim2.new(1, 0, 0, 28)
toggleRow.BackgroundTransparency = 1
toggleRow.LayoutOrder = 0
toggleRow.Parent = content

local toggleLabel = Instance.new("TextLabel")
toggleLabel.Size = UDim2.new(0.55, 0, 1, 0)
toggleLabel.BackgroundTransparency = 1
toggleLabel.Text = "Enabled"
toggleLabel.TextColor3 = Color3.fromRGB(220, 220, 220)
toggleLabel.Font = Enum.Font.Gotham
toggleLabel.TextSize = 13
toggleLabel.TextXAlignment = Enum.TextXAlignment.Left
toggleLabel.Parent = toggleRow

local toggleButton = Instance.new("TextButton")
toggleButton.Size = UDim2.new(0.45, 0, 1, 0)
toggleButton.Position = UDim2.new(0.55, 0, 0, 0)
toggleButton.BackgroundColor3 = Color3.fromRGB(60, 180, 90)
toggleButton.BorderSizePixel = 0
toggleButton.Text = "ON"
toggleButton.TextColor3 = Color3.fromRGB(255, 255, 255)
toggleButton.Font = Enum.Font.GothamBold
toggleButton.TextSize = 13
toggleButton.Parent = toggleRow
Instance.new("UICorner", toggleButton).CornerRadius = UDim.new(0, 6)

local function setScriptEnabled(state)
	scriptEnabled = state
	if scriptEnabled then
		toggleButton.Text = "ON"
		toggleButton.BackgroundColor3 = Color3.fromRGB(60, 180, 90)
	else
		toggleButton.Text = "OFF"
		toggleButton.BackgroundColor3 = Color3.fromRGB(190, 60, 60)
		reeling, lettingOut = false, false
		sliding = false
		destroyAllWebs()
		cancelWebTravel()
		endRagdoll()
		if humanoid then
			humanoid:ChangeState(Enum.HumanoidStateType.Freefall)
		end
	end
end

toggleButton.MouseButton1Click:Connect(function()
	setScriptEnabled(not scriptEnabled)
end)

-- keybind rows
local function inputToText(bound)
	if not bound then return "None" end
	if bound == Enum.UserInputType.MouseButton1 then return "Left Click" end
	if bound == Enum.UserInputType.MouseButton2 then return "Right Click" end
	if bound == Enum.UserInputType.MouseButton3 then return "Middle Click" end
	if bound.EnumType == Enum.KeyCode then return bound.Name end
	return tostring(bound)
end

local rowsOrder = {"Shoot", "Release", "ReelIn", "LetOut"}
local bindButtons = {}

for i, key in ipairs(rowsOrder) do
	local action = actions[key]

	local row = Instance.new("Frame")
	row.Size = UDim2.new(1, 0, 0, 26)
	row.BackgroundTransparency = 1
	row.LayoutOrder = i
	row.Parent = content

	local label = Instance.new("TextLabel")
	label.Size = UDim2.new(0.5, 0, 1, 0)
	label.BackgroundTransparency = 1
	label.Text = action.name
	label.TextColor3 = Color3.fromRGB(220, 220, 220)
	label.Font = Enum.Font.Gotham
	label.TextSize = 12
	label.TextXAlignment = Enum.TextXAlignment.Left
	label.TextTruncate = Enum.TextTruncate.AtEnd
	label.Parent = row

	local bindBtn = Instance.new("TextButton")
	bindBtn.Size = UDim2.new(0.5, 0, 1, 0)
	bindBtn.Position = UDim2.new(0.5, 0, 0, 0)
	bindBtn.BackgroundColor3 = Color3.fromRGB(45, 45, 52)
	bindBtn.BorderSizePixel = 0
	bindBtn.TextColor3 = Color3.fromRGB(255, 255, 255)
	bindBtn.Font = Enum.Font.GothamBold
	bindBtn.TextSize = 12
	bindBtn.Text = inputToText(action.input)
	bindBtn.Parent = row
	Instance.new("UICorner", bindBtn).CornerRadius = UDim.new(0, 6)

	bindButtons[key] = bindBtn
end

-- minimize: collapses the panel down to just the header
local minimized = false
local function setMinimized(state)
	minimized = state
	content.Visible = not minimized
	minimizeButton.Text = minimized and "+" or "–"
end

minimizeButton.MouseButton1Click:Connect(function()
	setMinimized(not minimized)
end)

-- close: fully hides the panel AND turns the whole mechanic off
local function closePanel()
	panel.Visible = false
	setScriptEnabled(false)
end

closeButton.MouseButton1Click:Connect(closePanel)

-- drag the header to move the panel
local dragging = false
local dragInput, dragStart, startPos

local function updateDrag(input)
	local delta = input.Position - dragStart
	panel.Position = UDim2.new(
		startPos.X.Scale, startPos.X.Offset + delta.X,
		startPos.Y.Scale, startPos.Y.Offset + delta.Y
	)
end

header.InputBegan:Connect(function(input)
	if input.UserInputType == Enum.UserInputType.MouseButton1
		or input.UserInputType == Enum.UserInputType.Touch then
		dragging = true
		dragStart = input.Position
		startPos = panel.Position

		input.Changed:Connect(function()
			if input.UserInputState == Enum.UserInputState.End then
				dragging = false
			end
		end)
	end
end)

header.InputChanged:Connect(function(input)
	if input.UserInputType == Enum.UserInputType.MouseMovement
		or input.UserInputType == Enum.UserInputType.Touch then
		dragInput = input
	end
end)

UserInputService.InputChanged:Connect(function(input)
	if input == dragInput and dragging then
		updateDrag(input)
	end
end)

-- ===================== INPUT (dispatch + rebinding) =====================

local listeningFor = nil -- which action is waiting for a new key press

local function beginListening(actionKey)
	if listeningFor then
		bindButtons[listeningFor].Text = inputToText(actions[listeningFor].input)
	end
	listeningFor = actionKey
	bindButtons[actionKey].Text = "Press a key..."
end

for key, btn in pairs(bindButtons) do
	btn.MouseButton1Click:Connect(function()
		beginListening(key)
	end)
end

local function getInputBindFromEvent(input)
	if input.UserInputType == Enum.UserInputType.Keyboard then
		if input.KeyCode == Enum.KeyCode.Unknown then return nil end
		return input.KeyCode
	elseif input.UserInputType == Enum.UserInputType.MouseButton1
		or input.UserInputType == Enum.UserInputType.MouseButton2
		or input.UserInputType == Enum.UserInputType.MouseButton3 then
		return input.UserInputType
	end
	return nil
end

local function inputMatches(io, bound)
	if not bound then return false end
	if typeof(bound) ~= "EnumItem" then return false end
	if bound.EnumType == Enum.KeyCode then
		return io.KeyCode == bound
	elseif bound.EnumType == Enum.UserInputType then
		return io.UserInputType == bound
	end
	return false
end

UserInputService.InputBegan:Connect(function(input, gameProcessed)
	if listeningFor then
		if input.UserInputType == Enum.UserInputType.Keyboard and input.KeyCode == Enum.KeyCode.Escape then
			-- Esc = make the action unbound (no key) instead of binding it to Esc
			actions[listeningFor].input = nil
			bindButtons[listeningFor].Text = "None"
			listeningFor = nil
			return
		end

		local newBind = getInputBindFromEvent(input)
		if newBind then
			-- clear any duplicate on another action
			for otherKey, otherAction in pairs(actions) do
				if otherKey ~= listeningFor and otherAction.input == newBind then
					otherAction.input = nil
					if bindButtons[otherKey] then
						bindButtons[otherKey].Text = "None"
					end
				end
			end
			actions[listeningFor].input = newBind
			bindButtons[listeningFor].Text = inputToText(newBind)
		else
			bindButtons[listeningFor].Text = inputToText(actions[listeningFor].input)
		end
		listeningFor = nil
		return
	end

	if gameProcessed then return end

	for key, action in pairs(actions) do
		if action.input and inputMatches(input, action.input) and actionHandlers[key] then
			actionHandlers[key]()
		end
	end
end)

UserInputService.InputEnded:Connect(function(input, _gameProcessed)
	for key, action in pairs(actions) do
		if action.input and inputMatches(input, action.input) and actionHandlersEnded[key] then
			actionHandlersEnded[key]()
		end
	end
end)

Features & Information

Become spiderman inside of any roblox game!
#spiderman#spider#UI#swing#fun

Community Comments (0)

No comments yet for this script.

More Scripts for Universal Scripts