Ball starts spinning and glitching after getting it's position set to a part

I want it so that when the ball reaches “Spot” for it to stop moving and just be there.

The issue is that the ball starts spinning around and glitching after it reaches the spot. It’s moving it to the CFrame of the Spot.

At first I tried to keep the ball up there by constantly teleporting it to the Spot’s position but that was weird so I removed it. The idea I got was to bring the ball to the Spot and then anchor the ball once it has reached the spot. But for some reason. It gets anchored and I don’t think it’s constantly being held or teleported for it to get that weird spinning and glitching effect. So can somebody please help me out?

local magnet = script.Parent – The hitbox part
local ballsFolder = workspace:WaitForChild(“Balls”)
local TweenService = game:GetService(“TweenService”)
local Debris = game:GetService(“Debris”)

– Configuration parameters
local tweenDuration = 1 – Seconds for the smooth transfer (tween)
local launchCooldown = 5 – Seconds before the magnet can affect this ball again
local launchSpeed = 30 – Horizontal launch impulse (studs/sec)

– Get the Spot part (should be in the same model as the hitbox, but not inside it)
local model = magnet.Parent
local spot = model:FindFirstChild(“Spot”)
if not spot then
warn(“Spot part not found in the model!”)
return
end

– Tables to track state for each ball
local cooldownBalls = {} – ballModel → cooldown expiration time
local heldBalls = {} – ballModel → { tween, launchConn, toggleEvent, player }

– Helper: Given a part, find its ball model (first ancestor whose parent is workspace.Balls)
local function getBallModel(part)
local current = part:FindFirstAncestorWhichIsA(“Model”)
while current and current.Parent and current.Parent ~= ballsFolder do
current = current.Parent
end
return current
end

local function holdBall(ballModel)
if cooldownBalls[ballModel] and tick() < cooldownBalls[ballModel] then
return – Ball is still on cooldown
end
if heldBalls[ballModel] then
return – Already processing this ball
end

-- Always set the PrimaryPart to the CollisionBall from ball.Exterior.
local exterior = ballModel:FindFirstChild("Exterior")
if exterior then
	local collisionBall = exterior:FindFirstChild("CollisionBall")
	if collisionBall then
		ballModel.PrimaryPart = collisionBall
	end
end
if not ballModel.PrimaryPart then
	warn("Ball model " .. ballModel.Name .. " has no PrimaryPart (CollisionBall).")
	return
end

-- Get required RemoteEvents and the owning player.
local eventsFolder = ballModel:FindFirstChild("Events")
local toggleEvent = eventsFolder and eventsFolder:FindFirstChild("ToggleInputControlsRemoteEvent")
local launchEvent = eventsFolder and eventsFolder:FindFirstChild("LaunchBallRemoteEvent")
local playerValue = ballModel:FindFirstChild("Player")
local player = (playerValue and playerValue.Value) or nil

-- Disable the ball's own input controls so its movement doesn't interfere.
if toggleEvent and player then
	toggleEvent:FireClient(player, false)
end

local part = ballModel.PrimaryPart
part.Anchored = true  -- Anchor to take full control of movement

-- Create a tween to smoothly move the ball (including upward) to the Spot.
local tweenInfo = TweenInfo.new(tweenDuration, Enum.EasingStyle.Quad, Enum.EasingDirection.Out)
local tweenGoal = { CFrame = spot.CFrame }
local tween = TweenService:Create(part, tweenInfo, tweenGoal)

heldBalls[ballModel] = {
	tween = tween,
	launchConn = nil,
	toggleEvent = toggleEvent,
	player = player,
}

tween:Play()
tween.Completed:Connect(function()
	-- Once the tween completes, explicitly set the ball exactly at Spot and anchor it.
	part.CFrame = spot.CFrame
	part.Anchored = true
	print("DEBUG: Ball " .. ballModel.Name .. " reached the spot and is now anchored.")
end)

-- Listen for the launch event from the client.
if launchEvent and player then
	local conn
	conn = launchEvent.OnServerEvent:Connect(function(plr)
		if plr == player and heldBalls[ballModel] then
			if conn then conn:Disconnect() end

			-- Unanchor the ball and apply a horizontal impulse.
			part.Anchored = false
			local forward = part.CFrame.LookVector
			forward = Vector3.new(forward.X, 0, forward.Z).Unit
			local bv = Instance.new("BodyVelocity")
			bv.Name = "LaunchImpulse"
			bv.MaxForce = Vector3.new(1e5, 0, 1e5)  -- Only affect horizontal motion.
			bv.Velocity = forward * launchSpeed
			bv.Parent = part
			Debris:AddItem(bv, 0.1)

			-- Re-enable the ball's input controls.
			if toggleEvent and player then
				toggleEvent:FireClient(player, true)
			end

			heldBalls[ballModel] = nil
			cooldownBalls[ballModel] = tick() + launchCooldown
		end
	end)
	heldBalls[ballModel].launchConn = conn
end

end

local function onTouched(otherPart)
local ballModel = getBallModel(otherPart)
if ballModel and ballModel:IsDescendantOf(ballsFolder) then
holdBall(ballModel)
end
end

magnet.Touched:Connect(onTouched)

Setting the CFrame of a Part, either directly from Lua or indirectly using TweenService, is not a physics-engine-aware movement, it’s basically a teleport. If the assembly inside your hamster ball thing is set up with Roblox physics system constraints, having them go nuts when you teleport the Model is expected. You can generate crazy high linear and angular velocities from the previous frame to the current, which can send things flying or spinning out of control.

The correct way to do this is to use physics engine constraints on the model’s primary part, like AlignPosition and AlignOrientation, or the older BodyPosition and BodyGyro. You want to animate the CFrame property of those constraints, not the Part directly. This should fix it, as the physics engine will move the model towards the goals you set and (usually) keep things stable. There will still be properties of these constraints (like max force and damping) that you can set too high and cause things to go crazy, but you have control over these.

TweenService is for moving GUI and cosmetic non-collidable stuff, not really meant for physically simulated parts in the 3D world, though you can sometimes get it to work if you tween the goals indirectly.

2 Likes

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.