How can I implement BulkMoveTo to this part rain script?

Hey all. So I have this neat little raycast rain system that is already pretty optimized, but there are still some minor framerate drop issues. And a good solution I can think of here is using BulkMoveTo which is perfect for my situation as I am moving hundreds of parts for every hearbeat.

But there’s one issue, I have no idea on how to create an array for the list of CFrames needed to move each and every single part effectively.

How should I do this?

local RainRadiusX = 80 -- Left, Right
local RainRadiusZ = 70 -- Forward, Back
local MaxRainDistance = 800
local RainSpeed = 300
local Rate = 15
local RainParts = 500
local RainOffsetZ = 30

local Params = RaycastParams.new()
Params.FilterType = Enum.RaycastFilterType.Whitelist

local Gamesettings = UserSettings().GameSettings
local RS = game:GetService("RunService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local TweenService = game:GetService("TweenService")
local Terrain = workspace.Terrain

local PartCache = require(ReplicatedStorage:WaitForChild("PartCache")) -- All this does is move parts to it's desired location when it needs to be used, or moves it very far away so that it doesn't get rendered.

local RainPartsFolder = Instance.new("Folder")
RainPartsFolder.Name = "RainContent"
RainPartsFolder.Parent = workspace

local RainPartTemplate = Instance.new("Part")
RainPartTemplate.Material = Enum.Material.SmoothPlastic
RainPartTemplate.Color = Color3.fromRGB(255, 255, 255)
RainPartTemplate.Size = Vector3.new(0.1, 5, 0.1)
RainPartTemplate.Transparency = 0.9
RainPartTemplate.CanCollide = false
RainPartTemplate.CanTouch = false
RainPartTemplate.CastShadow = false
RainPartTemplate.Anchored = true
RainPartTemplate.Name = "RainPart"

local SplashPart = Instance.new("Part")
SplashPart.Size = Vector3.new(0.1, 0.1, 0.1)
SplashPart.Transparency = 1
SplashPart.CanCollide = false
RainPartTemplate.CastShadow = false
SplashPart.Anchored = true
SplashPart.CanTouch = false
SplashPart.Name = "RainSplashPart"
SplashPart.Parent = RainPartsFolder

local SplashParticle = script.SplashEffect:Clone()
SplashParticle.Parent = SplashPart

local AbsorbantMaterials = { -- Materials that don't create the rain splash effect
	Enum.Material.Grass,
	Enum.Material.Fabric,
	Enum.Material.Sand,
}

local DeltaTime = 0
local RainDeltaIncrement = RainSpeed

local WhiteListIndex = 0
local Whitelist = {}

local Cache = PartCache.new(RainPartTemplate, RainParts, RainPartsFolder) -- All this module does is move parts either really far away out of render distance, or brings the part used it can be used as a rain drop instead of destroying and creating new parts

local function SetupWhitelist(InstanceToSearch)
	for i, child in ipairs(InstanceToSearch:GetDescendants()) do
		if child:IsA("BasePart") and child.CanCollide and child.Transparency < 1 then
			WhiteListIndex = WhiteListIndex + 1
			table.insert(Whitelist, WhiteListIndex, child) -- Add the part to the whitelist
		end
	end
end

function CastRain()
	Params.FilterDescendantsInstances = Whitelist
	local RainOrigin = (game.Workspace.CurrentCamera.CFrame * CFrame.new(0, 0, -RainOffsetZ)).Position + Vector3.new(0, math.random(100, 120), 0)
	local RandomPosition = RainOrigin + Vector3.new(math.random(-RainRadiusX/2, RainRadiusX/2), 0, math.random(-RainRadiusZ/2, RainRadiusZ/2))
	local Direction = Vector3.new(0, -MaxRainDistance, 0)
	local RainRay = workspace:Raycast(RandomPosition, Direction, Params)
	
	if RainRay then
		local HitPos = RainRay.Position
		
		local Dist = (RainOrigin - HitPos).Magnitude
		
		-- Create the rain drop
		local RainPart = Cache:GetPart()
		RainPart.Position = RandomPosition
		
		local RainFallCFrame = CFrame.new(0, -RainDeltaIncrement, 0)
		
		-- Move the rain drop until it hits the surface
		repeat
			RS.RenderStepped:Wait()
			RainPart.CFrame *= RainFallCFrame
			
		until RainPart.CFrame.Y <= HitPos.Y
		
		-- Create the splash effect
		if not table.find(AbsorbantMaterials, RainRay.Instance.Material) and RainRay.Instance.CanCollide then
			SplashPart.Position = HitPos
			SplashParticle:Emit(1)
		end
		
		Cache:ReturnPart(RainPart)
	end
end

local function CheckQuality()
	local Quality = Gamesettings.SavedQualityLevel.Value
	if Quality == 10 then
		Rate = 15
	elseif Quality >= 8 then
		Rate = 10
	elseif Quality >= 5 then
		Rate = 6
	else
		Rate = 3
	end
end

Gamesettings.Changed:Connect(CheckQuality)

RS.Heartbeat:Connect(function(step)
	for i = 1, Rate do
		CastRain()
		RainDeltaIncrement = RainSpeed * step
		DeltaTime = step
	end
end)

SetupWhitelist(workspace)
CheckQuality()

First off- I would STRONGLY advise you move to a local script. It’s not efficient for your server to cast hundreds of rays just for the rain.

Second- you should not cast every drop ‘literally’. Send one ray, calculate some points with an offset of 5X and 5Z for example, and spawn raindrops on them (with a particle emitter or such). The more parts- the more lag.

1 Like

This is already a local script. I was just told that heartbeat is faster than renderstepped. And also i want to stick to parts because i dont like using particle emitters since they look out of place when you look up or down. I also want to realistically simulate each rain drop with parts. Also i want to point out that parts are dirt cheap since they are baked into the engine. Especially with almost every property set to false. I am also using a part cache so i am never creating or destroying parts.

I’m unsure about heartbeat being faster then RenderStepped since RenderStepped is linked to the time it takes for your screen to refresh. (60/s on average), but the parts will lag regardless. You could try setting a limit per device, like 100 ‘drops’ on screen at a time.

Lag isnt an issue as of right now. Currently i can have over 300 rain drop parts on screen with no lag (at least on my 2013 mac and iphone 8)

I just want to know how to implement BulkMoveTo into my code instead of moving each raindrop separately. This will allow this run much better on more low end devices and allow for possibly even more rain drops on screen.

Well as I’ve taken from the Roblox wiki:

You should only use this function if you are sure that part movement is a bottleneck in your code
Which, I assume is not the case here as far as I know. I would strongly recommend just going ahead with part.CFrame, but, if you do want to use BulkMove, this is the way to do it:

local cfs = {}
local ps = {}

for x = 1, 10 do
	local p = Instance.new("Part")
	p.Parent = workspace
	p.Name = x
	p.Anchored = true
	
	p.Size = Vector3.new(1, 1, 1)
	
	
	ps[x] = p
	cfs[x] = CFrame.new(x*1.5, 20, 20)
end


workspace:BulkMoveTo(ps, cfs)

Basically, what you do is, you make two arrays indexed from 1 to the amount of parts you have and enter the two lists in the two arguments on BulkMoveTo()

For example, if you have a part indexed 1 in the part list, it will use index 1 from the CFrame list as it’s CFrame.

https://developer.roblox.com/en-us/api-reference/function/WorldRoot/BulkMoveTo

1 Like