Feedback on my simple "Dynamic" smoke I made

So, I decided to make a smoke particle emitter that properly interacts with the world.

What I mean by this is that the smoke will not clip through objects.

It was easy to make, and has probably been done a million times before, but I wanted to just have some fun and make something random.

Here are some clips of it in action:

The code automatically detects when a new smoke object is inserted into workspace, and also starts updating it at the proper rate.

Now, question, should I make this a public asset that anyone can use? I don’t really have a purpose for it.

If I do make this public, do I move the code to be client sided for further optimization?

I have done my best to optimize the code, mainly by changing the update rate depending on how many parts are getting updated. (number of parts / 60)
This number updates every time a new smoke part is detected.


I assume this uses a ray with the lifetime of the particle right? Is there a way to instantly cut off or stop the particles past a certain point without them lingering before disappearing? Pretty cool tho

Yeah, fires a ray upwards with the selected range, and then uses the position difference divided by 2 minus 1 as the particle lifetime.

As for making the particles stop moving up when a part is dropped down onto them, I don’t think that’s possible unless if I start using parts with Billboard GUIs in them as particles instead, which I wont because that’s a terrible idea.

This is the current code, might as well make it public since I just said the math on how the particle lifetime is decided.

if script.Parent ~= game:GetService('ServerScriptService') then local Folder = script.Parent script.Parent = game:GetService('ServerScriptService') Folder:Destroy() end

local UpdateRate = 1/60

local DetectedSmoke = {}
local ActiveSmoke = {}

function SmokeFunctions()
	for i, Smoke in pairs(DetectedSmoke) do
		if not table.find(ActiveSmoke, Smoke) then 
			table.insert(ActiveSmoke, Smoke)
				while task.wait(UpdateRate) do
					local RaycastParamaters =
					RaycastParamaters.RespectCanCollide = true
					local RayOrigin = Smoke.Position
					local RayDirection =, Smoke.MaxRange.Value, 0)
					local Raycast = workspace:Raycast(RayOrigin, RayDirection, RaycastParamaters)
					if not Raycast or not Raycast.Position then Smoke.ParticleEmitter.Lifetime = / 2 - 1) end
					if Raycast and Raycast.Position then
						local Distance = (RayOrigin - Raycast.Position).Magnitude
						Smoke.ParticleEmitter.Lifetime = / 2 - 1)

function CheckForSmoke()
	for i, Smoke in pairs(workspace:GetDescendants()) do
		if Smoke.Name == 'DynamicSmoke' and Smoke:FindFirstChild("MaxRange") and not table.find(DetectedSmoke, Smoke) then
			table.insert(DetectedSmoke, Smoke)
	UpdateRate = #DetectedSmoke/60 or 0