Spider-Man Ai GPT-6 ASTRA UPDATED SCRIPT
Key RequiredUniversal

Spider-Man Ai GPT-6 ASTRA UPDATED SCRIPT

Universal Scriptri5282649ri5282649 1.1K views 23.2K executesUpdated Sep 12, 2026
0.0/ 5(1 votes)
010% Positive
Lua Script•1425 lines (32620 chars)
-- PROJECT SPIDER V3 — FREE SWING EDITION
-- LocalScript: StarterPlayer > StarterPlayerScripts
-- Q: Hold Swing | E: Zip | R: Hold Pull
-- C: Toggle Crawl | F: Charge / Fire Slingshot
-- SPACE: Reel while swinging / Leap while crawling
-- SHIFT: Extend rope / Crawl faster | X: Cancel
-- Mobile buttons are included.

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

local player = Players.LocalPlayer
local playerGui = player:WaitForChild("PlayerGui")

local CONFIG = {
	SwingForward = 42,
	SwingHeight = 52,
	SwingBoost = 30,
	SwingAcceleration = 60,
	SwingAssist = 16,
	SwingMaxSpeed = 145,
	RopeMinLength = 12,
	RopeMaxLength = 180,
	RopeReelSpeed = 26,

	ZipDistance = 90,
	ZipSpeed = 145,
	ZipDuration = 0.7,

	PullDistance = 100,
	PullSpeed = 110,
	PullDuration = 2.5,

	ClimbReach = 5.5,
	ClimbSpeed = 19,
	ClimbSprintSpeed = 29,
	SurfaceOffset = 2.3,
	SurfaceStickSpeed = 24,
	SurfaceGraceTime = 0.22,

	SlingDistance = 85,
	SlingChargeTime = 1.6,
	SlingMinSpeed = 95,
	SlingMaxSpeed = 180,
	SlingLift = 45,

	Cooldowns = {
		Swing = 0.12,
		Zip = 0.45,
		Pull = 0.6,
		Climb = 0.15,
		Sling = 6,
	},
}

local COLORS = {
	Red = Color3.fromRGB(210, 35, 48),
	Blue = Color3.fromRGB(35, 115, 225),
	Dark = Color3.fromRGB(25, 28, 36),
	Active = Color3.fromRGB(30, 155, 110),
	White = Color3.fromRGB(245, 247, 255),
}

local previous = playerGui:FindFirstChild("SpiderUI")
if previous then
	local oldShutdown = previous:FindFirstChild("Shutdown")
	if oldShutdown and oldShutdown:IsA("BindableEvent") then
		oldShutdown:Fire()
	end
	previous:Destroy()
end

local previousSling = playerGui:FindFirstChild("SpiderSlingshotSolo")
if previousSling then
	previousSling:Destroy()
end

local gui = Instance.new("ScreenGui")
gui.Name = "SpiderUI"
gui.ResetOnSpawn = false
gui.ZIndexBehavior = Enum.ZIndexBehavior.Sibling
gui.Parent = playerGui

local shutdown = Instance.new("BindableEvent")
shutdown.Name = "Shutdown"
shutdown.Parent = gui

local running = true
local connections = {}
local deathConnection
local characterGeneration = 0

local character
local humanoid
local root

local cooldowns = {}
local keys = {}
local actionHolds = {}
local crawlTouches = {}
local ropeTouches = {}
local buttons = {}

local notice = ""
local noticeUntil = 0

local state = {
	mode = "Idle",
	objects = {},
}

local stopMode
local startSwing
local startZip
local startPull
local toggleClimb
local slingAction
local leapOff

local function connect(signal, callback)
	local connection = signal:Connect(callback)
	table.insert(connections, connection)
	return connection
end

local function alive()
	return running
		and character ~= nil
		and character.Parent ~= nil
		and root ~= nil
		and root.Parent ~= nil
		and humanoid ~= nil
		and humanoid.Health > 0
end

local function unitOr(vector, fallback)
	if vector.Magnitude > 0.001 then
		return vector.Unit
	end
	return fallback
end

local function project(vector, normal)
	return vector - normal * vector:Dot(normal)
end

local function own(object)
	table.insert(state.objects, object)
	return object
end

local function tell(text)
	notice = text
	noticeUntil = os.clock() + 2.2
end

local function ready(action)
	return os.clock() >= (cooldowns[action] or 0)
end

local function cooldown(action)
	cooldowns[action] = os.clock() + CONFIG.Cooldowns[action]
end

local function playSound(id, volume, speed)
	if not alive() then return end

	local sound = Instance.new("Sound")
	sound.SoundId = "rbxassetid://" .. tostring(id)
	sound.Volume = volume or 0.8
	sound.PlaybackSpeed = speed or 1
	sound.Parent = root
	sound:Play()
	Debris:AddItem(sound, 4)
end

