Make a billboard gui Tornado

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:

image

(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:

image
image

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)

image

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)

2 Likes