3DParticle - finally, mesh particles

i’ve seen a lot of people say that roblox should add support for 3d particles so you can use meshes instead of flat textures. obviously, there is a performance cost to this, which is probably why roblox hasn’t made it a built in feature, but that won’t stop me.

based on my UI particle module, it tries to replicate the built in particle emitter as closely as possible.

it is fully typechecked, half bug tested, and open source so if anyone wants to fix anything that i messed up then be my guest!

92 Likes

How performant is this? laggy and stutter, or smooth as butter.

1 Like

depends on how many particles you have. in the video, the rate was 20 and it was pretty buttery smooth.

1 Like

in a empty place all runs pretty fine, but i think it should run good and better withouth any collisions so.

its anchored, no cancollide, no cantouch, no canquery. all movement is done via script and there are no physics calculations made on the objects. that being said, it is highly recommended that you use this locally in starterplayer because it will look stuttery on the server

hey! does this use any instance pooler like PartCache?

i thought about some sort of caching system, i might implement it

Great job!
This is a good resource to have, and I believe this would
be awesome if ROBLOX implemented something similar!


For the time being though, there are a number of things I’d love to see done here to improve performance!
Most of these I’m sure your aware of, so feel free to skip over whatever you feel like.

As mentioned by @CleverTango, Having some form of “Caching System” is a step in the right direction!

But you can build on it by making use of WorldRoot:BulkMoveTo(), to quickly set the CFrames of a large number of particles at once.

This part might be a bit overkill, but using Octrees would be a great idea, since not all active particles are going to be visible from long distances, so slowly moves particles at a certain distance from the camera into the cache. doing so would be a great way to improve performance if done correctly!

If you don’t want to go through through the effort of designing an octree system yourself, @Quenty’s got a great resource you can check out here!


This isn’t anything important, but I appreciate that you included Type-Annotation! :pray:

3 Likes

i can see how this would work for the emit function, but i can’t think of a way i would implement it

as for the caching system in general, i think i’ve got something going:

1 Like

I’m not sure if I used the latest version, but here is a modified copy I made which uses :BulkMoveTo()
In-case you would like to check it out for yourself!
3DP.lua (11.0 KB)

ill implement this, pretty neat!

2 Likes

Hello! Great resource! I was wondering if you could list an example of how to use the module. I tried looking though the code and everything but I couldn’t figure out how to use it.
Other than that though This seems great and I will most likely be using it! Thanks!

(Sorry for the trouble)

1 Like

you can create it via script and set its properties just as if it were a normal particle emitter. to actually create it, you do ParticleEmitterClass.new(partToEmitFrom, MeshId, TextureId), which returns the emitter for you to set its properties.

1 Like

I did that but it didn’t seem to do anything when I ran it in-game. I’m sorry for the trouble. Thanks though!

can i see your code? im not sure why its not working

Yeah sure here!

local ParticleSystem = require(game.ReplicatedStorage.ParticleSystem)

ParticleSystem.new(game.Workspace.Part, "rbxassetid://10265263853", "rbxassetid://8444070621")

EDIT: I’m an Idiot. I didn’t know it was disabled by default. My bad!

By any chance can I see the code for the demo in the video? I think that would be cool where like there’s a cave in and you blow it up and rocks fly everywhere.

Sorry for the trouble.

This is super cool! Always wanted to have 3D particles in my games but I couldn’t find a decent way to make that work.

Pretty sure i’m doing something wrong, but I tried to use the module today and I encountered a couple issues:


Particles never being destroyed.wmv (Sorry for the lag, using an old laptop for a few days and on top of that I used the roblox recorder)

As seen in the vid, for some reason the parts are never being removed upon calling :Destroy()? Not only that but they seem to become visible after they are done playing, and they just kind of stay static in the air