local function cameraDirection()
	local camera = Workspace.CurrentCamera
	return camera and camera.CFrame.LookVector
		or root.CFrame.LookVector
end

local function isPointer(input)
	return input.UserInputType == Enum.UserInputType.Touch
		or input.UserInputType == Enum.UserInputType.MouseButton1
end

local function makeButton(parent, name, text, color, size)
	local button = Instance.new("TextButton")
	button.Name = name
	button.Size = size or UDim2.fromOffset(70, 70)
	button.BackgroundColor3 = color
	button.TextColor3 = COLORS.White
	button.Text = text
	button.TextSize = 14
	button.TextWrapped = true
	button.Font = Enum.Font.GothamBold
	button.AutoButtonColor = true
	button.Parent = parent

	local corner = Instance.new("UICorner")
	corner.CornerRadius = UDim.new(0, 18)
	corner.Parent = button

	local stroke = Instance.new("UIStroke")
	stroke.Color = COLORS.White
	stroke.Transparency = 0.72
	stroke.Thickness = 1.5
	stroke.Parent = button

	return button
end

local status = Instance.new("TextLabel")
status.AnchorPoint = Vector2.new(0.5, 0)
status.Position = UDim2.fromScale(0.5, 0.025)
status.Size = UDim2.new(0.92, 0, 0, 46)
status.BackgroundTransparency = 1
status.Font = Enum.Font.GothamBold
status.TextSize = 14
status.TextWrapped = true
status.TextColor3 = COLORS.White
status.TextStrokeTransparency = 0.35
status.Text = "PROJECT SPIDER V3"
status.Parent = gui

local actionPanel = Instance.new("Frame")
actionPanel.AnchorPoint = Vector2.new(1, 0.5)
actionPanel.Position = UDim2.new(1, -14, 0.5, 0)
actionPanel.Size = UDim2.fromOffset(76, 388)
actionPanel.BackgroundTransparency = 1
actionPanel.Parent = gui

local actionScale = Instance.new("UIScale")
actionScale.Parent = actionPanel

local actionLayout = Instance.new("UIListLayout")
actionLayout.Padding = UDim.new(0, 8)
actionLayout.HorizontalAlignment = Enum.HorizontalAlignment.Center
actionLayout.SortOrder = Enum.SortOrder.LayoutOrder
actionLayout.Parent = actionPanel

local definitions = {
	{"Swing", "SWING\n[Q]", COLORS.Red},
	{"Zip", "ZIP\n[E]", COLORS.Blue},
	{"Pull", "PULL\n[R]", COLORS.Dark},
	{"Climb", "CRAWL\n[C]", COLORS.Red},
	{"Sling", "SLING\n[F]", COLORS.Blue},
}

for index, definition in ipairs(definitions) do
	local action, text, color = table.unpack(definition)
	local button = makeButton(actionPanel, action, text, color)
	button.LayoutOrder = index

	buttons[action] = {
		button = button,
		text = text,
		color = color,
	}
end

local crawlPanel = Instance.new("Frame")
crawlPanel.AnchorPoint = Vector2.new(0.5, 1)
crawlPanel.Position = UDim2.new(0.5, 0, 1, -20)
crawlPanel.Size = UDim2.fromOffset(204, 136)
crawlPanel.BackgroundTransparency = 1
crawlPanel.Visible = false
crawlPanel.Parent = gui

local crawlScale = Instance.new("UIScale")
crawlScale.Parent = crawlPanel

local function makeDirection(name, text, x, y)
	local button = makeButton(
		crawlPanel,
		name,
		text,
		COLORS.Dark,
		UDim2.fromOffset(62, 62)
	)
	button.Position = UDim2.fromOffset(x, y)
	return button
end

local upButton = makeDirection("Up", "UP", 71, 0)
local downButton = makeDirection("Down", "DOWN", 71, 72)
local leftButton = makeDirection("Left", "LEFT", 0, 72)
local rightButton = makeDirection("Right", "RIGHT", 142, 72)

local leapButton = makeButton(
	gui,
	"LeapOff",
	"LEAP OFF [SPACE]",
	COLORS.Blue,
	UDim2.fromOffset(190, 42)
)
leapButton.AnchorPoint = Vector2.new(0.5, 0)
leapButton.Position = UDim2.new(0.5, 0, 0.025, 50)
leapButton.Visible = false

local ropePanel = Instance.new("Frame")
ropePanel.AnchorPoint = Vector2.new(0.5, 1)
ropePanel.Position = UDim2.new(0.5, 0, 1, -28)
ropePanel.Size = UDim2.fromOffset(224, 54)
ropePanel.BackgroundTransparency = 1
ropePanel.Visible = false
ropePanel.Parent = gui

local ropeScale = Instance.new("UIScale")
ropeScale.Parent = ropePanel

