Rope constraints are causing player to spin uncontrollably

I am making a Spider-man swinging game, similar to others, where a rope appears at where you click.

I have an annoying bug where when the player hits a wall, or swings too fast, they spin uncontrollably. Here is a video:

I have tried a bunch of fixes, like changing the density of the part that is attached to the players hand to be bigger, changing the density to be smaller, changing the rope restitution, and others, and still cant find a solution.

Here is my current code, it creates a part that it welds to the players hand, then when you click it creates a rope that attaches to a part at the mouse, and the part at the hand:

local plrs = game:GetService("Players")
local uis = game:GetService("UserInputService")
local rs = game:GetService("RunService")

local plr = plrs.LocalPlayer
local stateType = Enum.HumanoidStateType
local char = plr.Character or plr.CharacterAdded:Wait()
local hum = char:WaitForChild("Humanoid")
local PartA = Instance.new("Part")
local AttachmentA = Instance.new("Attachment")
local AttachmentB = Instance.new("Attachment")
local mouse = plr:GetMouse()
local rope = Instance.new("RopeConstraint")
local testing = true
local handPart = Instance.new("Part")
local testHum = workspace.testhum
local fall = false
local anims = game.ReplicatedStorage.Animations
local randNum = nil
local camToggle = false
local swinging
local chargeCapacity
local physicsOn
local charging

local MAX_LENGTH = 300
local MAX_CAPACITY = 15
local JUMP_POWER = 1000
local CHARGED_MAX_POWER = 4250


local s1
local s2
local swing1Track
local swing2Track
local charge

local hand = char:WaitForChild("RightHand")
local properties = PhysicalProperties.new(2, 0 ,0)
handPart.Parent = hand
handPart.Position = hand.Position
handPart.CanCollide = false
handPart.Transparency = 1
handPart.CustomPhysicalProperties = properties
local const = Instance.new("WeldConstraint")
const.Part0 = handPart
const.Part1 = hand
const.Parent = workspace

local function preloadAnims(folder)
	local desc = folder:GetChildren()
	for i, v in desc do
		print(i)
		local track = hum.Animator:LoadAnimation(v)
		track:Play()
		task.wait()
		track:Stop()
	end
end

local function playAnim(anim)
	local track = hum.Animator:LoadAnimation(anim)
	track:Play(0.35)
	return track
end
preloadAnims(anims)

mouse.Icon = "rbxassetid://83537166843785"

AttachmentA.Parent = PartA
AttachmentA.Position = Vector3.zero
AttachmentB.Parent = handPart
AttachmentB.Position = Vector3.zero
PartA.Parent = workspace
PartA.CanCollide = false
PartA.Anchored = true

local function Web(state)
	if state == "shoot" then
		PartA.Transparency = 1
		PartA.Position = mouse.Hit.Position
		local distance = PartA.Position - char.RightHand.Position
		if distance.Magnitude < MAX_LENGTH then
			hum:ChangeState("Physics")
			physicsOn = true
			rope.Length = distance.Magnitude
			rope.Attachment0 = AttachmentA
			rope.Attachment1 = AttachmentB
			rope.Parent = workspace
			rope.Visible = true
			rope.Restitution = 0.15
			rope.Enabled = true
			rope.Thickness = 0.5
			rope.Color = BrickColor.White()
			print(distance.Magnitude)
			return distance.Magnitude
		end
	else
		rope.Enabled = false
	end
	
end
rs.RenderStepped:Connect(function()
	if camToggle then
		workspace.CurrentCamera.CFrame = CFrame.new(char.Head.CFrame.Position)
	end
end)

uis.InputBegan:Connect(function(input, gpe)
	if gpe then return end
	if input.UserInputType == Enum.UserInputType.MouseButton1 then
		if not charging then
			randNum = math.random(1,2)
			print(randNum)
			swinging = true

			Web("shoot")
			swing1Track = nil
			swing2Track = nil
			if randNum == 1 then
				swing1Track = playAnim(anims:WaitForChild("Swing1Still"))
			else
				swing2Track = playAnim(anims:WaitForChild("Swing2Still"))
			end			
		end
	end
	if input.KeyCode == Enum.KeyCode.LeftShift then
		hum.WalkSpeed *= 2
	end
	if input.KeyCode == Enum.KeyCode.F then
		hum:ChangeState("GettingUp")
		physicsOn = false
	end
	if input.KeyCode == Enum.KeyCode.E then
		if camToggle then
			camToggle = false
			char.Head.Transparency = 0
			plr.CameraMode = Enum.CameraMode.Classic
		else
			camToggle = true
			char.Head.Transparency = 1
			plr.CameraMode = Enum.CameraMode.LockFirstPerson
		end
	end
	if input.KeyCode == Enum.KeyCode.Space then
		if swinging then
			Web("delete")
			swinging = false
			handPart:ApplyImpulse(Vector3.new(0,JUMP_POWER,0))
			print(swing1Track)
			print(swing2Track)
			print(randNum)
			if randNum == 1 then
				swing1Track:Stop()
			else
				print(randNum)
				swing2Track:Stop()
			end
		end
	end
	if input.KeyCode == Enum.KeyCode.LeftControl then
		if not swinging and not physicsOn and hum.FloorMaterial ~= Enum.Material.Air then
			task.wait()
			if hum.FloorMaterial == Enum.Material.Air then return end
			char.HumanoidRootPart.Anchored = true
			charging = true
			charge = playAnim(anims.BuildUp)
			chargeCapacity = 1
			repeat
				task.wait(0.2)
				if chargeCapacity == 0 then return end
				chargeCapacity += 1
			until chargeCapacity == MAX_CAPACITY or chargeCapacity == 0
			print(chargeCapacity)
		end
	end
end)

