How to make Moving Gradient

The problem with your script is that ColorSequnce one has the position from 0 -1. Your position number are increasing

1 Like

ColorSequences are required to have a time of 0.0 and 1.0 otherwise it won’t run, this one will automatically determine time positions depending on how many colors you assign to the GradientColors list (as long as it’s 2 or more, the amount needed for keypoints).

It works just fine.

3 Likes

O wow, i didnt know that. TY :slight_smile:

1 Like

One smoother option is that you could manually calculate the color of each keypoint every frame, which is complex but probably not a bad solution. Here’s a working implementation (sorry formatting is a little stuffed up mb):

local colors = {
    BrickColor.new("Bright red").Color,
    BrickColor.new("Bright orange").Color,
    BrickColor.new("Bright yellow").Color,
    BrickColor.new("Bright green").Color,
    BrickColor.new("Bright blue").Color,
    BrickColor.new("Royal purple").Color
}

local num_colors = #colors
local color_length = 1 / num_colors
local period = 3 -- how long it takes for full animation

game:GetService("RunService").RenderStepped:Connect(function()
   local progress = (tick() % period) / period -- ranges from 0 to 1 based on progress
   local newColors = {}
	local wrapColor = false -- the color at the edges
	
	for i = 1, num_colors + 1 do -- add 1 for extra edge
	    local color = colors[i] or colors[i-num_colors] -- accounting for +1
        local position = progress + (i-1)/num_colors -- the current color's position

		if position > 1 then position = position - 1 end -- wrap positions
		if position == 0 or position == 1 then wrapColor = true end -- the color is at edge
		
		table.insert(newColors, ColorSequenceKeypoint.new(position, color))
   end

	if not wrapColor then -- if there were no clean matches on the edge
		local indexProgress = ((1 - progress) / color_length) + 1
		local col1 = colors[math.floor(indexProgress)]
		local col2 = colors[math.ceil(indexProgress)] or colors[1]
		local finalCol = col1:Lerp(col2, indexProgress % 1) -- find the color between these two cols
		
		table.insert(newColors, ColorSequenceKeypoint.new(0, finalCol))
		table.insert(newColors, ColorSequenceKeypoint.new(1, finalCol))
	end
	
	table.sort(newColors, function(a, b)
		return a.Time < b.Time
	end)
	
	script.Parent.Frame.UIGradient.Color = ColorSequence.new(newColors)
end)

Alternatively, if you’re tweening a frame background and not text itself, you could treat this like the infinitely scrolling background image problem (example How to make moving background scroll infinitely?). The solution here is to tween both of the gradient frames in one direction, then reset their positions and repeat.

