How do I make a infinite pattern ui background?

Hello, I need help. I am working on a game and I want to add this cool loading menu but the problem is I don’t know how to make a infinite moving pattern gui. Here is what I want:

Kind of like offset with textures but instead with Gui

5 Likes

You can use ImageLabel.TileSize to repeat a texture (make sure to set the ScaleType to Tile for the property to appear), and then you can use a script to slowly animate the object moving. After it moves by 1 whole tile you can shift it back to where it started for seamless looping.

9 Likes

Thanks a lot, i’ve been looking for this all day.

1 Like

you can use ui tiles in the imagelabel

1 Like

I need help, when I did this, it didn’t look seamless, it became larger then back to small and on repeat

local gui = script.Parent
wait(10)
while gui.Active == true do
	for i = 0,1,0.01 do
		print("please work")
		gui.TileSize = UDim2.new(i,100,0,100)
		wait(0)
	end
end

heres my quick code I wrote for this

1 Like

You should animate the Position property instead, so that it will slide around instead of growing and shrinking.

You’ll also want to use the RenderStepped event instead of wait() so that it animates smoothly. wait() only resumes 30 times per second, instead of in lockstep with FPS like RenderStepped. Make sure you put this into a LocalScript as well.

This script assumes you have your tile size set to 0,100,0,100, and that you want the tiles to move towards the right side of the screen. It should be pretty easy to adjust as needed.

local RunService = game:GetService("RunService")

local gui = script.Parent
local distance = 0
local secondsPerCycle = 5 -- How long it takes to cycle through one tile.
local speed = 1 / secondsPerCycle

RunService.RenderStepped:Connect(function(dt)
	distance += dt * speed
	-- This is called the modulo operator and wraps the distance from 0 to 1.
	distance = distance % 1
	-- Subtracts 200 here so that the GUI's left edge stays off the side of the screen. You'll need to make the object larger than 1,0,1,0 to make sure it covers everything.
	gui.Position = UDim2.fromOffset(distance * 100 - 200, 0)
end)

Update (July 2025)

Here’s a more fleshed out version of the script. It supports angles (not pure left-to-right scrolling) and figures out the TileSize automatically instead of assuming 0, 100, 0, 100.

-- Scrolling background script. To use, make a UI hierarchy like this:
--
-- * Frame with ClipsDescendants enabled
-- `-* ImageLabel with ScaleType=Tile
--   `-* LocalScript with this script in it
--
-- The Size of the ImageLabel should be set to a large value like 2,0,2,0.

local RunService = game:GetService("RunService")


-- This is the gui that will be scrolled.
-- By default, this script should be added as a child of it.
local gui = script.Parent

-- How long it takes to cycle through one tile.
local secondsPerCycle = 10

-- The direction angle, in degrees.
-- 0 is up, 90 is right, 180 is down, 270 is left.
-- You can use others like 45, 30, 22.5.
local angle = 90-22.5


-- The amount of distance travelled in both directions
local accumulator = Vector2.zero


RunService.RenderStepped:Connect(function(dt)
	-- Calculate the speed (tiles/second) from the frequency (seconds/tile)
	local speed = 1 / secondsPerCycle
	-- Convert angle to radians
	local angleRad = math.rad(angle)
	-- Convert angleRad to an x/y direction using trigonometry
	local direction = Vector2.new(
		math.sin(angleRad),
		-math.cos(angleRad)
	)


	-- This section calculates the final size of the TileSize property.
	local tileSizeOffset = Vector2.new(
		gui.TileSize.X.Offset,
		gui.TileSize.Y.Offset
	)
	local tileSizeScale = Vector2.new(
		gui.TileSize.X.Scale,
		gui.TileSize.Y.Scale
	)
	local absoluteTileSize = gui.AbsoluteSize * tileSizeScale + tileSizeOffset


	-- `dt * speed` is how far the tile scrolled this tick.
	accumulator += direction * (dt * speed)
	-- Wrap the accumulator from the range 0 to 1 using the modulo (%) operator.
	accumulator = Vector2.new(
		accumulator.X % 1,
		accumulator.Y % 1
	)
	
	-- Position the UI so that the top left corner is always
	-- outside of the clip rect of the parent.
	local position = accumulator * absoluteTileSize - 2.0 * absoluteTileSize

	-- Setting the Position to an Offset size would cause ugly pixel snapping.
	-- So instead, divide the position and set Scale size instead.
	local positionScale = position / gui.Parent.AbsoluteSize
	gui.Position = UDim2.fromScale(positionScale.X, positionScale.Y)
end)

And an RBXM showing a working example:
ScrollingBackground.rbxm (6.1 KB)

25 Likes

Hey sorry for annoying you but, I want to know what dt is or what it stands for

1 Like

It’s the amount of time that’s passed since the last time the event was fired.

3 Likes

Yeah, I just figured that out by printing it ty

2 Likes