Help with scripting a natural disaster survival type game

Hello, I wanna try scripting a natural disaster like game but I don’t know how to really script things such as: Tornadoes, Sandstorm, or etc. I would love it if someone were here to teach me! Please and thank you.

I have a sample of a tornado code btw.

	if mdl then
		if mdl:IsA('BasePart') then
			if mdl.Position.y<maxhight then -- Apply tornado effects regardless of anchored state
				local dist=((mdl.Position*Vector3.new(1,0,1))-script.Parent.Position).magnitude
				if dist<breakradius then
					local h=mdl.Parent:FindFirstChild("Humanoid")
					if not h then
						mdl:BreakJoints()
					end
					local pushpull=pull
					local hightpercentage=(mdl.Position.y/(maxhight-low))
					pushpull=pull+((push-pull)*hightpercentage)
					if dist<cycloneradius then
						pushpull=push
					end
					local angle=math.atan2(mdl.Position.x-script.Parent.Position.x,mdl.Position.z-script.Parent.Position.z)
					local ncf=(CFrame.new(script.Parent.Position.X, mdl.Position.Y, script.Parent.Position.Z))*CFrame.Angles(0,angle+.1,0)*CFrame.new(0,0,dist+pushpull) -- Corrected ncf calculation!


1 Like

Not sure how much this will help but its a start so taking code found from Cone’s Video I altered a little and changed some API usage.

local ServerScriptService = game:GetService("ServerScriptService")

local weather = require(ServerScriptService:WaitForChild("WeatherModule"))

task.wait(5)

-- 1. Define where the tornado will start
local spawnLocation = Vector3.new(0, 0, 0) 

-- 2. Setup your tornado's configuration
local tornadoConfig = {
	Duration = 45,        -- How long it lasts in seconds
	Height = 25,          -- How many segments tall it is
	BaseWidth = 10,       -- Size of the funnel at the bottom
	TopWidth = 70,        -- Size of the funnel at the top (creates the V-shape)
	Aggression = 20,      -- How wildly it swings and wobbles
	UseCache = true,      -- Set to false if you want the map destruction to be permanent
	MoveSpeed = 12        -- How fast it wanders around the map
}

-- 3. Spawn the tornado!
print("Spawning Tornado...")
weather.initTornado(spawnLocation, tornadoConfig)
local RunService = game:GetService("RunService")
local Debris = game:GetService("Debris")
local TweenService = game:GetService("TweenService")

local weather = {}

-- [[ CONFIGURATION TEMPLATES ]] --
local lightday = {
	Color3.fromRGB(225, 225, 225), -- cloud color
	0.64, -- cover
	0.44, -- cloud density
	0.3,  -- atmos density
	Color3.fromRGB(225, 225, 225), -- color correction
	4.3   -- brightness
}

local lightingStorm = {
	Color3.fromRGB(80, 80, 80),
	1,
	1,
	0.8,
	Color3.fromRGB(150, 160, 170),
	1
}

local function convertLighting(template)
	if workspace.Terrain:FindFirstChild("Clouds") then
		TweenService:Create(workspace.Terrain.Clouds, TweenInfo.new(5), {Color = template[1], Cover = template[2], Density = template[3]}):Play()
	end
	if game.Lighting:FindFirstChild("ColorCorrection") then
		TweenService:Create(game.Lighting.ColorCorrection, TweenInfo.new(5), {TintColor = template[5]}):Play()
	end
	if game.Lighting:FindFirstChild("Atmosphere") then
		TweenService:Create(game.Lighting.Atmosphere, TweenInfo.new(5), {Density = template[4]}):Play()
	end
	TweenService:Create(game.Lighting, TweenInfo.new(5), {Brightness = template[6]}):Play()
end