local reelButton = makeButton(
	ropePanel,
	"ReelIn",
	"REEL IN",
	COLORS.Blue,
	UDim2.fromOffset(106, 54)
)

local extendButton = makeButton(
	ropePanel,
	"LetOut",
	"LET OUT",
	COLORS.Dark,
	UDim2.fromOffset(106, 54)
)
extendButton.Position = UDim2.fromOffset(118, 0)

-- Web abilities use invisible, non-colliding points in open air.
local function makeAirAnchor(position)
	local part = own(Instance.new("Part"))
	part.Name = "SpiderAirAnchor"
	part.Size = Vector3.new(0.1, 0.1, 0.1)
	part.Anchored = true
	part.CanCollide = false
	part.CanTouch = false
	part.CanQuery = false
	part.CastShadow = false
	part.Transparency = 1
	part.Position = position
	part.Parent = Workspace

	local attachment = own(Instance.new("Attachment"))
	attachment.Name = "WebAnchor"
	attachment.Parent = part

	return attachment
end

local function handPart(side)
	return character:FindFirstChild(side .. "Hand")
		or character:FindFirstChild(side .. " Arm")
end

local function makeHandWeb(side, target)
	local hand = handPart(side)
	if not hand then return end

	local attachment = own(Instance.new("Attachment"))
	attachment.Name = "Spider" .. side .. "Web"
	attachment.Position = Vector3.new(0, -hand.Size.Y * 0.45, 0)
	attachment.Parent = hand

	local beam = own(Instance.new("Beam"))
	beam.Name = "SpiderWeb"
	beam.Attachment0 = attachment
	beam.Attachment1 = target
	beam.Width0 = 0.085
	beam.Width1 = 0.065
	beam.FaceCamera = true
	beam.Segments = 12
	beam.LightEmission = 0.35
	beam.Color = ColorSequence.new(COLORS.White)
	beam.Parent = hand
end

local function startHandPose()
	state.pose = {
		targets = {},
		shoulders = {},
	}

	for _, side in ipairs({"Left", "Right"}) do
		local hand = handPart(side)
		local upperArm = character:FindFirstChild(side .. "UpperArm")

		if humanoid.RigType == Enum.HumanoidRigType.R15
			and hand and upperArm then

			local target = own(Instance.new("Attachment"))
			target.Name = "Spider" .. side .. "Grip"
			target.Parent = root
			state.pose.targets[side] = target

			local ik = own(Instance.new("IKControl"))
			ik.Name = "Spider" .. side .. "ArmIK"
			ik.Type = Enum.IKControlType.Position
			ik.ChainRoot = upperArm
			ik.EndEffector = hand
			ik.Target = target
			ik.Weight = 1
			ik.SmoothTime = 0.08
			ik.Priority = 10
			ik.Parent = humanoid
		else
			local torso = character:FindFirstChild("Torso")
			local shoulder = torso
				and torso:FindFirstChild(side .. " Shoulder")

			if shoulder and shoulder:IsA("Motor6D") then
				state.pose.shoulders[side] = {
					motor = shoulder,
					transform = shoulder.Transform,
				}
			end
		end
	end
end

local function updateHandPose()
	if not state.pose or not state.anchors then return end

	for _, side in ipairs({"Left", "Right"}) do
		local index = side == "Left" and 1 or 2
		local anchor = state.anchors[index] or state.anchors[1]
		local target = state.pose.targets[side]

		if target and anchor and anchor.Parent then
			local shoulderPosition =
				root.Position + root.CFrame.UpVector * 1.25

			local direction = unitOr(
				anchor.WorldPosition - shoulderPosition,
				root.CFrame.UpVector
			)

			local separation = side == "Left" and -0.19 or 0.19
			local grip = shoulderPosition
				+ direction * 2
				+ root.CFrame.RightVector * separation

			target.Position = root.CFrame:PointToObjectSpace(grip)
		end
	end
end

local function attachWebs(firstPosition, secondPosition)
	local first = makeAirAnchor(firstPosition)
	local second = secondPosition
		and makeAirAnchor(secondPosition)
		or first

	state.anchors = {first, second}

	makeHandWeb("Left", first)
	makeHandWeb("Right", second)
	startHandPose()
	updateHandPose()
end

local function validAnchors()
	if not state.anchors then return false end

	for _, anchor in ipairs(state.anchors) do
		if not anchor:IsDescendantOf(Workspace) then
			return false
		end
	end

	return true
end

