Math Based "Particles"

So, today I thought it would be cool to make a module script to get the value and envelope of a given time of a NumberSequence.
Then I came up with the idea for making “particles” that will go to given points.

So for the past hour, I worked a part based system and BillboardGui based system. Both operate at about 58 FPS for me.
I have also open sourced this code for anyone to optimize, change, add on to, or implement to anything.

Open sourced place: Math Based Particles - Roblox

Spline module:

[code]–Module made by me to get the value of a spline curve when given a time.

local Module = {}

function Module:Load(Numbersequence)
local Curve = {}
local Points = {}
local Numbers = {}
for _,Point in pairs(Numbersequence.Keypoints) do
table.insert(Numbers,Point.Time)
local Envelope = Point.Envelope
local ScaleUp = 0
if Envelope > 0 then
ScaleUp = math.random(-Envelope100,Envelope100)/100
end
Points[Point.Time] = Point.Value + ScaleUp
end

local function GetNextLowerNumber(Number)
	local LowestNumber,Dif
	for i = 1, #Numbers do
		if Numbers[i] <= Number then
			if not Dif then
				LowestNumber,Dif = Numbers[i],Number - Numbers[i]
			elseif Dif > Number - Numbers[i] then
				LowestNumber,Dif = Numbers[i],Number - Numbers[i]
			end
		end
	end
	return LowestNumber or 0
end

local function GetNextHigherNumber(Number)
	local HighestNumber,Dif
	for i = 1, #Numbers do
		if Numbers[i] >= Number then
			if not Dif then
				HighestNumber,Dif = Numbers[i],Numbers[i] - Number
			elseif Dif > Numbers[i] - Number  then
				HighestNumber,Dif = Numbers[i],Numbers[i] - Number
			end
		end
	end
	return HighestNumber or 1
end

local function Lerp(Min,Max,Ratio)
	return Min + (Max - Min)*Ratio
end

function Curve:Get(Time)
	local LowerNumber = GetNextLowerNumber(Time)
	local HigherNumber = GetNextHigherNumber(Time)
	local Percent =  1 - (HigherNumber - Time)/(HigherNumber - LowerNumber)
	if tostring(Percent) == "-1.#IND" then Percent = 1 end
	local MinValue,MaxValue = Points[LowerNumber],Points[HigherNumber]
	return Lerp(MinValue,MaxValue,Percent)
end
return Curve

end

return Module[/code]

Part based:

[code]–Points the particles go to
local Points = {Vector3.new(0,10,-20),Vector3.new(0,20,-20),Vector3.new(20,10,0),Vector3.new(20,30,-40)}

–Properties
local ColorR = NumberSequence.new({NumberSequenceKeypoint.new(0,0.75,0.25),NumberSequenceKeypoint.new(0.5,0,0),NumberSequenceKeypoint.new(1,0,0)})
local ColorG = NumberSequence.new({NumberSequenceKeypoint.new(0,0,0),NumberSequenceKeypoint.new(0.5,170/255,0),NumberSequenceKeypoint.new(1,0,0)})
local ColorB = NumberSequence.new({NumberSequenceKeypoint.new(0,0,0),NumberSequenceKeypoint.new(0.5,0,0),NumberSequenceKeypoint.new(1,0.5,0.5)})
local Size = NumberSequence.new({NumberSequenceKeypoint.new(0,1,0),NumberSequenceKeypoint.new(0.3,3.5,0),NumberSequenceKeypoint.new(0.5,1,0),NumberSequenceKeypoint.new(0.9,1,0),NumberSequenceKeypoint.new(0.95,6.5,3.5),NumberSequenceKeypoint.new(1,1,0)})
local Transparency = NumberSequence.new({NumberSequenceKeypoint.new(0,0.5,0.25),NumberSequenceKeypoint.new(1,0)})
local Enabled = true
local LifeTime = NumberRange.new(0.5,3)
local Rate = 20
local Speed = NumberRange.new(25,35)

–Main Script

local AdvancedSpline = require(game.Workspace:WaitForChild(“SplineModule”))

local RenderSpeed = 60
local RenderStepped = game[“Run Service”].RenderStepped
local function Wait(Number)
if Number then
wait(Number)
else
RenderStepped:wait()
end
end

