The best way to optimize my cloud generation script?

Hello! I made a script that generates low-poly clouds for my low-poly world.

The script is entirely client-sided; since it’s purely aesthetic, I saw no need to have it be on the server. This is what they look like:


cloud4

As you can see, there are a LOT of clouds! At any given moment, the client is rendering between 900 to 1,100 clouds.

The way it works is I’ve got 20 unions. Half of them are small clouds separated into a file in ReplicatedStorage called A and the other half are big bunches of clouds in a folder called B. There is a higher chance of a cloud from A being created than B, about a 75 percent chance to B’s 25 percent. Additionally, in any given node in the sky where a cloud could appear, there’s only a 20 percent chance of a cloud appearing.

I use unions because I figure it’d be better than rendering a bunch of smaller blocks individually and welding them. Also, unions look better when transparent in my opinion. As the sun goes down, the color of all the clouds changes too in order to match the night sky.

When a player first joins my game, a script in ReplicatedFirst creates a big line of nodes in the distance, each responsible for sending a line of clouds every 10 seconds. When the clouds spawn, they can be off on the X or Z axis by between 0 or 500 studs to keep the clouds from looking like a grid and more natural. They can also be up 10 or down 10 studs. As clouds get farther away, they are automatically removed as new ones take their place so that the number of clouds is never continuously increasing, taking up the client’s memory.

I find that my game runs relatively fine when I play. My ping hovers between 60 to 110 when I test. I wanna see if I can make that a bit better.

Here is my main script. (Note: the script that causes the clouds to change color is part of a separate Time Cycle script, which I won’t show because I don’t think it’s part of the problem)

local OPAQUE = 0 --Cloud transparency
local SPEED = 10 --Studs clouds move per second
local DISTANCE = 25 --Distance between each point
local NUMBER = 300 --How many clouds to be generated ahead and to the sides of the starting part
local RANDOM = 500 --Max studs a cloud can be off a point by

local A = script.Parent.CloudStorage.A
local B = script.Parent.CloudStorage.B

local TS = game:GetService("TweenService")
local activity = workspace:WaitForChild("Activity")
local storage = activity.Clouds:WaitForChild("Storage")
local points = activity.Clouds:WaitForChild("Points")
local start = activity.Clouds:WaitForChild("Start")

local function generatePoints()
	local pointsList = {}
	local lastPoint = start
	local side = 1
	for pointNum = 1, NUMBER do
		local newPoint = Instance.new("Part")
		newPoint.Name = pointNum
		newPoint.Transparency = 1
		newPoint.Anchored = true
		newPoint.CanCollide = false
		newPoint.CFrame = lastPoint.CFrame * CFrame.new(DISTANCE * side * pointNum, 0, 0)
		newPoint.Parent = points
		lastPoint = newPoint
		side = side * -1 --Alternate left and right
		pointsList[tostring(pointNum)] = newPoint
		wait()
	end
	pointsList["0"] = start
	return pointsList
end

local function createCloud(point, headStart)
	local willACloudAppear = math.random(1, 5) --20% chance of a cloud appearing
	local appearInfo = TweenInfo.new(5, Enum.EasingStyle.Linear, Enum.EasingDirection.In, 0, false, 0)
	local moveInfo = TweenInfo.new(10, Enum.EasingStyle.Linear, Enum.EasingDirection.In, 0, false, 0)
	if willACloudAppear == 1 then
		local pickCloud = math.random(1, 4) --One in four chance of a smaller cloud
		if pickCloud ~= 1 then
			pickCloud = B[math.random(1, 10)]
		else
			pickCloud = A[math.random(1, 10)]
		end
		local createCloud = pickCloud:Clone()
		createCloud.Name = "1"
		createCloud.Parent = storage
		createCloud.Transparency = 1
		if headStart == nil then
			headStart = 0
		end
		createCloud.CFrame = point.CFrame * CFrame.new(math.random(-RANDOM, RANDOM), math.random(-10, 10), math.random(-RANDOM, RANDOM)) * CFrame.new(0, 0, -headStart * 750) --The RANDOM variable allows clouds to spawn with a bit of freedom in where they are in conjunction with their node
		local cloudAppear = TS:Create(createCloud, appearInfo, {Transparency = OPAQUE})
		cloudAppear:Play()
		local moveNum = 0
		spawn(function()
			repeat
				local cloudMove = TS:Create(createCloud, moveInfo, {CFrame = createCloud.CFrame * CFrame.new(0, 0, -SPEED * 10)})
				cloudMove:Play()
				wait(10)
				moveNum = moveNum + 1
			until moveNum >= (DISTANCE - headStart)
			local cloudGone = TS:Create(createCloud, appearInfo, {Transparency = 1})
			cloudGone:Play()
			wait(1)
			createCloud:Destroy()
		end)
	end