local function makePhysics(useVelocity)
	local attachment = own(Instance.new("Attachment"))
	attachment.Name = "SpiderMotion"
	attachment.Parent = root
	state.motionAttachment = attachment

	local orientation = own(Instance.new("AlignOrientation"))
	orientation.Mode = Enum.OrientationAlignmentMode.OneAttachment
	orientation.Attachment0 = attachment
	orientation.MaxTorque = 1000000
	orientation.MaxAngularVelocity = 14
	orientation.Responsiveness = 22
	orientation.CFrame = root.CFrame.Rotation
	orientation.Parent = root
	state.orientation = orientation

	if useVelocity then
		local velocity = own(Instance.new("LinearVelocity"))
		velocity.Attachment0 = attachment
		velocity.RelativeTo = Enum.ActuatorRelativeTo.World
		velocity.VelocityConstraintMode =
			Enum.VelocityConstraintMode.Vector
		velocity.ForceLimitsEnabled = false
		velocity.VectorVelocity = Vector3.zero
		velocity.Parent = root
		state.velocity = velocity
	end
end

stopMode = function()
	local old = state
	if old.mode == "Idle" then return end

	state = {
		mode = "Idle",
		objects = {},
	}

	if old.pose then
		for _, entry in pairs(old.pose.shoulders) do
			if entry.motor.Parent then
				entry.motor.Transform = entry.transform
			end
		end
	end

	for index = #old.objects, 1, -1 do
		old.objects[index]:Destroy()
	end

	if old.saved and old.saved.humanoid.Parent then
		local hum = old.saved.humanoid
		hum.AutoRotate = old.saved.autoRotate
		hum.PlatformStand = old.saved.platformStand

		if hum.Health > 0 and not hum.PlatformStand then
			hum:ChangeState(Enum.HumanoidStateType.Freefall)
		end
	end

	crawlPanel.Visible = false
	leapButton.Visible = false
	ropePanel.Visible = false

	table.clear(crawlTouches)
	table.clear(ropeTouches)

	if old.mode == "Swing"
		or old.mode == "Pull"
		or old.mode == "Climb"
		or old.mode == "Sling" then
		cooldown(old.mode)
	end
end

local function beginMode(mode, useVelocity)
	if not alive() then return false end

	stopMode()

	state.mode = mode
	state.started = os.clock()
	state.saved = {
		humanoid = humanoid,
		autoRotate = humanoid.AutoRotate,
		platformStand = humanoid.PlatformStand,
	}

	humanoid.AutoRotate = false
	humanoid.PlatformStand = true
	makePhysics(useVelocity)

	return true
end

local function faceDirection(direction, preferredUp)
	local forward = unitOr(direction, root.CFrame.LookVector)
	local up = preferredUp or Vector3.yAxis

	if math.abs(forward:Dot(up)) > 0.96 then
		up = root.CFrame.RightVector
	end

	if math.abs(forward:Dot(up)) > 0.96 then
		up = Vector3.zAxis
	end

	if math.abs(forward:Dot(up)) > 0.96 then
		up = Vector3.xAxis
	end

	state.orientation.CFrame = CFrame.lookAt(
		Vector3.zero,
		forward,
		up
	)
end

local function freeSwingPosition()
	local look = cameraDirection()
	local fallback = unitOr(
		project(root.CFrame.LookVector, Vector3.yAxis),
		Vector3.new(0, 0, -1)
	)

	local forward = unitOr(
		project(look, Vector3.yAxis),
		fallback
	)

	return root.Position
		+ forward * CONFIG.SwingForward
		+ Vector3.yAxis * (
			CONFIG.SwingHeight + math.max(look.Y, 0) * 28
		)
end

local function freeAimPosition(distance)
	return root.Position
		+ Vector3.yAxis * 1.5
		+ unitOr(cameraDirection(), root.CFrame.LookVector) * distance
end

startSwing = function()
	if not alive() or not ready("Swing") then return end
	if state.mode == "Swing" then return end

	local position = freeSwingPosition()

	if not beginMode("Swing", false) then return end
	attachWebs(position)

	local rope = own(Instance.new("RopeConstraint"))
	rope.Attachment0 = state.motionAttachment
	rope.Attachment1 = state.anchors[1]
	rope.Length = math.clamp(
		(position - root.Position).Magnitude,
		CONFIG.RopeMinLength,
		CONFIG.RopeMaxLength
	)
	rope.Visible = false
	rope.Restitution = 0
	rope.Parent = root
	state.rope = rope

	local force = own(Instance.new("VectorForce"))
	force.Attachment0 = state.motionAttachment
	force.RelativeTo = Enum.ActuatorRelativeTo.World
	force.ApplyAtCenterOfMass = true
	force.Force = Vector3.zero
	force.Parent = root
	state.force = force

	local radial = unitOr(position - root.Position, Vector3.yAxis)
	local forward = unitOr(
		project(cameraDirection(), radial),
		unitOr(project(root.CFrame.LookVector, radial), Vector3.zero)
	)

	root.AssemblyLinearVelocity += forward * CONFIG.SwingBoost

	ropePanel.Visible = true
	playSound(1284846268, 1)
end

startZip = function()
	if not alive() or not ready("Zip") then return end

	local position = freeAimPosition(CONFIG.ZipDistance)

	if not beginMode("Zip", true) then return end

	attachWebs(position)
	state.deadline = os.clock() + CONFIG.ZipDuration
	cooldown("Zip")
	playSound(9084017080, 1, 1.1)
