4 UIGradient Animations (Including Rainbow!)

Wow, really nice tutorial!
I hope this will be helping other developers who want their game to have a good GUI.
thanks for the help we really appreciated your work :+1:

9 Likes

This is really cool especially since it’s something sort of new to roblox; gradients. You did an amazing job on animations (smooth) and making this open sourced.

I don’t know if this is me or applies to a lot of users; gradients don’t fit “my theme” of user interface. They just don’t look right having them as a button because of the way they look.

7 Likes

This is a really good tutorial

8 Likes

Thank you for this tutorial on gradients! Helped me out a lot.

2 Likes

This is so useful! Thank you for this I’ll be using it for my future games.

2 Likes

This is a great guide! Learned a lot from it.

Although I think there’s a much nicer way to do rainbows (it does require a loop rather than TweenService). For those interested, you can read more about it on this site explaining rainbows and some colour theory behind them, as well as how to implement them (it’s in JavaScript, not Lua). The code carries over quite easily and it’s pretty easy to follow along in case you don’t know JS.

The overhead is fairly simple, if you have a UIGradient all you need to do is the following:

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)

It’s different than how a TweenService implementation would look like, but in a way the update loop every frame is in itself a custom Tweening (it’s linear anyways, so TweenService doesn’t really add functionality). It’s much shorter this way, and if you wanted to get a hover effect that changes directions, all you need to do is set up a state system (again, different from TweenService - instead of hovering calling a certain Tween into action, we make a custom state system using booleans or by changing the variables directly if they’re in the scope). This would involve something like:

rainbow.MouseEnter:Connect(function()
    w = -math.pi/12
end)
rainbow.MouseLeave:Connect(function()
    w = math.pi/12
end)

And thus you get the same hover effect. I think it’s a lot cleaner for how little code is required (thanks to sinusoidals). I still think the Tweening method is more elegant, maybe I’ll look into optimizing that so it takes less code to write. Although I’m thinking it’s probably within reach since TweenService has a sine easing style…

Looks like I have some work to do.

13 Likes

Today I learned we need a lot more 10 bit displays.

3 Likes

Will we be expecting a module with alot of UIgradient animations?

3 Likes

Interesting idea. I did think of this before but I thought that there were so many possibilities and ways to customize them that having preset ones in a module. Plus, these animations are mostly easy and they’re in a tutorial so everyone should be able to know how to create them. However, I suppose that I can maybe create such a module.

3 Likes

Don’t mind if i do. Yoink! Thanks for that

3 Likes

Wow, this is amazing! Wasn’t looking for this, but now I’ll be sure to use this for future projects!

2 Likes

At first, I would like to thank you for this awesome idea. This is really cool and might help me earn x2.

I’m facing a problem with HoverStay. The script is same as yours, but-

create1.Completed:Connect(function() 
	if rot == 0 then rot = 180 elseif rot == 180 then rot = 0 end
	completed()	-- Here, the completed word is underlined and it says that "W001: Unknow global 'completed' "
end)

It works when it’s hovered for the first time, but after it stops - it doesn’t work anymore.

Would be really helpful if you could help. Anyways, if anyone wanna see it live in a game, play this game or watch a GIF at gyazo

I also made a HoverShine effect.

1 Like

Cool tutorial! I learned a few things from this!

2 Likes

I really loved @tralalah’s code, but for some reason it was taking up about 0.3% in the script performance analyzer for a single instance every few hundred milliseconds. I decided to pre-cache all of the ColorSequenceKeypoints, since they repeat, and minimize the actual loop that runs during the heartbeat. I also added some more comments to help those who are more beginners understand what is going on. I am going to expand this to be a general “rainbowifier” where you can inject any UIGradient into the process. It should be pretty trivial to make all gradients animate the same or set a random offset and have them all be different. I will post it to this thread when I finish it.

local RS = game:GetService("RunService")

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