local function ReleaseParticle(ColorR,ColorG,ColorB,Size,Transparency,LifeTime,Speed)
LifeTime = math.random(LifeTime.Min100,LifeTime.Max100)/100
Speed = math.random(Speed.Min100,Speed.Max100)/100
local Particle = Instance.new(“Part”)
Particle.FormFactor = “Custom”
Particle.Size = Vector3.new(1,1,1)
Particle.CanCollide = false
Particle.Anchored = true

local CR,CG,CB = AdvancedSpline:Load(ColorR),AdvancedSpline:Load(ColorG),AdvancedSpline:Load(ColorB)
local SizeSequence = AdvancedSpline:Load(Size)
local TransSequence = AdvancedSpline:Load(Transparency)

local Mesh = Instance.new("SpecialMesh")	
Mesh.MeshType = "FileMesh"
Mesh.MeshId = "http://www.roblox.com/Asset/?id=9856898"
Mesh.TextureId = "http://www.roblox.com/asset/?ID=1361097"
Mesh.Parent = Particle



spawn(function()
	local CurN = 1
	local CurPoint,NextPoint = Points[1],Points[2]
	local CurRatio,RatioInc = 0
	local Length = (CurPoint - NextPoint).magnitude
	Particle.Parent = game.Workspace
	for i = 1, LifeTime*RenderSpeed do
		local TimeRatio = i/(LifeTime*RenderSpeed)
		
		if not RatioInc then
			RatioInc = (Speed/RenderSpeed)/Length
		end
		CurRatio = CurRatio + RatioInc
		if CurRatio >= 1 then
			if Points[CurN + 2] then
				CurPoint = NextPoint
				NextPoint = Points[CurN + 2]
				CurN = CurN + 1
				
				local LengthLeft = (CurRatio - 1) * Length
				Length = (CurPoint - NextPoint).magnitude
				CurRatio = LengthLeft/Length
				RatioInc = (Speed/RenderSpeed)/Length
			end
		end
		local R,G,B = CR:Get(TimeRatio),CG:Get(TimeRatio),CB:Get(TimeRatio)
		Mesh.VertexColor = Vector3.new(R,G,B)
		local S = SizeSequence:Get(TimeRatio)
		Mesh.Scale = Vector3.new(S,S,S)
		
		local T = TransSequence:Get(TimeRatio)
		Particle.Transparency = T
		
		Particle.CFrame = CFrame.new(CurPoint:Lerp(NextPoint,CurRatio))
		Wait()
	end
	Particle:Destroy()
end)

end

while true do
if Enabled == true then
ReleaseParticle(ColorR,ColorG,ColorB,Size,Transparency,LifeTime,Speed)
end
wait(1/Rate)
end[/code]

BillboardGui based:

[code]–Points the particles go to
local Points = {Vector3.new(0,10,20),Vector3.new(0,20,20),Vector3.new(20,10,40),Vector3.new(20,30,0)}

–Properties
local ColorR = NumberSequence.new({NumberSequenceKeypoint.new(0,0.75,0.25),NumberSequenceKeypoint.new(0.5,0,0),NumberSequenceKeypoint.new(1,0,0)})
local ColorG = NumberSequence.new({NumberSequenceKeypoint.new(0,0,0),NumberSequenceKeypoint.new(0.5,170/255,0),NumberSequenceKeypoint.new(1,0,0)})
local ColorB = NumberSequence.new({NumberSequenceKeypoint.new(0,0,0),NumberSequenceKeypoint.new(0.5,0,0),NumberSequenceKeypoint.new(1,0.5,0.5)})
local Size = NumberSequence.new({NumberSequenceKeypoint.new(0,1,0),NumberSequenceKeypoint.new(0.3,3.5,0),NumberSequenceKeypoint.new(0.5,1,0),NumberSequenceKeypoint.new(0.96,1,0),NumberSequenceKeypoint.new(0.98,6.5,3.5),NumberSequenceKeypoint.new(1,1,0)})
local Texture = “rbxasset://textures/particles/sparkles_main.dds”
local Transparency = NumberSequence.new({NumberSequenceKeypoint.new(0,0.5,0.25),NumberSequenceKeypoint.new(1,0)})
local Enabled = true
local LifeTime = NumberRange.new(0.5,3)
local Rate = 20
local Rotation = NumberRange.new(0,20)
local RotSpeed = NumberRange.new(10,1080)
local Speed = NumberRange.new(25,35)

–Main Script

local AdvancedSpline = require(game.Workspace:WaitForChild(“SplineModule”))