export type config = {
    Duration: number?,
    Height: number?,
    BaseWidth: number?,
    TopWidth: number?,
    Aggression: number?,
    UseCache: boolean?,
    MoveSpeed: number?
}
function weather.initTornado(spawnPosition, config: config)
	config = config or {}
	local duration = config.Duration or 30
	local height = config.Height or 20
	local baseWidth = config.BaseWidth or 10
	local topWidth = config.TopWidth or 50
	local aggression = config.Aggression or 15 -- How violently it wobbles
	local useCache = config.UseCache == nil and true or config.UseCache -- Defaults to true
	local moveSpeed = config.MoveSpeed or 10

	convertLighting(lightingStorm)

	-- Setup Data Structures
	local segments = {}
	local destroyedPartsCache = {}
	local isLive = true

	-- Create an invisible base part to control the tornado's overall position
	local tornadoBase = Instance.new("Part")
	tornadoBase.Size = Vector3.new(2, 2, 2)
	tornadoBase.Position = spawnPosition
	tornadoBase.Transparency = 1
	tornadoBase.Anchored = true
	tornadoBase.CanCollide = false
	tornadoBase.Parent = workspace

	-- Build the Tornado
	-- Works with a single mesh (if height = 1) or stacking segments
	for i = 1, height do
		local seg = script.tornadoSeg:Clone()

		-- Calculate Funnel Shape (Interpolate between baseWidth and topWidth based on height)
		local progress = height > 1 and (i - 1) / (height - 1) or 1
		local currentWidth = baseWidth + ((topWidth - baseWidth) * progress)

		seg.Size = Vector3.new(currentWidth, seg.Size.Y, currentWidth)
		seg.Anchored = true
		seg.CanCollide = false
		seg.Parent = workspace

		if i == 1 and seg:FindFirstChild("Sound") then
			seg.Sound.Playing = true
		end

		table.insert(segments, {
			part = seg,
			yOffset = (i - 1) * seg.Size.Y,
			width = currentWidth
		})
	end

	-- Movement & Wobble System
	local timeElapsed = 0
	local movementConnection

	movementConnection = RunService.Heartbeat:Connect(function(deltaTime)
		timeElapsed += deltaTime

		-- Move the base around randomly using Perlin Noise
		local moveX = math.noise(timeElapsed * 0.2, 0, 0) * moveSpeed
		local moveZ = math.noise(0, timeElapsed * 0.2, 0) * moveSpeed
		tornadoBase.CFrame *= CFrame.new(moveX * deltaTime, 0, moveZ * deltaTime)

		-- Update segments to follow base + apply wobble
		for i, segData in ipairs(segments) do
			local progress = i / height

			-- Wobble calculation
			local wobbleX = math.noise(timeElapsed, progress, 0) * aggression * progress
			local wobbleZ = math.noise(0, progress, timeElapsed) * aggression * progress

			local targetPos = tornadoBase.Position + Vector3.new(wobbleX, segData.yOffset, wobbleZ)

			-- Smoothly interpolate segments to their target positions
			segData.part.CFrame = segData.part.CFrame:Lerp(CFrame.new(targetPos), deltaTime * 10)
		end
	end)

	-- Destruction & Physics System (Runs every 0.2 seconds to save performance)
	local overlapParams = OverlapParams.new()
	overlapParams.FilterType = Enum.RaycastFilterType.Exclude

	-- Build exclusion list (Tornado parts + Base)
	local ignoreList = {tornadoBase}
	for _, segData in ipairs(segments) do table.insert(ignoreList, segData.part) end
	overlapParams.FilterDescendantsInstances = ignoreList

	task.spawn(function()
		while isLive do
			-- Check radius around the base of the tornado
			local partsInRadius = workspace:GetPartBoundsInRadius(tornadoBase.Position, topWidth * 0.8, overlapParams)

			for _, part in ipairs(partsInRadius) do
				if part:IsA("BasePart") and not part.Anchored then continue end -- Skip already flying parts

				local parentModel = part:FindFirstAncestorOfClass("Model")
				local isHumanoid = parentModel and parentModel:FindFirstChildOfClass("Humanoid")

				if isHumanoid then
					-- Fling players instead of instakilling
					local root = parentModel:FindFirstChild("HumanoidRootPart")
					if root then
						root.AssemblyLinearVelocity = Vector3.new(math.random(-50, 50), 100, math.random(-50, 50))
					end
				elseif part:IsA("BasePart") and part.Name ~= "Terrain" then
					-- CACHE SYSTEM: Save the part's original state
					if useCache and not destroyedPartsCache[part] then
						destroyedPartsCache[part] = {
							CFrame = part.CFrame,
							Anchored = part.Anchored,
							Parent = part.Parent
						}
					end

					-- Destroy/Break it
					part:BreakJoints()
					part.Anchored = false

					-- Fling it upward and outward
					local flingDir = (part.Position - tornadoBase.Position).Unit
					part:ApplyImpulse((flingDir + Vector3.new(0, 2, 0)) * part.Mass * math.random(30, 60))
				end
			end
			task.wait(0.2)
		end
	end)

	-- Duration & Cleanup
	task.delay(duration, function()
		isLive = false
		movementConnection:Disconnect()

		-- Fade out segments and delete
		for _, segData in ipairs(segments) do
			TweenService:Create(segData.part, TweenInfo.new(3), {Transparency = 1, Size = Vector3.new(0,0,0)}):Play()
			Debris:AddItem(segData.part, 3.5)
		end
		Debris:AddItem(tornadoBase, 3.5)

		convertLighting(lightday)

		-- Reconstruct cached parts
		if useCache then
			task.wait(4) -- Wait for tornado to fully disappear before rebuilding
			for part, originalData in pairs(destroyedPartsCache) do
				if part and part.Parent then
					part.AssemblyLinearVelocity = Vector3.zero
					part.AssemblyAngularVelocity = Vector3.zero

					part.Anchored = true
					TweenService:Create(part, TweenInfo.new(1.5, Enum.EasingStyle.Quart), {CFrame = originalData.CFrame}):Play()
				end
			end
			destroyedPartsCache = {} -- Clear memory
		end
	end)
