Windows Pipes Screensaver Help

You can write your topic however you want, but you need to answer these questions:

  1. What do you want to achieve?

  1. What is the issue?

I am a bad scripter and i do not know how to do this

  1. What solutions have you tried so far?

I checked yt and the forums but no one made a topic about this

I do not ask for you to design entire systems, i just want a general explanation on how to do this. And (if you want) you can script it but all i would want is a general idea

4 Likes

try to make the first cylinder first on a position that has attachments to each end. Using those attachments make another cylinder which one of its attachments are in the same position of the last and rotate the cylinder randomly. Then repeat how many times you want i think

1 Like


The basic idea is that you’d use cylinders (the long segment) and spheres (at the ends) and give the cylinders random lengths. You’d attach a sphere at the end, and then randomly change direction (in increments of 90 degrees). As well as changing the colour every few pipes drawn.

2 Likes

Hey so I made this script here

-- Enhanced version with continuous animation
local PipeScreensaver = {}

function PipeScreensaver:Start()
	self.pipeColors = {
		Color3.fromRGB(255, 0, 0),    -- Red
		Color3.fromRGB(0, 255, 0),    -- Green
		Color3.fromRGB(0, 0, 255),    -- Blue
		Color3.fromRGB(255, 255, 0),  -- Yellow
		Color3.fromRGB(255, 0, 255),  -- Magenta
		Color3.fromRGB(0, 255, 255),  -- Cyan
	}

	self.boundarySize = Vector3.new(50, 30, 50) -- Area where pipes can generate
	self.currentPosition = Vector3.new(0, 15, 0)
	self.currentDirection = Vector3.new(1, 0, 0)
	self.currentColorIndex = 1
	self.pipeCount = 0
	self.colorChangeFrequency = 5
	self.isRunning = true

	-- Clean up old pipes
	self:Cleanup()

	-- Start the pipe generation loop
	self:GeneratePipes()
end

function PipeScreensaver:Stop()
	self.isRunning = false
end

function PipeScreensaver:Cleanup()
	for _, obj in ipairs(workspace:GetChildren()) do
		if obj.Name == "PipeSegment" or obj.Name == "PipeJoint" then
			obj:Destroy()
		end
	end
end

function PipeScreensaver:CreatePipeSegment(startPos, endPos, color)
	local length = (endPos - startPos).Magnitude
	local centerPos = (startPos + endPos) / 2

	local pipe = Instance.new("Part")
	pipe.Name = "PipeSegment"
	pipe.BrickColor = BrickColor.new(color)
	pipe.Material = Enum.Material.Neon
	pipe.Size = Vector3.new(0.4, 0.4, length)
	pipe.Anchored = true
	pipe.CanCollide = false
	pipe.CFrame = CFrame.lookAt(centerPos, endPos) * CFrame.Angles(math.pi/2, 0, 0)
	pipe.Parent = workspace

	return pipe
end

function PipeScreensaver:CreateJointSphere(position, color)
	local sphere = Instance.new("Part")
	sphere.Name = "PipeJoint"
	sphere.Shape = Enum.PartType.Ball
	sphere.BrickColor = BrickColor.new(color)
	sphere.Material = Enum.Material.Neon
	sphere.Size = Vector3.new(0.6, 0.6, 0.6)
	sphere.Anchored = true
	sphere.CanCollide = false
	sphere.Position = position
	sphere.Parent = workspace

	return sphere
end