local RenderSpeed = 60
local RenderStepped = game[“Run Service”].RenderStepped
local function Wait(Number)
if Number then
wait(Number)
else
RenderStepped:wait()
end
end
local function ReleaseParticle(ColorR,ColorG,ColorB,Size,Texture,Transparency,LifeTime,Rotation,RotSpeed,Speed)
if LifeTime.Min ~= LifeTime.Max then LifeTime = math.random(LifeTime.Min100,LifeTime.Max100)/100 else LifeTime = LifeTime.Min end
if Speed.Min ~= Speed.Max then Speed = math.random(Speed.Min100,Speed.Max100)/100 else Speed = Speed.Min end
if Rotation.Min ~= Rotation.Max then Rotation = math.random(Rotation.Min100,Rotation.Max100)/100 else Rotation = Rotation.Min end
if RotSpeed.Min ~= RotSpeed.Max then RotSpeed = math.random(RotSpeed.Min100,RotSpeed.Max100)/100 else RotSpeed = RotSpeed.Min end

local Particle = Instance.new("Part")
Particle.FormFactor = "Custom"
Particle.Size = Vector3.new(1,1,1)
Particle.CanCollide = false
Particle.Anchored = true
Particle.Transparency = 1

local CR,CG,CB = AdvancedSpline:Load(ColorR),AdvancedSpline:Load(ColorG),AdvancedSpline:Load(ColorB)
local SizeSequence = AdvancedSpline:Load(Size)
local TransSequence = AdvancedSpline:Load(Transparency)

local BBG = Instance.new("BillboardGui")


local Image = Instance.new("ImageLabel")
Image.BackgroundTransparency = 1
Image.Image = Texture
Image.Size = UDim2.new(1,0,1,0)
Image.Rotation = Rotation
Image.Parent = BBG
BBG.Parent = Particle


spawn(function()
	local CurN = 1
	local CurPoint,NextPoint = Points[1],Points[2]
	local CurRatio,RatioInc = 0
	local Length = (CurPoint - NextPoint).magnitude
	Particle.Parent = game.Workspace
	for i = 1, LifeTime*RenderSpeed do
		local RenderRatio = i/(LifeTime*RenderSpeed)
		if not RatioInc then
			RatioInc = (Speed/RenderSpeed)/Length
		end
		CurRatio = CurRatio + RatioInc
		if CurRatio >= 1 then
			if Points[CurN + 2] then
				CurPoint = NextPoint
				NextPoint = Points[CurN + 2]
				CurN = CurN + 1
				
				local LengthLeft = (CurRatio - 1) * Length
				Length = (CurPoint - NextPoint).magnitude
				CurRatio = LengthLeft/Length
				RatioInc = (Speed/RenderSpeed)/Length
			end
		end
		Image.Rotation = Image.Rotation + (RotSpeed/RenderSpeed)
		local R,G,B = CR:Get(RenderRatio),CG:Get(RenderRatio),CB:Get(RenderRatio)
		Image.ImageColor3 = Color3.new(R,G,B)
		local S = SizeSequence:Get(RenderRatio)
		BBG.Size = UDim2.new(S,0,S,0)
		
		local T = TransSequence:Get(RenderRatio)
		Image.ImageTransparency = T
		
		Particle.CFrame = CFrame.new(CurPoint:Lerp(NextPoint,CurRatio))
		Wait()
	end
	Particle:Destroy()
end)

end

while true do
if Enabled == true then
ReleaseParticle(ColorR,ColorG,ColorB,Size,Texture,Transparency,LifeTime,Rotation,RotSpeed,Speed)
end
wait(1/Rate)
end[/code]

Edit: Ok, I guess I need to post the purpose. It was to make particles/parts go between given points without the dependency for multiple parts and emitters for each turn.

1 Like

Hey nice!
Make it a plugin, select two nodes to create a link.

I’ve seen something like that but I think it wasn’t scripted:

It’s open sauce if you want to see it in studio.

Making grids with particles is a good idea, but what about the lag ?
IDK if there’s limits to particles.

[quote] Hey nice!

I’ve seen something like that but I think it wasn’t scripted:
-Snip-
It’s open sauce if you want to see it in studio.

Making grids with particles is a good idea, but what about the lag ?
IDK if there’s limits to particles. [/quote]
Does not do super well ingame or studio because it uses so many particles.
It would do worse in the system I made because it creates parts for each particle, plus that does not do sharp turns as much as going strait to an end. That is an idea where a PartilceEmitter plus a lot of math makes sense.