here's the code I used
local function WallSlideParticle(Bool)
	if Bool == true then
		WallSlideEffectPart = Instance.new("Part"); WallSlideEffectPart.CanCollide = false; WallSlideEffectPart.CanTouch = false --I'd love it if this worked with attachments
		WallSlideEffectPart.CanQuery = false; WallSlideEffectPart.Size = Vector3.new(1, 1, 1); WallSlideEffectPart.Transparency = 1
		WallSlideEffectPart.CFrame = (RootPart.CFrame + Vector3.new(0, 0, -0.5)); WallSlideEffectPart.Parent = RootPart
		local Weld = Instance.new("WeldConstraint"); Weld.Parent = WallSlideEffectPart; Weld.Part0 = RootPart; Weld.Part1 = WallSlideEffectPart
		WallSlideEffectCall = Particles3D.new(WallSlideEffectPart, WallSlideEffect.Mesh.MeshId, WallSlideEffect.Mesh.TextureId)
		WallSlideEffectCall.EmissionDirection = "Top"; WallSlideEffectCall.Lifetime = NumberRange.new(1, 1.2)
		WallSlideEffectCall.Rate = 15; WallSlideEffectCall.Speed = 3; WallSlideEffectCall.SpreadAngle = Vector2.new(0, 10)
		WallSlideEffectCall.Transparency = NumberSequence.new{
			NumberSequenceKeypoint.new(0, 1),
			NumberSequenceKeypoint.new(0.3, 0.4),
			NumberSequenceKeypoint.new(1, 1),
		}
		WallSlideEffectCall.Size = NumberSequence.new{
			NumberSequenceKeypoint.new(0, 0.2),
			NumberSequenceKeypoint.new(0.3, 0.4),
			NumberSequenceKeypoint.new(1, 0)
		}
		WallSlideEffectCall.Enabled = true
	else
		if WallSlideEffectCall ~= nil then --If the wallslide effect exists
			WallSlideEffectCall.Enabled = false --Stop particle
			local StoreEffect = WallSlideEffectCall; WallSlideEffectCall = nil --If we touch another wall, we need the variable free again
			local StorePart = WallSlideEffectPart; WallSlideEffectPart = nil --If we touch another wall, we need the variable free again
			task.delay(1.5, function() --Give the particle time to dissapear
				if StoreEffect then --If it still exists destroy it
					StoreEffect:Destroy()
				end
				task.delay(0.5, function() --I assume it takes a bit to destroy the particles so wait a bit before destroying the part
					if StorePart then --If its part still exists too, destroy it
						StorePart:Destroy()
					end
				end)
			end)
		end
	end
end

After that I tried removing the cache system, and it kind of did the trick, parts were getting destroyed now (with a couple rare exceptions that I assume didn’t get destroyed due to lag), but particles now showed for a split second before being destroyed.


Transparency and Direction not working.wmv

Then I tried using the exact same code for this walking particle (only difference being the surface and size, but the transparency didn’t seem to change even after removing the wallslide effect and the direction of the particle didn’t seem to follow the part either, as seen in the vid it always goes to the left.

code again
local function SetupRunParticle()
	
	--Get Floor
	local CharPos, CharSize = Character:GetBoundingBox()
	local Floor = CharPos.Position - Vector3.new(0, CharSize.Y / 2, 0)
	
	--Effect
	RunEffectPart = Instance.new("Part"); RunEffectPart.CanCollide = false; RunEffectPart.CanTouch = false
	RunEffectPart.CanQuery = false; RunEffectPart.Size = Vector3.new(1, 1, 1); RunEffectPart.Transparency = 1
	RunEffectPart.CFrame = (RootPart.CFrame + Vector3.new(0, Floor.Y - RootPart.Position.Y, -0.2)); RunEffectPart.Parent = RootPart
	local Weld = Instance.new("WeldConstraint"); Weld.Parent = RunEffectPart; Weld.Part0 = RootPart; Weld.Part1 = RunEffectPart
	RunEffectCall = Particles3D.new(RunEffectPart, WallSlideEffect.Mesh.MeshId, WallSlideEffect.Mesh.TextureId)
	RunEffectCall.EmissionDirection = "Back"; RunEffectCall.Lifetime = NumberRange.new(0.5, 0.7);
	RunEffectCall.Rate = 6; RunEffectCall.Speed = 3; RunEffectCall.SpreadAngle = Vector2.new(10, 0);
	RunEffectCall.Transparency = NumberSequence.new{
		NumberSequenceKeypoint.new(0, 1),
		NumberSequenceKeypoint.new(0.3, 0.4),
		NumberSequenceKeypoint.new(1, 1),
	}
	RunEffectCall.Size = NumberSequence.new{
		NumberSequenceKeypoint.new(0, 0.3),
		NumberSequenceKeypoint.new(0.3, 0.8),
		NumberSequenceKeypoint.new(1, 0)
	}
end

local function RunParticle(Bool)
	if not RunEffectCall or not RunEffectPart then
		SetupRunParticle()
	end
	
	if Bool == true and MovesetValue.Value == true then	
		RunEffectCall.Enabled = true
	else
		RunEffectCall.Enabled = false
	end
end

By this point I was completly lost as to what to do, so I just added the caching system again and to my surprise the walking particles destroyed fine but the wallslide ones still didn’t and the transparency still wasn’t working on the walk particle so I don’t really know what to do about it, hoping i’m doing something wrong on my end.

go ahead and get the latest version from the github, it should fix this stuff!

also, it might be better to just use one particle emitter instead of making new ones

2 Likes

Thanks a lot for the quick response, that fixed it.

As for this transparency bug, it appears to happen when the lifetime of the particle is too short (in my case 0.5-0.7), making it a bit bigger solved that too.

May I get a place file for this?