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

What do you want to achieve? Square flood fill effect, Essentially a background that on one end start shrinking into a smaller square which propagates in a wave like fashion Until the entire background is gone

What is the issue? I have a basic idea of the script, But no way to get the amount to loop on each different screen size plus i use multi frame method which I will use won’t be efficient as It require a lot of frame, Is there anyway that I could solve both of this problem

The performance will be fine with many frames, unless you’re talking about ViewportFrames or something.

As for the other part of your question, you should generate the squares programmatically.

You can use a for loop to do so.

You can choose, for instance, the number of squares you want width-wise, regardless of screen size:

local columns = 10

And calculate the number of squares you would need height-wise, based on the dimensions of your screen (you probably want to turn on IgnoreGuiInset, or deal with it manually):

local screen = -- ... some ScreenGui (probably this LocalScript's parent)
-- calculate the width (and thus height) of each square
local squareSize = screen.AbsoluteSize.X / columns
-- ... and use that to figure out the minimum number of squares you need to cover the screen
local rows = math.ceil(screen.AbsoluteSize.Y / squareSize)

Now you know how big your squares should be, and how many you need. Use a nested for loop to generate them:

for row = 1, rows do
for col = 1, columns do
-- calculate the position based on which row and column we're in
-- note that this gives us the position of the center of the square...
local x = (col - 0.5) * squareSize
local y = (row - 0.5) * squareSize
local f = Instance.new("Frame")
-- ...so that we can set its anchor in the middle for easy resizing
f.AnchorPoint = Vector2.new(0.5, 0.5)
f.Size = UDim2.fromOffset(squareSize, squareSize)
f.Position = UDim2.fromOffset(x, y)
f.BackgroundColor = BrickColor.Random()
f.Parent = screen
end
end

That’s a good place to start. If you can get that far, and share what you have, I’m happy to answer more questions you have!

Got bored and had some time… here’s a fancier method. I’ll say that this is way overkill and a simpler method is better for most people and easier to maintain. Anyways.

You do what I described above, but also project the positions of the squares along a line with a dot product. Scale that to a range from 0 to 1.

Then you can use those projected positions as an input to a function like this:

Then map that to the square’s sizes.

Tie everything to a NumberValue so you can tween it and you have a nice transition:

LocalScript/usage:

local WaveTransition = require(script.WaveTransition)
-- create transition object inside a ScreenGui
local t = WaveTransition.new(script.Parent)
-- create dummy number value so we have something to tween
local v = Instance.new("NumberValue")
-- update the transition with the number value
v.Changed:Connect(function(value) t:Update(value) end)
-- tween the number value (and thus the transition)
local info = TweenInfo.new(1, Enum.EasingStyle.Linear, Enum.EasingDirection.InOut, -1, true)
local tween = game:GetService("TweenService"):Create(v, info, { Value = 1 })
tween:Play()
tween.Completed:Wait()

Module:

--!strict
local WaveTransition = {}
WaveTransition.__index = WaveTransition
local TYPEDEF_ONLY = {} -- hack for roblox type system
-- Options for creating a transition object
type WaveTransitionParams = {
color: Color3?; -- color of squares (default white)
width: number?; -- number of squares widthwise (default 10)
waveDirection: Vector2?; -- direction vector of the wave (default (1,1))
waveFunction: ((x: number, t: number) -> number)? -- shape of the wave (default below)
}
-- this function: https://www.desmos.com/calculator/linp7zw3sa
local function DefaultWaveFunction(x: number, t: number): number
local l = 0.5 -- length of wave
local edge = t * (1 + l)
if x < edge - l then return 1
elseif x > edge then return 0
else return 0.5 + 0.5 * math.cos(math.pi / l * (x - edge + l))
end
end
-- Creates a new transition object with the given (optional) parameters
-- container should probably be a ScreenGui with IgnoreGuiInsets turned on
function WaveTransition.new(container: GuiBase2d, params: WaveTransitionParams?)
local width = params and params.width and params.width or 10
-- compute number of rows needed to cover screen
local size = container.AbsoluteSize.X / width
local height = math.ceil(container.AbsoluteSize.Y / size)
local self = {
squares = table.create(width*height) :: {GuiObject},
percents = table.create(width*height) :: {number},
color = params and params.color and params.color or Color3.new(1, 1, 1),
width = width,
height = height,
size = size,
waveDirection = params and params.waveDirection and params.waveDirection or Vector2.new(1, 1),
waveFunction = params and params.waveFunction and params.waveFunction or DefaultWaveFunction
}
if container ~= TYPEDEF_ONLY then -- type system hack
local shortest = math.huge
local longest = -math.huge
for r = 1, self.height do
for c = 1, self.width do
local pos = Vector2.new((c-1) * self.size, (r-1) * self.size)
local f = Instance.new("Frame")
f.AnchorPoint = Vector2.new(0.5, 0.5)
-- we add 1 so there's overlap between frames (borders wouldn't let us make a 0,0 size)
f.Size = UDim2.fromOffset(self.size + 1, self.size + 1)
f.Position = UDim2.fromOffset(pos.X + self.size/2, pos.Y + self.size/2)
f.BackgroundColor3 = self.color
f.BorderSizePixel = 0
f.Parent = container
-- project 2d position along direction line
local percent = self.waveDirection:Dot(pos)
if percent < shortest then shortest = percent end
if percent > longest then longest = percent end
local idx = (r - 1) * self.width + c
self.squares[idx] = f
self.percents[idx] = percent
end
end
-- remap projected positions on a scale from 0 to 1
for i, s in ipairs(self.squares) do
self.percents[i] = (self.percents[i] - shortest) / (longest - shortest)
end
end
setmetatable(self, WaveTransition)
return self
end
-- (private) updates a single square based on its position along the direction vector
-- using the wave function
-- alpha: 0 to 1
function WaveTransition:_UpdateOne(square: GuiObject, percent: number, alpha: number)
local self: WaveTransition = self
local t = self.waveFunction(percent, alpha)
square.Size = UDim2.fromOffset(t * (self.size + 1), t * (self.size + 1))
end
-- Sets the wave's progress
-- alpha: from 0 to 1
function WaveTransition:Update(alpha)
local self: WaveTransition = self
for i, square in ipairs(self.squares) do
self:_UpdateOne(square, self.percents[i], alpha)
end
end
-- Destroys all GUI objects and frees their memory
-- The object is invalid and unusable after
function WaveTransition:Destroy()
local self: WaveTransition = self
for _, s in ipairs(self.squares) do
s:Destroy()
end
self.squares = nil :: any
self.percents = nil :: any
end
-- hack bc roblox doesn't like OOP
type WaveTransition = typeof(WaveTransition.new(TYPEDEF_ONLY :: any))
return WaveTransition

Projects for the reader:

2d wave fronts

dynamic resizing e.g. when the window changes size