§ How to · with Picoo

How to make particle effects in Roblox

By Sametcan Tasgiran, Founder & Developer·Published ·Updated

Particles are the cheapest way to make a Roblox game look expensive, and the fastest way to make it run badly.

Three rules cover almost everything: how bursts are fired, what sequences will accept, and the one multiplication that tells you what you are actually spending.

Correct one-shot bursts

Emit-based impacts that finish their lifetime instead of being destroyed mid-flight.

Valid sequences

Size, transparency and colour curves built to the keypoint rules, so they do not error at runtime.

Budgeted emitters

Rate and Lifetime chosen against a target particle count rather than by eye.

Attachment-based effects

Beams and Trails wired to attachments correctly, including the part that only shows when things move.

Layered VFX

Impact, smoke and spark emitters combined into one reusable effect instance.

Client-side where it belongs

Cosmetic effects rendered locally instead of replicating hundreds of instances.

Files Picoo ships for this prompt

2 files · 175 lines · ~30s · 1 credit

VFXLibrary

Named effects, emitter templates, sequence builders, burst helper.

120 lines

VFXClient

Local playback, pooling, cleanup after lifetime.

55 lines

Sample output: ReplicatedStorage.VFXLibrary

-- ONE-SHOT BURSTS. The wrong version sets Enabled = true, waits, and destroys
-- the part -- which deletes particles that are still mid-flight, so the effect
-- visibly cuts off. Correct: keep Enabled false, Emit(n), and clean up only
-- after the longest possible lifetime has passed.
local function burst(emitter: ParticleEmitter, count: number)
	emitter.Enabled = false
	emitter:Emit(count)
	local maxLife = emitter.Lifetime.Max
	Debris:AddItem(emitter.Parent :: Instance, maxLife + 0.1)
end

-- SEQUENCES have rules the docs state and the editor hides: at least two
-- keypoints, the first at time 0 and the last at time 1. Build them and you
-- get a runtime error, not a warning.
local function fade(startSize: number, endSize: number): NumberSequence
	return NumberSequence.new({
		NumberSequenceKeypoint.new(0, startSize),      -- MUST be time 0
		NumberSequenceKeypoint.new(0.35, startSize * 0.8),
		NumberSequenceKeypoint.new(1, endSize),        -- MUST be time 1
	})
end

-- THE BUDGET. Particles alive at once ~= Rate * Lifetime. An emitter at
-- Rate 200 with Lifetime 3 keeps ~600 particles on screen forever -- and ten
-- of those emitters is what kills frame rate on phones, not "too many parts".
local function aliveCount(e: ParticleEmitter): number
	return e.Rate * e.Lifetime.Max
end

Building particle effects in Roblox

Particles carry more of a Roblox game's perceived quality than almost anything else you can add, which is why they are worth understanding properly rather than dragging in from a free model.

The first thing to get right is how a burst is fired. The intuitive approach — enable the emitter, wait a moment, disable it, destroy the part — produces an effect that visibly cuts off, because destroying the instance deletes every particle it owns regardless of how much lifetime they had left. The correct shape is the opposite: leave the emitter disabled, call Emit with the count you want, and schedule cleanup for after the longest lifetime has expired. You also get an exact particle count that way, instead of one that varies with frame rate.

The second is the sequences. Size, transparency and colour are all curves, and Roblox enforces rules on them that the property editor quietly handles for you: at least two keypoints, the first at time zero, the last at time one. Build one in code that ends at 0.9 and you get a runtime error rather than a warning, which is a confusing failure the first time it happens.

The third is the budget, and it is a single multiplication. Rate times Lifetime is approximately how many particles are alive at any given moment. An emitter at Rate 200 with a three-second lifetime is sustaining around six hundred particles indefinitely — fine on its own, ruinous when eight of them are running near each other on a phone. Knowing that number turns "the game got laggy" into a thing you can budget for before shipping.

After that it is mostly placement. Beams and Trails are attachment-driven, and a Trail only draws while its attachments are moving and separated — two attachments at the same point produce a trail with no width, which looks exactly like a trail that is broken.

See more on the Luau generator, the game builder, or browse the full blog.

Frequently asked

Why does my explosion cut off halfway?+

The emitter or its parent was destroyed while particles were still alive. Destroying the instance removes its particles instantly, no matter how much lifetime they had left. Emit the burst, then delay cleanup by at least the emitter's maximum Lifetime.

Rate or Emit?+

Rate is for continuous effects — smoke, fire, a torch. Emit is for one-shot events — an impact, a level-up, a hit spark. Firing a one-shot by enabling Rate briefly gives you an inconsistent particle count, because how many you get depends on how many frames passed before you turned it off.

Why does my NumberSequence error?+

Sequences need at least two keypoints, the first at time 0 and the last at time 1. Anything else throws at runtime rather than warning, and it is easy to hit when generating curves in code — a loop that ends at 0.9 produces a sequence Roblox rejects.

How many particles is too many?+

Multiply Rate by Lifetime — that is roughly how many are alive at any moment. An emitter at Rate 200 and Lifetime 3 sustains around 600, and a handful of those together is what drops frame rate on phones. Compare that number against a budget instead of judging by eye on a desktop.

Should particles be created on the server?+

Only when every player must see the same thing at the same moment, and even then send an event rather than replicating dozens of instances. Purely cosmetic effects — muzzle flashes, footstep dust, UI sparkles — belong on the client, where they cost nothing in replication.

Why is my Trail invisible?+

Trails only draw when their attachments move, and they need both Attachment0 and Attachment1 set to two different attachments with a gap between them. A trail with both attachments in the same spot has no width and renders nothing.

Related Picoo prompts