local frame1, frame2
local tweenInfo = TweenInfo.new(1, Enum.EasingStyle.Linear)
local tween1 = game:GetService('TweenService'):Create(frame1, tweenInfo, {Position = UDim2.new(1, 0, 0, 0})
local tween2 = game:GetService('TweenService'):Create(frame2, tweenInfo, {Position = UDim2.new(0, 0, 0, 0})
tween1.Completed:Connect(function()
   tween2:Cancel() -- make sure the other tween isn't running
   frame1.Position = UDim2.new(0, 0, 0, 0)
   frame2.Position = UDim2.new(-1, 0, 0, 0)
   tween1:Play()
   tween2:Play()
end)
frame1.Position = UDim2.new(0, 0, 0, 0)
frame2.Position = UDim2.new(-1, 0, 0, 0)
tween1:Play()
tween2:Play()

In this example, frame1 is the default frame you see at the start of the animation, and frame2 is to the left of it, hidden by the ClipDescendants property. Both tween to the right, and then when the animation is done it resets their positions to the default positions and tweens them to the right again.

13 Likes

That is not what the OP is asking for it to look like, though. You cannot do that while showing all of the colors at once like the OP wants as there is no way to show a gradient twice within a single gradient in that way. One idea I had was putting the gradient into the ColorSequence twice, but that still won’t work as we can’t scale a gradient’s size (in this case to be double its normal size).

1 Like

Just to add on, I proposed a solution that takes up a lot less work with smooth results (and is what I use currently). I talk more about it here if you are interested:

But as a rundown, you can just use sinusoidals to generate rainbow (and other repetitive) colour sequences. And due to them being sinusoidals, you can offset them by a phase shift which can be updated every frame. The code for this is below, it’s also in the post I made above.

I didn’t come up with this myself, I read about it here. It’s a long read (and engaging!) but it will help you understand where the code is coming from and what the technique is really doing if you wish to take a look.

local RS = game:GetService("RunService")

local rainbow = script.Parent  -- GUI object
local grad = rainbow.UIGradient

local counter = 0       -- phase shift 
local w = math.pi / 12  -- frequency 
local CS = {}           -- colorsequence table
local num = 15 			-- number of colorsequence points (maxed at 15 or 16 I believe)
local frames = 0		-- frame counter, used to buffer if you want lower framerate updates

RS.Heartbeat:Connect(function()
	if math.fmod(frames, 2) == 0 then
		for i = 0, num do
			local c = Color3.fromRGB(127 * math.sin(w*i + counter) + 128, 127 * math.sin(w*i + 2 * math.pi/3 + counter) + 128, 127*math.sin(w*i + 4*math.pi/3 + counter) + 128)
			table.insert(CS, i+1, ColorSequenceKeypoint.new(i/num, c))
		end
		grad.Color = ColorSequence.new(CS)
		CS = {}
		counter = counter + math.pi/40
		if (counter >= math.pi * 2) then
			counter = 0
		end
	end
	if frames >= 1000 then
		frames = 0
	end
	frames = frames + 1
end)
17 Likes

This really works well, but its moving right to left, I want it to move left the right

All you have to do is subtract counter inside math.sin instead of adding it. Glad it works well for you :slight_smile:

1 Like

You can also do this with UIGradient’s offset property. Just flip the rotation every once in a while and it’s way easier and shorter than anything posted here.

Code:
(could probably be cleaned up but I made it in like 5 minutes)

local speed = 1
rs.RenderStepped:Connect(function(dt)
	gradient.Offset = Vector2.new(gradient.Offset.X + (speed*dt), 0)
	if gradient.Offset.X >= 1 then
		local isRotated = (gradient.Rotation == 180)
		gradient.Rotation = isRotated and 0 or 180; gradient.Offset = Vector2.new(-1, 0) --isRotated and -0.5 or -1
	end
end)

10 Likes

This is similar to the one provided by roblox in documentation.

UIGradient | Documentation - Roblox Creator Hub

In all honesty, everything that’s been discussed previously on this thread can be accomplished using the RenderStepped loop provided by roblox without taking an entire day of concentration as stated by @Ashp116.

No it took a day of full concentration to make it

A lot of the code that’s been provided sort of complicates things and could potentially cause more client side stress especially because a lot of the loops I’ve seen been being used are loops such as:

while wait() do
    -- code
end

So unless I’m misunderstanding what the purpose of the thread is or what’s trying to be accomplished I suggest just using the link I provided above to create your own moving gradient script with a renderStepped loop that will not stress the client and is very easy to understand.

1 Like

Is there a way to make it serversided? If it’s put in a local script only the local player can see it.

1 Like

Yes use remove event
Do this:

game.ReplicatedStorage.Event.OnClientEvent()
local speed = 1
rs.RenderStepped:Connect(function(dt)
	gradient.Offset = Vector2.new(gradient.Offset.X + (speed*dt), 0)
	if gradient.Offset.X >= 1 then
		local isRotated = (gradient.Rotation == 180)
		gradient.Rotation = isRotated and 0 or 180; gradient.Offset = Vector2.new(-1, 0) --isRotated and -0.5 or -1
	end
end)
end)

And is server do

game.ReplicatedStorage.Event:FireAllClients()

Hope it helps!

2 Likes

I did this in a localscript:

game.ReplicatedStorage.Event.OnClientEvent()
local speed = 1
local gradient = script.Parent.UIGradient
local rs = game:GetService("RunService")
rs.RenderStepped:Connect(function(dt)
	gradient.Offset = Vector2.new(gradient.Offset.X + (speed*dt), 0)
	if gradient.Offset.X >= 1 then
		local isRotated = (gradient.Rotation == 180)
		gradient.Rotation = isRotated and 0 or 180; gradient.Offset = Vector2.new(-1, 0) --isRotated and -0.5 or -1
	end
end)

And this in the script:
game.ReplicatedStorage.Event:FireAllClients()
Both are under a nametag script in serverscriptservice under the textlabels
but the rainbow isn’t showing up

2 Likes

How hard is it to make it between a select few colors? Like Red, Dark Red, Light Red. Or White, Black, Gray

1 Like

Could u elaborate on your question

1 Like

I mean as simple as making the text black and white gradient instead of a rainbow gradient? Just wondering how hard it would be to make it a black and white pattern.

1 Like

one of the easiest ways to do that would probably be just using the one he shows in the post but with only black and white

2 Likes

You can refer to this comment How to make Moving Gradient - #16 by vastqud

1 Like

question to our pro’s:
how can i extend (width) the gradient and back again?
I am currently using this script:

local RS = game:GetService("RunService")
local rainbow = script.Parent  -- GUI object
local grad = rainbow.UIGradient

local counter = 0       -- phase shift 
local w = math.pi / 12  -- frequency 
local CS = {}           -- colorsequence table
local num = 15 			-- number of colorsequence points (maxed at 15 or 16 I believe)
local frames = 0		-- frame counter, used to buffer if you want lower framerate updates

RS.Heartbeat:Connect(function()
	if math.fmod(frames, 2) == 0 then
		for i = 0, num do
			local c = Color3.fromRGB(127 * math.sin(w*i + counter) + 128, 127 * math.sin(w*i + 2 * math.pi/3 + counter) + 128, 127*math.sin(w*i + 4*math.pi/3 + counter) + 128)
			table.insert(CS, i+1, ColorSequenceKeypoint.new(i/num, c))
		end
		grad.Color = ColorSequence.new(CS)
		CS = {}
		counter = counter + math.pi/40
		if (counter >= math.pi * 2) then
			counter = 0
		end
	end
	if frames >= 1000 then
		frames = 0
	end
	frames = frames + 1
end)
1 Like

I am not sure what your question is. But if you are asking how to increase the time it takes to change to another color, then maybe change the w variable or counter variable.

1 Like