Sqaure flood effect help

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

  1. 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

  2. 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

I’m guessing you mean an effect similar to the one described in this After Effects tutorial? How to Create Square Transitions in After Effects Tutorial - YouTube

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!

1 Like

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:

image

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
  • built in tweening api?
2 Likes