Confetti Effect

Hi all!

I made a confetti animation/effect that I’d like you share with you.
It’s a ModuleScript with a function that you can use to Emit particles, you can input the number of particles (pieces of confetti) that you want. If you don’t input anything, it will default to 250.

Here is a demo:

Note that I have a sound included in the Emit function. You can delete this or just simply change the path.

There is also a second argument that you can add, giving the spawn point of the confetti (Udim2). However, this doesn’t change the direction they go (they always go upward). It wouldn’t be that hard to change though!

Here is the link to the module:
click here

Here is the code (not as recommended to copy and paste):

local Confetti = {}
Confetti.__index = Confetti

local TweenService = game:GetService("TweenService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")
local Players = game:GetService("Players")
local player = Players.LocalPlayer

local bonusSound = ReplicatedStorage:WaitForChild("ClientSounds").Bonus

local confettiShapes = {"Square", "Circle", "Triangle", "Diamond"}
local baseColors = {
	Color3.fromRGB(255, 75, 75),
	Color3.fromRGB(75, 255, 75),
	Color3.fromRGB(75, 75, 255),
	Color3.fromRGB(255, 255, 75),
	Color3.fromRGB(255, 75, 255),
	Color3.fromRGB(75, 255, 255)
}

function Confetti.new()
	local self = setmetatable({}, Confetti)
	self.ActiveConfetti = 0
	self.activeEmitters = 0
	return self
end

function Confetti:CreateConfettiPiece(spawnPos)
	local confetti = Instance.new("Frame")
	confetti.Name = "ConfettiPiece"
	local size = math.random(24, 36)
	confetti.Size = UDim2.new(0, size, 0, size)
	confetti.BackgroundTransparency = 1
	confetti.BorderSizePixel = 0
	confetti.AnchorPoint = Vector2.new(0.5, 0.5)

	if spawnPos and typeof(spawnPos) == "UDim2" then
		confetti.Position = spawnPos
	else
		confetti.Position = UDim2.fromScale(math.random(0, 100)/100, math.random(-25, 125)/100)
	end
	
	local baseColor = baseColors[math.random(#baseColors)]
	local brightnessFactor = math.random(95, 105) / 100
	confetti.BackgroundColor3 = Color3.new(
		math.clamp(baseColor.R * brightnessFactor, 0, 1),
		math.clamp(baseColor.G * brightnessFactor, 0, 1),
		math.clamp(baseColor.B * brightnessFactor, 0, 1)
	)

	local gradient = Instance.new("UIGradient")
	gradient.Rotation = math.random(0, 360)
	gradient.Color = ColorSequence.new{
		ColorSequenceKeypoint.new(0, Color3.new(math.random(75, 100)/100, math.random(75, 100)/100, math.random(75, 100)/100)),
		ColorSequenceKeypoint.new(1, Color3.new(math.random(75, 100)/100, math.random(75, 100)/100, math.random(75, 100)/100))
	}
	gradient.Parent = confetti

	local shapeType = confettiShapes[math.random(#confettiShapes)]
	local uiCorner = Instance.new("UICorner")
	if shapeType == "Circle" then
		uiCorner.CornerRadius = UDim.new(1, 0)
	elseif shapeType == "Diamond" then
		confetti.Rotation = 45
	elseif shapeType == "Triangle" then
		confetti.Size = UDim2.new(0, math.random(28, 38), 0, math.random(18, 28))
	end
	uiCorner.Parent = confetti

	TweenService:Create(confetti, TweenInfo.new(0.5, Enum.EasingStyle.Quart, Enum.EasingDirection.InOut), {BackgroundTransparency = 0}):Play()
	return confetti
end

function Confetti:Emit(amount, spawnPos : UDim2)
	if typeof(amount) ~= "number" then amount = 250 end
	local playerGui = player:FindFirstChildOfClass("PlayerGui")
	local screenGui = playerGui:FindFirstChild("ConfettiScreenGui")
	if not screenGui then
		screenGui = Instance.new("ScreenGui")
		screenGui.Name = "ConfettiGui"
		screenGui.Parent = playerGui
		screenGui.IgnoreGuiInset = true
	end

	self.activeEmitters = (self.activeEmitters or 0) + 1

	bonusSound:Play()
	
	for _ = 1, amount do
		local confetti = self:CreateConfettiPiece(spawnPos)
		confetti.Parent = screenGui

		local velocityX, velocityY, gravity = math.random(-300, 300), math.random(-350, -200), 75
		local rotationSpeed = math.random(-300, 300)
		local startTime = tick()

		local connection
		connection = RunService.RenderStepped:Connect(function(deltaTime)
			if not confetti.Parent then
				connection:Disconnect()
				return
			end

			local elapsedTime = tick() - startTime
			local newX = confetti.Position.X.Scale + (velocityX * deltaTime / 400)
			local newY = confetti.Position.Y.Scale + (velocityY * deltaTime / 400) + (0.5 * gravity * elapsedTime^2 / 3000)
			confetti.Position = UDim2.new(newX, 0, math.min(newY, 1.3), 0)
			confetti.Rotation += rotationSpeed * deltaTime + math.sin(elapsedTime * 2) * 0.5

			if newY > 1.3 then
				confetti:Destroy()
				connection:Disconnect()
				self.ActiveConfetti -= 1
			end
		end)

		self.ActiveConfetti = (self.ActiveConfetti or 0) + 1
	end

	coroutine.wrap(function()
		while self.ActiveConfetti > 0 do task.wait(1) end
		self.activeEmitters -= 1
		if self.activeEmitters ~= 0 then return end
		screenGui:Destroy()
	end)()
end

return Confetti

Ask me questions if you have any, I’d be happy to answer.

Thanks,
7eoeb

30 Likes

Thanks for this contribution, will definitely be using it! :slight_smile:

1 Like

It looks great! I’m going to use it in my game, and will be sure you are credited appropriately. Have a great day!

1 Like

Thank you so much.
What is the game link?

1 Like

No worries. The game is unreleased as of now, but I’m nearing completion of it (few more asset related things left). I’ll be sure to let you know once it’s released!

1 Like

It’s out now if you want to check it out. It’s a puzzle game called 4 Pics 1 Word. I’m not really sure how to advertise it so I guess I’ll spend the next little bit figuring that out.

When you get to the end of the game (Lvl. 100), there is your confetti effect and a small message with your credits! It looks something like this:

1 Like

I had a play of your game a noticed something odd with the mouse over events. It’s possible to have 2 letters highlighted simultaneously.
To reproduce, hover over a letter, move to an adjacent letter then return to the original letter and click. The result is the second letter is entered, not the one you are actually hovering over.
Nice game and good to see something educational on here.

1 Like

Hi BadDad2004,

Thanks so much for your feedback. Before I saw this, I released an update that allows players to type in their answers, as I realized clicking was slow and a bit tedious. (though the option to click is still there)

As for the double button issue, I think it’s because the hitbox is equal to the visual button, which isn’t good UI best practice (I’ve now realized). I will be working on fixing that as well as the adjacent hovering problem.

Thank you for the kind words about my game—it does mean a lot to me. Have a lovely day!

Sorry for turning your forum post into an advertisement! @7eoeb

honestly a must-have for any cash grab game. thanks for posting this :folded_hands:

2 Likes