end

return weather
1 Like

I modified the script so it can fit the aspects of my game it was really cool but i wish that the destruction is more like that one tornado from natural disaster survival game

I found a script but the thing is sometimes it could be buggy. For example, when the tornado pulls the parts circle around the tornado (which is what I want) and when parts reach on top of the tornado the parts all fall at once instead of one part falling when it reaches it’s maximum height here’s the code: `— START OF FILE Script.txt —
–Create a model or folder called “Structure”. Place anything you want to be affected in it.
–Place this script in any object or tornado part

maxhight=300
low=40
speed=100
push=.1
pull=-.5
lift=.25
breakradius=35
cycloneradius=10
rate = 1/30
unanchorChance = 0.005 – Chance for an object to be unanchored per frame (adjust as needed)
–storage = game.ServerStorage

function unanchorObjects(mdl)
if mdl then
if mdl:IsA(‘BasePart’) then
if mdl.Anchored and mdl.Position.y<maxhight then – Only check anchored parts for unanchoring
local dist=((mdl.Position*Vector3.new(1,0,1))-script.Parent.Position).magnitude
if dist<breakradius then
– Chance to unanchor objects
if math.random() < unanchorChance then
mdl.Anchored = false
end
end
end
end
if mdl then
if mdl.Parent~=nil then
for i3,v3 in ipairs(mdl:GetChildren()) do
unanchorObjects(v3) – Recursively check children for unanchoring
end
end
end
end
end

function updatetornado(mdl)
if mdl then
if mdl:IsA(‘BasePart’) then
if mdl.Position.y<maxhight then – Apply tornado effects regardless of anchored state
local dist=((mdl.Position*Vector3.new(1,0,1))-script.Parent.Position).magnitude
if dist<breakradius then
local h=mdl.Parent:FindFirstChild(“Humanoid”)
if not h then
mdl:BreakJoints()
end
local pushpull=pull
local hightpercentage=(mdl.Position.y/(maxhight-low))
pushpull=pull+((push-pull)*hightpercentage)
if dist<cycloneradius then
pushpull=push
end
local angle=math.atan2(mdl.Position.x-script.Parent.Position.x,mdl.Position.z-script.Parent.Position.z)
local ncf=(CFrame.new(script.Parent.Position.X, mdl.Position.Y, script.Parent.Position.Z))*CFrame.Angles(0,angle+.1,0)*CFrame.new(0,0,dist+pushpull) – Corrected ncf calculation!

				if game.Workspace:FindFirstChild("Marker") then
					game.Workspace.Marker.CFrame=ncf
				end

				local vec=(ncf.Position-mdl.Position).unit
				-- print(vec.Y) -- Uncomment for debugging vec.Y

				local speedpercent=(dist-cycloneradius)/(breakradius-cycloneradius)
				if speedpercent<0 then
					speedpercent=0
				end
				speedpercent=1-speedpercent
				speedpercent=speedpercent+.1
				if speedpercent>1 then
					speedpercent=1
				end
				mdl.Velocity=(vec*speedpercent*speed*(1+(2*hightpercentage)))+Vector3.new(0,(lift*(speedpercent+hightpercentage)*speed),0)
				mdl.RotVelocity=mdl.RotVelocity+Vector3.new(math.random(-1,1),math.random(-1,1)+.1,math.random(-1,1))
				if not tornadodparts[mdl] then
					if h then
						if mdl.Name=="HumanoidRootPart" and not h.PlatformStand then
							h.PlatformStand=true
							delay(5,function()
								if h then
									h.PlatformStand=false
								end
							end)
						end
						if mdl.Name~='HumanoidRootPart' and mdl.Name~='Head' and mdl.Name~='UpperTorso' and mdl.Name~='LowerTorso' then
							if math.random()<0.005 then	-- randomly rip limbs off. :D
								mdl:Destroy()
							end
						end
					else
						if math.random(1,2)==1 then
							mdl:Destroy()
						else
							tornadodparts[mdl]=true
						end
					end
				end
			end
		end
	end
	if mdl then
		if mdl.Parent~=nil then
			for i3,v3 in ipairs(mdl:GetChildren()) do
				updatetornado(v3)
			end
		end
	end
end

end

local tornadoing=true
tornadodparts={}
–local tp=storage.TornadoPart:clone()
–tp.CFrame=CFrame.new(script.Parent.Position - Vector3.new(0,low,0))
–tp.Parent=game.Workspace.Structure
wait(.2)
–local iwsound=tp:FindFirstChild(“IntenseWind”)
–if iwsound then
– iwsound:Play()
–end
delay(0,function()
local starttime=tick()
local endtime=starttime+90
while tornadoing do
– No tornado movement path anymore, tornado position is always the parent’s position
unanchorObjects(game.Workspace.Structure) – Call the unanchor function
updatetornado(game.Workspace.Structure) – Call the tornado effect function
for i,v in ipairs(game.Players:GetPlayers()) do
if v then
if v.Character~=nil then
unanchorObjects(v.Character) – Apply unanchoring to characters too, if desired
updatetornado(v.Character) – Apply tornado effects to characters
end
end
end
wait(rate)
end
end)
–wait(90)
–tornadodparts={}
–tornadoing=false`

