I’m not going to deeply explain how to create cloth simulation, but I will publish the one I made, which you can see in this video: https://www.youtube.com/watch?v=U5j8l4_o288 Mainly publishing this because the way it works is pretty bad in my opinion and its almost a year old now.
The basics
You simply need a mesh with TONS of bones (110 in my model) and a pretty simple script. This script can be ran both on server and client, however its not recommended to handle it on server.
(theres better ways to script that)
repeat task.wait(0.1) until game:IsLoaded() == true
local cloth = game:GetService('Workspace'):WaitForChild('Cloth')
local plane = cloth:WaitForChild('Plane')
local bones = {}
local groupsAmount = #plane:GetChildren()
for count = 1, groupsAmount do
bones[count] = {}
end
local debugMode = true
function bone(bone)
local physPart = Instance.new('Part')
local a = 0.4
physPart.Size = Vector3.new(a,a,a)
--physPart.Shape = Enum.PartType.Ball
physPart.Position = bone.WorldPosition
physPart.Anchored = false
physPart.Color = Color3.new(1, 0, 0)
physPart.Parent = cloth.Physics
if debugMode == false then
physPart.Transparency = 1
end
local att = Instance.new('Attachment', physPart)
att.Name = 'Att'
local info = string.split(bone.Name, '.')
local group = tonumber(info[2])
local index = tonumber(info[3])
bones[group][index] = {physPart, bone}
physPart.Name = bone.Name
end
for i, v in pairs(plane:GetDescendants()) do
if v:IsA('Bone') then
bone(v)
end
end
local rows = {}
for count = 1, #plane:GetChildren() do
rows[count] = {}
end
for i, v in pairs(bones) do --// physPart, bone
for d, b in pairs(v) do
if d ~= 1 then
local rope = Instance.new('RopeConstraint', cloth.Physics)
rope.Attachment0 = v[d-1][1].Att
rope.Attachment1 = b[1].Att
rope.Name = rope.Attachment0.Parent.Name..rope.Attachment1.Parent.Name
rope.Visible = debugMode
rope.Color = BrickColor.new('Really blue')
rope.Length = (v[d-1][1].Position-b[1].Position).Magnitude
else
b[1].Anchored = true
end
table.insert(rows[d], b[1])
end
end
for i, v in pairs(rows) do
for ii, b in pairs(v) do
if ii ~= 1 then
local rope = Instance.new('RopeConstraint', cloth.Physics)
rope.Attachment0 = b.Att
rope.Attachment1 = rows[i][ii-1].Att
rope.Name = rope.Attachment0.Parent.Name..rope.Attachment1.Parent.Name
rope.Visible = debugMode
rope.Color = BrickColor.new('Really blue')
rope.Length = (b.Position-rows[i][ii-1].Position).Magnitude
end
end
end
game:GetService('RunService').Heartbeat:Connect(function()
for i, v in pairs(bones) do
for i, b in pairs(v) do
b[2].WorldPosition = b[1].Position
end
end
end)
What this script does:
Splits all the bones into colums, rows
Creates a part for each bone
Connects those parts with ropes
Synces bones with thier physical parts on Heartbeat
Cloth ends up looking like this with visualization:
Only thing I would consider is that you could consider using CFrame since it is faster and more accurate. Unless it breaks the function
function bone(bone)
local physPart = Instance.new('Part')
local a = 0.4
physPart.Size = Vector3.new(a,a,a)
--physPart.Shape = Enum.PartType.Ball
physPart.Position = bone.WorldPosition
physPart.Anchored = false
physPart.Color = Color3.new(1, 0, 0)
physPart.Parent = cloth.Physics
if debugMode == false then
physPart.Transparency = 1
end
local att = Instance.new('Attachment', physPart)
att.Name = 'Att'
local info = string.split(bone.Name, '.')
local group = tonumber(info[2])
local index = tonumber(info[3])
bones[group][index] = {physPart, bone}
physPart.Name = bone.Name
end
Since parts mostly spin like crazy, it will result in mesh deforming. You can try that and see yourself. That’s why I think the method I used is bad too.
You could test CFrame.Position and see if there is any different in performance, if their is any it may be seen in a simulation in scale. I would also reccomend making a template part with the consistent properties that are defined and replicated. Also you should see if your function still works without tonumber
Also one final thing is you can define the plane
plane:GetChildren() as planearray=plane:GetChildren()
Also check out this code I wrote about the cframe of bones
if v:IsA("BasePart") or v:IsA("Bone") or v:IsA("Attachment") then
local Vcf = v.CFrame
local _,_,_,C0,C1,C2,c3,c4,c5,c6,c7,c8 = Vcf:components()
if v:IsA("BasePart") then
v.Size = v.Size * Scale
v.CFrame = CFrame.new(Vcf.p * Scale) * CFrame.new(0,0,0,C0,C1,C2,c3,c4,c5,c6,c7,c8)
v.AssemblyLinearVelocity=Vector3.new(0,0,0)
else
v.CFrame = CFrame.new(Vcf.p * Scale) * CFrame.new(0,0,0,C0,C1,C2,c3,c4,c5,c6,c7,c8)
end
Yeah, this code is definitely possible to improve. I wrote it 10 months ago and haven’t touched it since then. Moments like multiple plane:GetChildren() may occur, I was way worse in Luau.
Also make sure you are putting the cloth script inside an Actor! Super easy to utilize that performance gain by simply placing the script inside of an actor. Another thing is you can always throttle or change the linear velocity. to stop things from getting out of control.
v.AssemblyLinearVelocity=Vector3.new(0,0,0)
But the Resize function I provided is interesting tool I would reccomend you check out potnetially as well. It has advantages over the ScaleTo API such as working on parts and their descendants. and not changing the initial position of the model, in addition to having a tween function to gradually increase the size of a model. It works in real time on animated rigs without yeeting them out of existance like the scaleto api.
Never tested it in games, you could improve performance by not simulation cloth far away + decreasing the amount of bones. I still wouldn’t recommend to use it.
why would it
it is possible, but it needs to be implemented
i would recommend adding a constant force equal to the current wind force with a direction vector equal to the wind’s vector to each point in the simulation
this might be slightly inaccurate due to aerodynamics issues, but i have yet to see someone conducting a realtime CFD simulation in Roblox.
if none of what i said makes sense to you, just ask chatgpt lol.