uis.InputEnded:Connect(function(input)
	if input.UserInputType == Enum.UserInputType.MouseButton1 then
		Web("delete")
		task.wait()
		if swinging then
			swinging = false
			print(swing1Track)
			print(swing2Track)
			print(randNum)
			if randNum == 1 then
				swing1Track:Stop()
			else
				print(randNum)
				swing2Track:Stop()
			end
		end
	end
	if input.KeyCode == Enum.KeyCode.LeftControl then
		if not swinging and not physicsOn and hum.FloorMaterial ~= Enum.Material.Air then
			char.HumanoidRootPart.Anchored = false
			local amount = CHARGED_MAX_POWER/MAX_CAPACITY
			charging = false
			print(amount)
			print(chargeCapacity*amount)
			handPart:ApplyImpulse(Vector3.new(0,chargeCapacity*amount,0))
			charge:Stop()
			chargeCapacity = 0
		end
	end
	if input.KeyCode == Enum.KeyCode.LeftShift then
		hum.WalkSpeed /= 2
	end
end)

hum.FallingDown:Connect(function()
	hum:ChangeState("Running")
end)

Thanks in advance.

I don’t know if it’s the best solution, but have you tried to use a script that decrease the AssemblyAngularVelocity property of the character if they spin too fast?

This script hard cap the angular speed of the character but you can edit it to turn it into a soft cap.

local part = workspace.Part -- Replace this by the HumanoidRootPart of the character.

RunService.Stepped:Connect(function()
	local angularVelocity = part.AssemblyLinearVelocity
	local angularSpeed = angularVelocity.Magnitude

	local newSpeed = math.min(angularSpeed, 6)
	local newangularVelocity = angularVelocity.Unit * newSpeed

	part.AssemblyAngularVelocity = newangularVelocity
end)

I made a small Spiderman game too you need to use body gyro to orientate the character by using align orientation. If you are using rod constraints or rope constraints I do think that just doing it with math and no roblox constraints was the best option when I stopped adding onto my game. But to fix collision while using roblox physics to swing you should just use align orientation to or if your using assembly angular velocity i would just clamp it’s delta to not violently turn too fast.

Video

here is my orientation script

function Swing:PositionHRP(dt)
	if not self.pivot then
		return
	end

	local currPos = self.hrp.Position
----------------------- IMPORTANT PART HERE-----------------------------------
	-- 1) compute rope unit & velocities    
	local ropeUnit   = (self.pivot.Position - currPos).Unit
	local vel        = self.hrp.AssemblyLinearVelocity
	local velAlong   = vel:Dot(ropeUnit)
	local tangential = vel - ropeUnit * velAlong
	local tanSpeed   = tangential.Magnitude


	-- 3) pick your forward vector (with apex guard)
	local forwardVec
	if tanSpeed > APEX_THRESHOLD then
		forwardVec = tangential.Unit
		self.lastForward = forwardVec
	else
		forwardVec = self.lastForward or self.camera.CFrame.LookVector
	end

	-- 4) extract flat yaw direction
	local flatDir = Vector3.new(forwardVec.X, 0, forwardVec.Z)
	if flatDir.Magnitude > 0.1 then
		flatDir = flatDir.Unit
	else
		local lv = self.hrp.CFrame.LookVector
		flatDir = Vector3.new(lv.X, 0, lv.Z).Unit
	end

	-- 5) build the pure‑yaw CFrame
	local yawCF = CFrame.lookAt(currPos, currPos + flatDir, Vector3.new(0,1,0))

	-- 6) build a full rope‑aligned CFrame
	local upVec  = Vector3.new(0,1,0):Lerp(ropeUnit, ROLL_FACTOR)
	local fullCF = CFrame.lookAt(currPos, currPos + forwardVec, upVec)

	-- 7) mix yaw lock with pitch/roll
	local targetCF = yawCF:Lerp(fullCF, TILT_FACTOR)

	-- 8) slerp your HRP into that pose
	self.hrp.CFrame = self.hrp.CFrame:Lerp(targetCF, ROT_LERP_ALPHA)
---------------------------------------------------------------------------------------
	-- 9) FOV boost (unchanged)
	local horSpeed = Vector3.new(vel.X, 0, vel.Z).Magnitude

	-- 10) air‑control: use humanoid.MoveDirection but relative to camera
	if self.humanoid then
		local md = self.humanoid.MoveDirection
		if md.Magnitude > 0 then
			local camCF    = self.camera.CFrame
			local worldDir = camCF.LookVector * md.Z + camCF.RightVector * md.X
			local ctrl     = Vector3.new(worldDir.X, 0, worldDir.Z)
			if ctrl.Magnitude > 0 then
				self.hrp.Velocity = self.hrp.Velocity + ctrl.Unit * controlAccel 
			end
		end
	end

end

If you need me to explain anything im here btw

This does work, the player doesn’t spin when they hit a wall, but now the character spins while swinging normally.


What could I do to fix this?

hmmmm just wondering is the character in platform stand? and also is the align orientation relative to the character or world space?

The character is in the “Physics” mode so that the rope constraint physics work.