actual code: — START OF FILE Script.txt —
–Create a model or folder called “Structure”. Place anything you want to be affected in it.
–Place this script in any object or tornado part

maxhight=300
low=40
speed=100
push=.1
pull=-.5
lift=.25
breakradius=35
cycloneradius=10
rate = 1/30
unanchorChance = 0.005 – Chance for an object to be unanchored per frame (adjust as needed)
–storage = game.ServerStorage

function unanchorObjects(mdl)
if mdl then
if mdl:IsA(‘BasePart’) then
if mdl.Anchored and mdl.Position.y<maxhight then – Only check anchored parts for unanchoring
local dist=((mdl.Position*Vector3.new(1,0,1))-script.Parent.Position).magnitude
if dist<breakradius then
– Chance to unanchor objects
if math.random() < unanchorChance then
mdl.Anchored = false
end
end
end
end
if mdl then
if mdl.Parent~=nil then
for i3,v3 in ipairs(mdl:GetChildren()) do
unanchorObjects(v3) – Recursively check children for unanchoring
end
end
end
end
end

function updatetornado(mdl)
if mdl then
if mdl:IsA(‘BasePart’) then
if mdl.Position.y<maxhight then – Apply tornado effects regardless of anchored state
local dist=((mdl.Position*Vector3.new(1,0,1))-script.Parent.Position).magnitude
if dist<breakradius then
local h=mdl.Parent:FindFirstChild(“Humanoid”)
if not h then
mdl:BreakJoints()
end
local pushpull=pull
local hightpercentage=(mdl.Position.y/(maxhight-low))
pushpull=pull+((push-pull)*hightpercentage)
if dist<cycloneradius then
pushpull=push
end
local angle=math.atan2(mdl.Position.x-script.Parent.Position.x,mdl.Position.z-script.Parent.Position.z)
local ncf=(CFrame.new(script.Parent.Position.X, mdl.Position.Y, script.Parent.Position.Z))*CFrame.Angles(0,angle+.1,0)*CFrame.new(0,0,dist+pushpull) – Corrected ncf calculation!

				if game.Workspace:FindFirstChild("Marker") then
					game.Workspace.Marker.CFrame=ncf
				end

				local vec=(ncf.Position-mdl.Position).unit
				-- print(vec.Y) -- Uncomment for debugging vec.Y

				local speedpercent=(dist-cycloneradius)/(breakradius-cycloneradius)
				if speedpercent<0 then
					speedpercent=0
				end
				speedpercent=1-speedpercent
				speedpercent=speedpercent+.1
				if speedpercent>1 then
					speedpercent=1
				end
				mdl.Velocity=(vec*speedpercent*speed*(1+(2*hightpercentage)))+Vector3.new(0,(lift*(speedpercent+hightpercentage)*speed),0)
				mdl.RotVelocity=mdl.RotVelocity+Vector3.new(math.random(-1,1),math.random(-1,1)+.1,math.random(-1,1))
				if not tornadodparts[mdl] then
					if h then
						if mdl.Name=="HumanoidRootPart" and not h.PlatformStand then
							h.PlatformStand=true
							delay(5,function()
								if h then
									h.PlatformStand=false
								end
							end)
						end
						if mdl.Name~='HumanoidRootPart' and mdl.Name~='Head' and mdl.Name~='UpperTorso' and mdl.Name~='LowerTorso' then
							if math.random()<0.005 then	-- randomly rip limbs off. :D
								mdl:Destroy()
							end
						end
					else
						if math.random(1,2)==1 then
							mdl:Destroy()
						else
							tornadodparts[mdl]=true
						end
					end
				end
			end
		end
	end
	if mdl then
		if mdl.Parent~=nil then
			for i3,v3 in ipairs(mdl:GetChildren()) do
				updatetornado(v3)
			end
		end
	end