end

startPull = function()
	if not alive() or not ready("Pull") then return end
	if state.mode == "Pull" then return end

	local position = freeAimPosition(CONFIG.PullDistance)

	if not beginMode("Pull", true) then return end

	attachWebs(position)
	state.deadline = os.clock() + CONFIG.PullDuration
	playSound(1284846268, 1, 0.95)
end

slingAction = function()
	if not alive() then return end

	if state.mode == "Sling" then
		if not validAnchors() then
			stopMode()
			return
		end

		local charge = math.clamp(
			(os.clock() - state.started) / CONFIG.SlingChargeTime,
			0,
			1
		)

		local middle = (
			state.anchors[1].WorldPosition
				+ state.anchors[2].WorldPosition
		) * 0.5

		local direction = unitOr(
			middle - root.Position,
			cameraDirection()
		)

		local speed = CONFIG.SlingMinSpeed
			+ (CONFIG.SlingMaxSpeed - CONFIG.SlingMinSpeed) * charge

		stopMode()

		root.AssemblyLinearVelocity =
			direction * speed
			+ Vector3.yAxis * CONFIG.SlingLift

		playSound(9084017080, 1.1, 1.1)
		return
	end

	if not ready("Sling") then
		tell("Slingshot is recharging")
		return
	end

	local camera = Workspace.CurrentCamera
	if not camera then return end

	local middle = root.Position
		+ Vector3.yAxis * 13
		+ camera.CFrame.LookVector * CONFIG.SlingDistance

	local first = middle - camera.CFrame.RightVector * 18
	local second = middle + camera.CFrame.RightVector * 18

	if not beginMode("Sling", true) then return end

	attachWebs(first, second)
	state.holdPosition = root.Position
	root.AssemblyLinearVelocity = Vector3.zero
	state.velocity.VectorVelocity = Vector3.zero

	playSound(1284846268, 1, 0.85)
end

-- Only surface crawling uses raycasts.
local function surfaceRay(origin, direction)
	if not character then return nil end

	local params = RaycastParams.new()
	params.FilterType = Enum.RaycastFilterType.Exclude
	params.FilterDescendantsInstances = {character}
	params.IgnoreWater = true
	params.RespectCanCollide = true

	return Workspace:Raycast(origin, direction, params)
end

local function surfaceBasis(normal, preferred)
	local up = project(preferred, normal)

	if up.Magnitude < 0.1 then
		up = project(Vector3.yAxis, normal)
	end
	if up.Magnitude < 0.1 then
		up = project(Vector3.zAxis, normal)
	end
	if up.Magnitude < 0.1 then
		up = project(Vector3.xAxis, normal)
	end

	up = up.Unit
	local right = unitOr(up:Cross(normal), Vector3.xAxis)

	return up, right
end

local function initialSurface()
	local directions = {
		cameraDirection(),
		root.CFrame.LookVector,
		root.CFrame.UpVector,
		-root.CFrame.UpVector,
		root.CFrame.RightVector,
		-root.CFrame.RightVector,
		-root.CFrame.LookVector,
	}

	for _, direction in ipairs(directions) do
		local hit = surfaceRay(
			root.Position,
			direction * CONFIG.ClimbReach
		)
		if hit then return hit end
	end

	return nil
end

toggleClimb = function()
	if state.mode == "Climb" then
		stopMode()
		return
	end

	if not alive() or not ready("Climb") then return end

	local hit = initialSurface()
	if not hit then
		tell("Move closer to a wall, floor, or ceiling to crawl")
		return
	end

	if not beginMode("Climb", true) then return end

	state.normal = hit.Normal
	state.surfaceUp = surfaceBasis(hit.Normal, root.CFrame.UpVector)
	state.bodyUp = state.surfaceUp
	state.lastSurface = os.clock()

	crawlPanel.Visible = true
	leapButton.Visible = true
end

local function crawlInput()
	local x, y = 0, 0

	if keys[Enum.KeyCode.A] then x -= 1 end
	if keys[Enum.KeyCode.D] then x += 1 end
	if keys[Enum.KeyCode.W] then y += 1 end
	if keys[Enum.KeyCode.S] then y -= 1 end

	local directions = {}
	for _, direction in pairs(crawlTouches) do
		directions[direction] = true
	end

	if directions.Left then x -= 1 end
	if directions.Right then x += 1 end
	if directions.Up then y += 1 end
	if directions.Down then y -= 1 end

	local input = Vector2.new(x, y)
	return input.Magnitude > 1 and input.Unit or input
end