function PipeScreensaver:GetRandomDirection(currentDir)
	local possibleDirections = {
		Vector3.new(1, 0, 0),   -- Right
		Vector3.new(-1, 0, 0),  -- Left
		Vector3.new(0, 0, 1),   -- Forward
		Vector3.new(0, 0, -1),  -- Backward
		Vector3.new(0, 1, 0),   -- Up
		Vector3.new(0, -1, 0)   -- Down
	}

	-- Remove opposite direction
	local validDirections = {}
	for _, dir in ipairs(possibleDirections) do
		if dir ~= -currentDir then
			table.insert(validDirections, dir)
		end
	end

	return validDirections[math.random(1, #validDirections)]
end

function PipeScreensaver:IsWithinBounds(position)
	local halfSize = self.boundarySize / 2
	return math.abs(position.X) <= halfSize.X and 
		math.abs(position.Y - 15) <= halfSize.Y and 
		math.abs(position.Z) <= halfSize.Z
end

function PipeScreensaver:GeneratePipes()
	-- Create initial joint
	self:CreateJointSphere(self.currentPosition, self.pipeColors[self.currentColorIndex])

	while self.isRunning do
		local pipeLength = math.random(3, 10)
		local proposedEnd = self.currentPosition + (self.currentDirection * pipeLength)

		-- If out of bounds, change direction
		if not self:IsWithinBounds(proposedEnd) then
			self.currentDirection = self:GetRandomDirection(self.currentDirection)
			proposedEnd = self.currentPosition + (self.currentDirection * pipeLength)
		end

		-- Create pipe and joint
		self:CreatePipeSegment(self.currentPosition, proposedEnd, self.pipeColors[self.currentColorIndex])
		self:CreateJointSphere(proposedEnd, self.pipeColors[self.currentColorIndex])

		-- Update state
		self.currentPosition = proposedEnd
		self.currentDirection = self:GetRandomDirection(self.currentDirection)

		-- Handle color cycling
		self.pipeCount = self.pipeCount + 1
		if self.pipeCount % self.colorChangeFrequency == 0 then
			self.currentColorIndex = (self.currentColorIndex % #self.pipeColors) + 1
		end

		wait(0.2) -- Adjust speed here
	end
end

-- Start the screensaver
PipeScreensaver:Start()

-- Optional: Stop after 30 seconds
-- wait(30)
-- PipeScreensaver:Stop()

but it just does this:


(sped up)

any way you can help?

That is an AI generated script.

You just need to tell whatever AI model you’re using that the rotation/sizing is wrong. It’s probably because cylinders are flipped in Roblox, the XYZ direction don’t correlate correctly to how they actually look. I can’t remember the exact difference, but if you mention it, the model may be able to work it out.

2 Likes

I have asked it to do exactly what you said but it just gave this:

sorry if i sound like im asking for too much

Stop back with your script. I’ll be more than happy to help YOU.

1 Like

im sorry for using ai im not a really good scripter

but i made a new script:

local cylinder = game.Workspace.wait.Cylinder
local attachment = game.Workspace.wait.Circle.Attachment
local circle = game.Workspace.wait.Part
local startattachment = game.Workspace.wait.StartAttachment

function createNewCircle(bottomOrTop)
	bottomOrTop = bottomOrTop or "bottom"
	local newCircle = cylinder:Clone()
	newCircle.Name = "NewCircle"
	newCircle.Parent = game.Workspace
	newCircle.Shape = Enum.PartType.Cylinder
	local cylinderLength = newCircle.Size.Y
	local offset = Vector3.new(0, 0, 0)
	if bottomOrTop == "top" then
		offset = Vector3.new(0, -cylinderLength/2, 0)
	else
		offset = Vector3.new(0, cylinderLength/2, 0)
	end
	newCircle.Position = circle.Position + offset
	return newCircle
end

function createCircleOnTop(cylinderPart)
	local newCircle = circle:Clone()
	newCircle.Name = "newsphere"
	newCircle.Parent = game.Workspace
	local circleRadius = newCircle.Size.Y/2
	newCircle.Position = cylinderPart.Position + Vector3.new(0, cylinderPart.Size.Y/2 + circleRadius - 0.5, 0)
	return newCircle
end

for i = 1, 5 do
	local newCylinder = createNewCircle("bottom")
	local circleOnTop = createCircleOnTop(newCylinder)
	circle = circleOnTop
	wait()
end

that makes a new cylinder and circle but i dont know how i can make them rotate randomly 90 degrees with the circle being offset in the correct position

try CFrame.Angles() and math.rad() like

CFrame.Angles(math.rad(90), 0, 0) -- rotate 90 degrees to X axis

try replacing

newCircle.Position = cylinderPart.Position + Vector3.new(0, cylinderPart.Size.Y/2 + circleRadius - 0.5, 0)

with

newCircle.CFrame = CFrame.new(cylinderPart.Position + Vector3.new(0, cylinderPart.Size.Y/2 + circleRadius - 0.5, 0)) * CFrame.Angles(math.rad(math.floor(math.random(-180, 180) * 10)), math.rad(math.floor(math.random(-180, 180) * 10) ), math.rad(math.floor(math.random(-180, 180) * 10) ))

not sure if this works :stuck_out_tongue:

it works but what difference does this make what was it supposed to do

The pain of grind pays off, if you just keep at it..
You hardly need to apologize to me. Just keep at it.

Thanks where can i learn but i just need help on this topic if you dont mind

Made a few compromises to make this a bit easier.
Multiple angle on cylinders is a bit harder than I thought. :woozy_face:

Pipes
--ServerScript in ServerScriptService
--Or LocalScript in a local location

local RESTART = true
local PIPE_COUNT = 8
local GRID_SIZE = 16
local CELL_SIZE = 4
local TURN_PROBABILITY = 0.3
local UPDATE_INTERVAL = 0.05
local RESTART_INTERVAL = 30

local DIRECTIONS = {
	Vector3.new(1, 0, 0),
	Vector3.new(-1, 0, 0),
	Vector3.new(0, 1, 0),
	Vector3.new(0, -1, 0),
	Vector3.new(0, 0, 1),
	Vector3.new(0, 0, -1),
}

local COLORS = {
	Color3.fromRGB(31, 119, 180),
	Color3.fromRGB(255, 127, 14),
	Color3.fromRGB(44, 160, 44),
	Color3.fromRGB(214, 39, 40),
	Color3.fromRGB(148, 103, 189),
	Color3.fromRGB(140, 86, 75),
	Color3.fromRGB(227, 119, 194),
	Color3.fromRGB(127, 127, 127),
	Color3.fromRGB(23, 190, 207),
}

local function makePositionKey(position)
	return position.X .. "," .. position.Y .. "," .. position.Z
end

local Pipe = {}
Pipe.__index = Pipe

function Pipe.new(occupiedPositions, color)
	local self = setmetatable({}, Pipe)

	self.occupied = occupiedPositions
	self.color = color
	self.parts = {}
	self.hasStarted = false
	self.isStuck = false

	local startPos
	local attempts = 0
	while true do
		startPos = Vector3.new(
			math.random(-GRID_SIZE, GRID_SIZE) * CELL_SIZE,
			math.random(-GRID_SIZE, GRID_SIZE) * CELL_SIZE,
			math.random(-GRID_SIZE, GRID_SIZE) * CELL_SIZE
		)

		local key = makePositionKey(startPos)
		if not occupiedPositions[key] then
			occupiedPositions[key] = true
			break
		end

		attempts = attempts + 1
		if attempts > 100 then
			startPos = Vector3.new(0, 0, 0)
			break
		end
	end

	self.position = startPos
	self.direction = DIRECTIONS[math.random(1, #DIRECTIONS)]

	self:createSphere(startPos)
	self.hasStarted = true

	return self
end

function Pipe:createSphere(position)
	local sphere = Instance.new("Part")
	sphere.Name = "PipeSphere"
	sphere.Shape = Enum.PartType.Ball
	sphere.Size = Vector3.new(1.75, 1.75, 1.75) --2,2,2
	sphere.Position = position
	sphere.Anchored = true
	sphere.CanCollide = false
	sphere.Material = Enum.Material.Neon
	sphere.Color = self.color

	sphere.Parent = workspace
	table.insert(self.parts, sphere)

	return sphere
end

function Pipe:createBlockSegment(startPos, endPos)
	local middle = (startPos + endPos) / 2
	local direction = (endPos - startPos).Unit
	local length = (startPos - endPos).Magnitude

	local segment = Instance.new("Part")
	segment.Name = "PipeSegment"
	segment.Shape = Enum.PartType.Block
	segment.Anchored = true
	segment.CanCollide = false
	segment.Material = Enum.Material.Neon
	segment.Color = self.color

	if math.abs(direction.X) > 0.9 then
		segment.Size = Vector3.new(length, 1, 1)
	elseif math.abs(direction.Y) > 0.9 then
		segment.Size = Vector3.new(1, length, 1)
	else
		segment.Size = Vector3.new(1, 1, length)
	end

	segment.Position = middle
	segment.Parent = workspace
	table.insert(self.parts, segment)

	return segment
end

function Pipe:findValidDirection()
	local directionsToTry = {}
	for i = 1, #DIRECTIONS do
		directionsToTry[i] = DIRECTIONS[i]
	end

	for i = #directionsToTry, 2, -1 do
		local j = math.random(1, i)
		directionsToTry[i], directionsToTry[j] = directionsToTry[j], directionsToTry[i]
	end

	if math.random() > TURN_PROBABILITY then
		for i = 1, #directionsToTry do
			if directionsToTry[i] == self.direction then
				table.remove(directionsToTry, i)
				break
			end
		end
		table.insert(directionsToTry, 1, self.direction)
	end

	for i = 1, #directionsToTry do
		local direction = directionsToTry[i]
		local newPos = self.position + (direction * CELL_SIZE)

		if math.abs(newPos.X) > GRID_SIZE * CELL_SIZE then
		elseif math.abs(newPos.Y) > GRID_SIZE * CELL_SIZE then
		elseif math.abs(newPos.Z) > GRID_SIZE * CELL_SIZE then
		else
			local key = makePositionKey(newPos)
			if not self.occupied[key] then
				return direction, newPos
			end
		end
	end

	return nil, nil
end

function Pipe:update()
	if self.isStuck then
		return false
	end

	local direction, newPos = self:findValidDirection()

	if direction and newPos then
		local oldPos = self.position
		local key = makePositionKey(newPos)
		self.occupied[key] = true
		self:createBlockSegment(oldPos, newPos)

		if direction ~= self.direction then
			self:createSphere(oldPos)
		end

		self.position = newPos
		self.direction = direction
		return true
	else
		if RESTART then
			self:createSphere(self.position)
			self:restart()
			return true
		else
			self:createSphere(self.position)
			self.isStuck = true
			return false
		end
	end
end

function Pipe:restart()
	for i = 1, #self.parts do
		if self.parts[i] and self.parts[i].Parent then
			self.parts[i]:Destroy()
		end
	end
	self.parts = {}
	self.isStuck = false

	local newPos
	local attempts = 0
	while true do
		newPos = Vector3.new(
			math.random(-GRID_SIZE, GRID_SIZE) * CELL_SIZE,
			math.random(-GRID_SIZE, GRID_SIZE) * CELL_SIZE,
			math.random(-GRID_SIZE, GRID_SIZE) * CELL_SIZE
		)

		local key = makePositionKey(newPos)
		if not self.occupied[key] then
			self.occupied[key] = true
			break
		end

		attempts = attempts + 1
		if attempts > 100 then
			newPos = Vector3.new(0, 0, 0)
			break
		end
	end

	self.position = newPos
	self.direction = DIRECTIONS[math.random(1, #DIRECTIONS)]
	self:createSphere(newPos)
end

function Pipe:cleanup()
	for i = 1, #self.parts do
		if self.parts[i] and self.parts[i].Parent then
			self.parts[i]:Destroy()
		end
	end
	self.parts = {}
	self.isStuck = false
end

local PipeSystem = {}
PipeSystem.__index = PipeSystem

function PipeSystem.new()
	local self = setmetatable({}, PipeSystem)

	self.occupiedPositions = {}
	self.pipes = {}
	self.lastUpdate = tick()
	self.lastRestart = tick()
	self.isRunning = true
	self.stuckCount = 0
	self.totalPipes = PIPE_COUNT

	self.folder = Instance.new("Folder")
	self.folder.Name = "PipeSystem"
	self.folder.Parent = workspace

	for i = 1, PIPE_COUNT do
		local colorIndex = ((i - 1) % #COLORS) + 1
		local pipe = Pipe.new(self.occupiedPositions, COLORS[colorIndex])
		table.insert(self.pipes, pipe)
	end

	return self
end

function PipeSystem:updateAll()
	local activePipes = 0

	for i = 1, #self.pipes do
		local pipe = self.pipes[i]
		if pipe:update() then
			activePipes = activePipes + 1
		end
	end

	if not RESTART then
		local stuckPipes = 0
		for i = 1, #self.pipes do
			if self.pipes[i].isStuck then
				stuckPipes = stuckPipes + 1
			end
		end

		if stuckPipes == #self.pipes then
			self.isRunning = false
		end
	end

	return activePipes
end

function PipeSystem:restartAll()
	if not RESTART then
		return
	end

	for i = 1, #self.pipes do
		self.pipes[i]:cleanup()
	end

	self.occupiedPositions = {}
	self.pipes = {}

	for i = 1, PIPE_COUNT do
		local colorIndex = ((i - 1) % #COLORS) + 1
		local pipe = Pipe.new(self.occupiedPositions, COLORS[colorIndex])
		table.insert(self.pipes, pipe)
	end

	self.stuckCount = 0
end

local system = PipeSystem.new()

local RunService = game:GetService("RunService")

local connection
connection = RunService.Heartbeat:Connect(function()
	local currentTime = tick()

	if not system.isRunning and not RESTART then
		return
	end

	if RESTART then
		if currentTime - system.lastRestart >= RESTART_INTERVAL then
			system.lastRestart = currentTime
			system:restartAll()
		end
	end

	if currentTime - system.lastUpdate >= UPDATE_INTERVAL then
		system.lastUpdate = currentTime
		system:updateAll()
	end
end)

Delete the Baseplate.
Drop the script in wherever.

This looks complicated! Thanks for taking your time to help me dude! Very much thanks

1 Like

My first attempts at programming were spinning a box.
It was in assembly, but still. Start with smaller projects and work your way up.
Hit those tutorials if you want to get to know things faster.
I suggest doing everything on this page.. AlvinBlox
It may take you 6 months, but you will have skills.

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