How to make Moving Gradient

Wowozers this looks very cool. Is there any way to reduce the amount of repeated code in the script maybe using tweens or for loops?

You can probably do something like this for the same effect with easier configuration (there’s better ways to make it smoother as well).

    local ScreenGui = script.Parent
    local TextLabel = ScreenGui.TextLabel
    local UIGradient = TextLabel.UIGradient

    local GradientColors = {
    	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
    }

    while wait(0.1) do
    	local tVal = GradientColors[1]
    	table.move(GradientColors, 2, #GradientColors, 1)
    	GradientColors[#GradientColors] = tVal

    	local transitColors = {}
    	for seq, color in pairs (GradientColors) do
    		table.insert(transitColors, ColorSequenceKeypoint.new(((seq-1))/(#GradientColors-1), color))
    	end
    	UIGradient.Color = ColorSequence.new(transitColors)
    end
6 Likes

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

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.

2 Likes

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

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.

4 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).

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)
5 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:

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)

1 Like

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

https://developer.roblox.com/en-us/api-reference/property/UIGradient/Offset

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.

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

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!

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

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

Could u elaborate on your question

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.

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

1 Like

You can refer to this comment How to make Moving Gradient