local function findClimbSurface(movement)
	local normal = state.normal
	local origin = root.Position

	if movement.Magnitude > 0.1 then
		local ahead = surfaceRay(
			origin,
			movement.Unit * (CONFIG.SurfaceOffset + 1)
		)

		if ahead and ahead.Normal:Dot(normal) < 0.85 then
			return ahead
		end
	end

	local current = surfaceRay(
		origin,
		-normal * CONFIG.ClimbReach
	)

	if current then return current end

	if movement.Magnitude > 0.1 then
		local direction = movement.Unit

		local forwardSurface = surfaceRay(
			origin + direction * 1.4,
			-normal * CONFIG.ClimbReach
		)
		if forwardSurface then return forwardSurface end

		local around = surfaceRay(
			origin
				+ direction * 2
				- normal * (CONFIG.SurfaceOffset + 0.8),
			-direction * 3.5
		)
		if around then return around end
	end

	return nil
end

leapOff = function()
	if not alive() then return end

	if state.mode == "Climb" then
		local normal = state.normal
		local travel = state.bodyUp

		stopMode()

		root.AssemblyLinearVelocity =
			normal * 36
			+ travel * 22
			+ Vector3.yAxis * 22
	elseif state.mode ~= "Idle" then
		stopMode()
	end
end

local function updateSwing(dt)
	local target = state.anchors[1].WorldPosition
	local radial = unitOr(target - root.Position, Vector3.yAxis)
	local velocity = root.AssemblyLinearVelocity

	local steering = project(humanoid.MoveDirection, radial)
	local cameraTangent = project(cameraDirection(), radial)
	local travelTangent = project(velocity, radial)

	local assistDirection
	if travelTangent.Magnitude > 8 then
		assistDirection = travelTangent.Unit
	else
		assistDirection = unitOr(
			cameraTangent,
			unitOr(project(root.CFrame.LookVector, radial), Vector3.zero)
		)
	end

	local acceleration =
		steering * CONFIG.SwingAcceleration
		+ assistDirection * CONFIG.SwingAssist

	state.force.Force = acceleration * root.AssemblyMass

	if velocity.Magnitude > CONFIG.SwingMaxSpeed then
		local excess = velocity.Magnitude - CONFIG.SwingMaxSpeed
		state.force.Force -=
			velocity.Unit * excess * root.AssemblyMass * 5
	end

	local reelDirection = 0

	if keys[Enum.KeyCode.Space] then
		reelDirection -= 1
	end
	if keys[Enum.KeyCode.LeftShift]
		or keys[Enum.KeyCode.RightShift] then
		reelDirection += 1
	end

	for _, direction in pairs(ropeTouches) do
		reelDirection += direction
	end

	state.rope.Length = math.clamp(
		state.rope.Length
			+ math.clamp(reelDirection, -1, 1)
			* CONFIG.RopeReelSpeed
			* dt,
		CONFIG.RopeMinLength,
		CONFIG.RopeMaxLength
	)

	local facing = travelTangent
	if facing.Magnitude < 2 then
		facing = cameraTangent
	end

	faceDirection(facing, radial)
end

local function updateTravel()
	local delta = state.anchors[1].WorldPosition - root.Position
	local distance = delta.Magnitude
	local isZip = state.mode == "Zip"
	local stopDistance = 5

	if distance <= stopDistance or os.clock() >= state.deadline then
		if not isZip or distance <= stopDistance then
			root.AssemblyLinearVelocity *= 0.25
		end
		stopMode()
		return
	end

	local direction = delta / distance
	local maxSpeed = isZip and CONFIG.ZipSpeed or CONFIG.PullSpeed
	local speed = math.min(
		maxSpeed,
		math.max(12, (distance - stopDistance) * 7)
	)

	state.velocity.VectorVelocity = direction * speed
	faceDirection(direction)
end

local function updateClimb(dt)
	local input = crawlInput()
	local up, right = surfaceBasis(state.normal, state.surfaceUp)
	local movement = right * input.X + up * input.Y
	local hit = findClimbSurface(movement)

	if not hit then
		state.velocity.VectorVelocity = Vector3.zero

		if os.clock() - state.lastSurface > CONFIG.SurfaceGraceTime then
			stopMode()
		end
		return
	end

	state.lastSurface = os.clock()
	state.normal = hit.Normal
	state.surfaceUp, right = surfaceBasis(hit.Normal, state.surfaceUp)

	up = state.surfaceUp
	movement = right * input.X + up * input.Y

	local sprinting = keys[Enum.KeyCode.LeftShift]
		or keys[Enum.KeyCode.RightShift]

	local speed = sprinting
		and CONFIG.ClimbSprintSpeed
		or CONFIG.ClimbSpeed

	local gap = (root.Position - hit.Position):Dot(hit.Normal)
	local correction = math.clamp(
		(CONFIG.SurfaceOffset - gap) * 12,
		-CONFIG.SurfaceStickSpeed,
		CONFIG.SurfaceStickSpeed
	)

	local surfaceVelocity = Vector3.zero
	if hit.Instance:IsA("BasePart") then
		surfaceVelocity = hit.Instance:GetVelocityAtPosition(hit.Position)
	end

	state.velocity.VectorVelocity =
		movement * speed
		+ hit.Normal * correction
		+ surfaceVelocity

	-- The head follows travel direction, including head-first descent.
	if movement.Magnitude > 0.05 then
		local desiredUp = movement.Unit
		local currentUp = unitOr(
			project(state.bodyUp, hit.Normal),
			up
		)

		local angle = math.atan2(
			hit.Normal:Dot(currentUp:Cross(desiredUp)),
			math.clamp(currentUp:Dot(desiredUp), -1, 1)
		)

		if math.abs(angle) > math.pi - 0.001 then
			angle = math.pi
		end

		state.bodyUp = CFrame.fromAxisAngle(
			hit.Normal,
			angle * math.min(dt * 10, 1)
		):VectorToWorldSpace(currentUp)
	else
		state.bodyUp = unitOr(
			project(state.bodyUp, hit.Normal),
			up
		)
	end

	faceDirection(-hit.Normal, state.bodyUp)
