Hi, I want to show you how to make a billboard gui tornado. There isn’t any tutorial yet so i’m gonna make it.
First, you’re gonna create a folder containing your tornado, we are gonna call it “Tornado”.
Now with your folder, you are gonna create a few parts that are gonna be your tornado "structur path ".
Like this:
(you can use as many parts as you want)
Now, create a boolean value called “TornadoPart”, this is for the script to see if your part is a part of the structural path. You need to duplicate it into all the baseparts in the folder and enable the value if it’s a part of the structural path:
Finally, you’re gonna make the guipart which is the part that is containing the billboard gui.
(WARNING: ALL PARTS OF THE FOLDER SHOULD BE ANCHORED, CANCOLLIDE OFF AND CANTOUCH OFF)
Then you need to add two int values into your folder named “BaseWide” and “TopWide” which are gonna be your tornado shape size values.
And now, create a script and put this in it: (this would be your tornado script)
local numParticles = 4000 -- number of parts
local riseSpeed = 2 -- speed of rise
local swirlSpeed = 2 -- rotation speed
local chaosFactor = 10 -- adds random movments
local folderPart = Instance.new("Folder")
folderPart.Name = "TornadoFolder"
folderPart.Parent = workspace
local definingParts = {}
for _, part in ipairs(script.Parent:GetChildren()) do
if part:IsA("BasePart") then
if part:FindFirstChild("TornadoPart").Value == true then
table.insert(definingParts, part)
end
end
end
table.sort(definingParts, function(a, b)
return a.Position.Y < b.Position.Y
end)
local function interpolateBetweenAllParts(tProgress)
local numSegments = #definingParts - 1
local adjustedT = tProgress * numSegments
local segmentIndex = math.floor(adjustedT)
local localT = adjustedT - segmentIndex
segmentIndex = math.clamp(segmentIndex, 0, numSegments - 1)
local startPart = definingParts[segmentIndex + 1]
local endPart = definingParts[segmentIndex + 2] or definingParts[#definingParts]
return startPart.Position:Lerp(endPart.Position, localT)
end
local function calculateRadius(t)
local baseWide = script.Parent.BaseWide.Value
local topWide = script.Parent.TopWide.Value
return baseWide + (topWide - baseWide) * t
end
local particlesData = {}
for i = 1, numParticles do
local particle = guiPart:Clone()
particle.Parent = folderPart
table.insert(particlesData, {
particle = particle,
particleProgress = math.random(),
angle = math.random(0, 360),
randomOffset = Vector3.new(
math.random(-chaosFactor, chaosFactor),
0,
math.random(-chaosFactor, chaosFactor)
)
})
end
local lastUpdateTime = tick()
game:GetService("RunService").Heartbeat:Connect(function()
local deltaTime = tick() - lastUpdateTime
lastUpdateTime = tick()
for _, particleData in ipairs(particlesData) do
local particle = particleData.particle
local conePosition = interpolateBetweenAllParts(particleData.particleProgress)
local currentRadius = calculateRadius(particleData.particleProgress)
particleData.angle = particleData.angle - math.rad(swirlSpeed) * deltaTime
local x = math.cos(particleData.angle) * currentRadius + particleData.randomOffset.X
local z = math.sin(particleData.angle) * currentRadius + particleData.randomOffset.Z
particle.Position = conePosition + Vector3.new(x, 0, z)
particleData.particleProgress = particleData.particleProgress + (riseSpeed * 0.01)
if particleData.particleProgress > 1 then
particleData.particleProgress = 0
particleData.randomOffset = Vector3.new(
math.random(-chaosFactor, chaosFactor),
0,
math.random(-chaosFactor, chaosFactor)
)
end
end
end)
With a bit of modification, you should acheive something like that:
(EF3 that I made with this script)
(that’s not in the tutorial but I find this cool)
(btw, thanks to Masons2012 for the house)