end
local createPoints = generatePoints()

spawn(function()
	for start = 1, 11 do   --This generates a bunch of clouds right when the player joins
		spawn(function() --Without it, the sky would at first be blank as clouds approached from the distance
			for i = 0, NUMBER do
				createCloud(createPoints[tostring(i)], start)
				wait()
			end
		end)
	end
end)

while wait(10) do
	spawn(function() 
		for i = 0, NUMBER do
			createCloud(createPoints[tostring(i)])
			wait()
		end
	end)
end

Any helpful tips on how I could improve is super appreciated! Thanks! :relaxed:

1 Like

Sorry, I believe this should go in #help-and-feedback:code-review since the code is working as intended and you are looking for improvements, you can just edit to change the topic later.

Anyways in order to optimize it well, we can either

  1. Limit the number of clouds being rendered.

  2. Limit the rate at which the clouds are being rendered.

Yeah, I’m wondering about the while wait function, in your game do you go indoors by any chance if so you can just stop rendering clouds indoors, Moreover since it’s purely aesthetic you can have the client decide the number of clouds with a graphics quality setting.

Edit: Moreover, an addition idea to help optimize by reducing the amount of clouds is being rendered is to see if the client is in view of a cloud as currently you are creating clouds for the entire.

Perhaps you can use WorldToScreen point to see if the cloud is in view of a player, and then choose to render it like what Horizon zero dawn does for chunks. But then you would have to evaluate if Raycasting is less expensive then yeah the current method which I’m uncertain of.

I haven’t done testing yet but man the gif above looks cool. Here’s a thread to the WorldToScreen point with code sample:

1 Like

Best to store the Clouds are in the ServerStorage if its rendered for the server and let everyone see them otherwise if its for the client then replicatedstorage
and also try not to model the clouds into unions, use meshparts and turn cancollide off and CollisionFidelity to Box

One thing I’ve seen people do is instead of generating clouds with parts, people run a beam over the top of the game, allowing for you to add cloud textures to fade over the top of the map, making realistic clouds without it costing performance. Hope this helps.

Here is an example: Cloud Example - Roblox

1 Like

Moved it to the right category, sorry!

I love the idea of being able to customize how many clouds are rendered! I may implement that!

Hmmm… WorldToScreenPoint… wouldn’t that require a RenderStepped function to constantly check the CFrame of the camera? I dunno if that’d be better than a while loop that occurs every 10 seconds. Am I wrong or would it require changing the transparency of all the clouds every frame, especially if the player looks around really quickly?

I think storing in ServerStorage would suck considering the clouds are always constantly moving and disappearing. Isn’t that just constant signals from the server to the client?

1 Like

Hmm, for this I know the solution to avoid rendering the cloud is to fully set transparency to 1 which is true, or moving the part really far away like how PartCache operates by moving the parts far away with a CFrame.

Actually, yeah that’s probably not the best idea and I’m not certain how the Horizon Zero Dawn system actually works it’s just a theoretical concept. If you were to use the concept you would probably need to measure user input to counteract that situation where the players look around quickly and implement stuff like blur to hide the rendering needed.

However, what matters is the script performance what are you getting especially activity and rate?

Yeah ping shouldn’t matter since it’s all locally, if it’s on the server then there might be a delay if the cloud will appear or not due to networking.

There are articles here to help as well have you tried looking through them?

https://developer.roblox.com/en-us/articles/Improving-Performance

2 Likes

turn your cloud unions into meshparts, unions are known to be quite laggy

2 Likes