end

local function updateSling()
	local correction = (state.holdPosition - root.Position) * 12

	if correction.Magnitude > 35 then
		correction = correction.Unit * 35
	end

	state.velocity.VectorVelocity = correction

	local middle = (
		state.anchors[1].WorldPosition
			+ state.anchors[2].WorldPosition
	) * 0.5

	faceDirection(middle - root.Position)
end

local function performAction(action)
	if action == "Swing" then
		startSwing()
	elseif action == "Zip" then
		startZip()
	elseif action == "Pull" then
		startPull()
	elseif action == "Climb" then
		toggleClimb()
	elseif action == "Sling" then
		slingAction()
	end
end

local function actionStillHeld(action)
	for _, held in pairs(actionHolds) do
		if held == action then return true end
	end
	return false
end

local function releaseAction(action)
	if actionStillHeld(action) then return end

	if (action == "Swing" and state.mode == "Swing")
		or (action == "Pull" and state.mode == "Pull") then
		stopMode()
	end
end

for action, entry in pairs(buttons) do
	if action == "Swing" or action == "Pull" then
		connect(entry.button.InputBegan, function(input)
			if not isPointer(input) then return end
			actionHolds[input] = action
			performAction(action)
		end)
	else
		connect(entry.button.Activated, function()
			performAction(action)
		end)
	end
end

local function bindCrawlButton(button, direction)
	connect(button.InputBegan, function(input)
		if isPointer(input) and state.mode == "Climb" then
			crawlTouches[input] = direction
		end
	end)
end

bindCrawlButton(upButton, "Up")
bindCrawlButton(downButton, "Down")
bindCrawlButton(leftButton, "Left")
bindCrawlButton(rightButton, "Right")

local function bindRopeButton(button, direction)
	connect(button.InputBegan, function(input)
		if isPointer(input) and state.mode == "Swing" then
			ropeTouches[input] = direction
		end
	end)
end

bindRopeButton(reelButton, -1)
bindRopeButton(extendButton, 1)
connect(leapButton.Activated, leapOff)

local keyActions = {
	[Enum.KeyCode.Q] = "Swing",
	[Enum.KeyCode.E] = "Zip",
	[Enum.KeyCode.R] = "Pull",
	[Enum.KeyCode.C] = "Climb",
	[Enum.KeyCode.F] = "Sling",
}

connect(UserInputService.InputBegan, function(input, processed)
	if processed or UserInputService:GetFocusedTextBox() then return end
	if input.UserInputType ~= Enum.UserInputType.Keyboard then return end

	keys[input.KeyCode] = true

	local action = keyActions[input.KeyCode]
	if action then
		if action == "Swing" or action == "Pull" then
			actionHolds[input.KeyCode] = action
		end
		performAction(action)
	elseif input.KeyCode == Enum.KeyCode.X then
		stopMode()
	end
end)

connect(UserInputService.InputEnded, function(input)
	keys[input.KeyCode] = nil

	local pointerAction = actionHolds[input]
	actionHolds[input] = nil
	if pointerAction then
		releaseAction(pointerAction)
	end

	local keyboardAction = actionHolds[input.KeyCode]
	actionHolds[input.KeyCode] = nil
	if keyboardAction then
		releaseAction(keyboardAction)
	end

	crawlTouches[input] = nil
	ropeTouches[input] = nil
end)

connect(UserInputService.JumpRequest, function()
	if UserInputService:GetFocusedTextBox() then return end

	-- Swing release is controlled by Q / the Swing button.
	if state.mode ~= "Swing" then
		leapOff()
	end
end)

local function clearInputs()
	table.clear(keys)
	table.clear(actionHolds)
	table.clear(crawlTouches)
	table.clear(ropeTouches)
end