local counter = 0       -- phase shift {def = 0}
local w = math.pi / 6  -- frequency [the lower the number the tighter the rainbow] {def = 12}
local CS = {}           -- colorsequence table
local num = 15 			-- number of colorsequence points (maxed at 16) [provides a more granular rainbow] {def = 15}
local frames = 0		-- frame counter, used to buffer if you want lower framerate updates {def = 0}

local count = 0
local cskCache = {}

while true do
	-- build a ColorSequenceKeyPoint 
	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
	local newCS = ColorSequence.new(CS)
	
	-- build the cache	
	if #cskCache > 0 then
		if newCS == cskCache[1] then
			CS = {}
			break
		end
	end
	table.insert(cskCache, newCS)
	
	-- clear out the CS table	
	CS = {}
	
	-- this counter effectively sets the total cache to 80 ColorSequences (countStart = 0, countIncrement = math.pi/40, countMax = 2*math.pi)
	counter = counter + math.pi/40
	if (counter >= math.pi * 2) then counter = 0 end	
end

local finalCacheCt = #cskCache
local rotation = 1

RS.Heartbeat:Connect(function()	
	if math.fmod(frames, 2) == 0 then
		-- set the new gradient.
		grad.Color = cskCache[rotation]		
		-- reset rotation once we have iterated through all the frames		
		if rotation >= #cskCache then rotation = 0 end
		-- move to the next frame
		rotation = rotation + 1				
	end
	-- controls when the frame fires based off the fmod() above. We reset to prevent the number from endlessly increasing
	if frames >= 1000 then frames = 0 end
	frames = frames + 1
end)
3 Likes

The loop itself is pretty heavy, calling table.insert and resetting the UIGradient by calling a new ColorSequence every 2 frames will take its toll. Thanks for cleaning up the code and expanding on it :slight_smile:


There is one improvement I made in the meantime that I think is quite important for those who don’t want to cache (idk, maybe for memory constraints or something) - reset the color sequence table CS = {} before the for i = 0, num do loop when you are running this on a RunService event. Sometimes if the loop is running behind, a new event will fire before the previous one is finished (which means, CS = {} might not be called before it starts adding new elements to the table). Calling it at the start of the loop is safer and doesn’t change anything performance wise (in theory).

2 Likes

If you guys can’t find the animation you want here, I really recommend this plugin. Great tutorial though! :grinning:

Thanks for sharing this post! I was looking for a rainbow like effect to add into my game and I got inspired! I’ve got some code here to share in case it helps anyone. It assumes you already have a UIGradient with pre-set keypoints and takes those and shuffles them around. It’s not a perfect rainbow effect but looks neat!

    local function splitKeypoint(keypoint, startTime)
    	return ColorSequenceKeypoint.new(startTime, keypoint.Value)
    end

    local function shuffleKeypoints(keypoints)
    	local newColors = {}
    	math.randomseed(os.clock())

    	for i = 1, #keypoints do
    		local j = math.random(i)
    		local startTime = keypoints[i].Time

    		table.insert(newColors, splitKeypoint(keypoints[j], startTime))
    	end

    	return newColors
    end

    local function animate(gradient)
    	local tweenInfo = TweenInfo.new(1, Enum.EasingStyle.Linear, Enum.EasingDirection.Out)
    	local tween = TweenService:Create(gradient, tweenInfo, {Offset = Vector2.new(1, 0)})
    	local keypoints = gradient.Color.Keypoints

    	while true do
    		tween:Play()
    		tween.Completed:Wait()

    		gradient.Offset = Vector2.new(-1, 0)
    		gradient.Rotation = gradient.Rotation == 0 and 180 or 0
    		gradient.Color = ColorSequence.new(shuffleKeypoints(keypoints))
    	end
    end
1 Like

Really good Tutorial !!!
Thanks a lot

how to do that rainbow reverse but shift from right to left instead

maybe do a negative tween?

local tween = TweenService:Create(gradient, tweenInfo, {Offset = Vector2.new(-1, 0)})