end

end

local tornadoing=true
tornadodparts={}
–local tp=storage.TornadoPart:clone()
–tp.CFrame=CFrame.new(script.Parent.Position - Vector3.new(0,low,0))
–tp.Parent=game.Workspace.Structure
wait(.2)
–local iwsound=tp:FindFirstChild(“IntenseWind”)
–if iwsound then
– iwsound:Play()
–end
delay(0,function()
local starttime=tick()
local endtime=starttime+90
while tornadoing do
– No tornado movement path anymore, tornado position is always the parent’s position
unanchorObjects(game.Workspace.Structure) – Call the unanchor function
updatetornado(game.Workspace.Structure) – Call the tornado effect function
for i,v in ipairs(game.Players:GetPlayers()) do
if v then
if v.Character~=nil then
unanchorObjects(v.Character) – Apply unanchoring to characters too, if desired
updatetornado(v.Character) – Apply tornado effects to characters
end
end
end
wait(rate)
end
end)
–wait(90)
–tornadodparts={}
–tornadoing=false

Kinda swamped with my own game but I can help you with the overall logic.

It’s usually best to map out how these things go step by step.

General Map Flow

This is a very basic way to do it but this would get the job done for most cases

Read
LEFTRIGHT

Somethings you’re gonna need: ( for optional )

  • AlignPosition
  • AlignOrientation
  • reaction frame work or an understanding of meta tables for __newindex
  • A centralized loop to update part positions
  • An understanding of CFrame and Vector3 Position math
  • A definite way of destroying something
    (this can be un-anchoring single parts as they enter a boundingbox/ ZoneModule)
    (This can be un-anchoring full models/folders of parts as one part enters)

I Hope this helps you get a better structure to operate step by step.