connect(UserInputService.WindowFocusReleased, function()
	clearInputs()
	stopMode()
end)

connect(UserInputService.TextBoxFocused, function()
	clearInputs()
	stopMode()
end)

connect(RunService.PreSimulation, function()
	if not alive() or not state.pose then return end

	-- Apply the R6 pose after Animator has written its transforms.
	for side, entry in pairs(state.pose.shoulders) do
		if entry.motor.Parent then
			local spread = side == "Left" and -10 or 10
			entry.motor.Transform = CFrame.Angles(
				math.rad(165),
				0,
				math.rad(spread)
			)
		end
	end
end)

connect(RunService.Heartbeat, function(dt)
	if not alive() then
		stopMode()
		return
	end

	if state.mode == "Idle" then return end

	if state.anchors and not validAnchors() then
		stopMode()
		return
	end

	if state.mode == "Swing" then
		updateSwing(dt)
	elseif state.mode == "Zip" or state.mode == "Pull" then
		updateTravel()
	elseif state.mode == "Climb" then
		updateClimb(dt)
	elseif state.mode == "Sling" then
		updateSling()
	end

	updateHandPose()
end)

connect(RunService.RenderStepped, function()
	local now = os.clock()
	local camera = Workspace.CurrentCamera

	if camera then
		local viewport = camera.ViewportSize
		local scale = math.clamp(
			math.min(viewport.Y / 720, viewport.X / 420),
			0.6,
			1
		)

		actionScale.Scale = scale
		crawlScale.Scale = scale
		ropeScale.Scale = scale
	end

	for action, entry in pairs(buttons) do
		local remaining = math.max(
			0,
			(cooldowns[action] or 0) - now
		)

		local active = state.mode == action

		entry.button.BackgroundColor3 = active
			and COLORS.Active
			or remaining > 0 and COLORS.Dark
			or entry.color

		if action == "Sling" and state.mode == "Sling" then
			local charge = math.clamp(
				(now - state.started) / CONFIG.SlingChargeTime,
				0,
				1
			)

			entry.button.Text = string.format(
				"FIRE\n%d%%",
				math.floor(charge * 100)
			)
		elseif remaining > 0 and not active then
			entry.button.Text = string.format(
				"%s\n%.1f",
				action:upper(),
				remaining
			)
		else
			entry.button.Text = entry.text
		end
	end

	if now < noticeUntil then
		status.Text = notice
	elseif state.mode == "Swing" then
		status.Text = "FREE SWING • Release to launch • Adjust rope below"
	elseif state.mode == "Climb" then
		status.Text = "CRAWL • WASD / arrows • SPACE to leap"
	elseif state.mode == "Sling" then
		status.Text = "CHARGING • Tap SLING / F again to launch"
	elseif state.mode == "Pull" then
		status.Text = "AIR WEB PULL • Release to detach"
	elseif state.mode == "Zip" then
		status.Text = "AIR WEB ZIP"
	else
		status.Text = "PROJECT SPIDER V3 • FREE SWING"
	end
end)

local function bindCharacter(newCharacter)
	characterGeneration += 1
	local generation = characterGeneration

	stopMode()
	clearInputs()
	table.clear(cooldowns)

	if deathConnection then
		deathConnection:Disconnect()
		deathConnection = nil
	end

	character, humanoid, root = nil, nil, nil

	local newHumanoid = newCharacter:WaitForChild("Humanoid", 10)
	local newRoot = newCharacter:WaitForChild("HumanoidRootPart", 10)

	if not running
		or generation ~= characterGeneration
		or player.Character ~= newCharacter
		or not newHumanoid
		or not newRoot then
		return
	end

	character = newCharacter
	humanoid = newHumanoid
	root = newRoot

	deathConnection = humanoid.Died:Connect(function()
		stopMode()
		clearInputs()
	end)
end

connect(player.CharacterAdded, bindCharacter)

connect(player.CharacterRemoving, function(removingCharacter)
	if removingCharacter == character then
		characterGeneration += 1
		stopMode()
		clearInputs()
		character, humanoid, root = nil, nil, nil
	end
end)

local function cleanup()
	if not running then return end

	running = false
	characterGeneration += 1

	stopMode()
	clearInputs()

	if deathConnection then
		deathConnection:Disconnect()
		deathConnection = nil
	end

	for _, connection in ipairs(connections) do
		connection:Disconnect()
	end

	table.clear(connections)
end

connect(shutdown.Event, function()
	cleanup()
	gui:Destroy()
end)

connect(gui.Destroying, cleanup)
connect(script.Destroying, function()
	cleanup()
	gui:Destroy()
end)

if player.Character then
	task.spawn(bindCharacter, player.Character)
end

Features & Information

BECOME THE ROBLOXIANS ONLY SPIDERMAN

Community Comments (0)

No comments yet for this script.

More Scripts for Universal Scripts