Hi,
I’m working on creating a cape made of beams to represent what would be fabric, which works alright, it’s just that I’m not sure how I would apply a curve when the parent part is at an angle. I’m fine with the idea of bezier curves which I think I would be using for this, I’m just not too sure how I would make the cape curve like a real fabric cape would (assuming the cape needs to be “locked”/pinned to the humanoid root part’s top-left and top-right rear vertices).
Currently this is what I have.
My idea was initially to just not update the uppermost attachment when the humanoid root part rotates, however because the next attachments’ positions depend on this first attachment’s CFrame, it would make the cape not rotate when the parent part is at an angle, meaning the cape could stay at an angle which is not ideal because this doesn’t look very realistic:
Here is each segment, names are numbered 1-9 from top to bottom:
Ideally the cape would bend like so:
Anyway, here’s my script which is pretty unoptimized at the moment, I’m just working on getting the cape “physics” (if you can even call it that) right. I’ve added comments but I can give more explanation if needed:
for i,v in ipairs(attachments) do
local previous = attachments[(tonumber(v.Name) :: number) - 1]
if not previous then
runService:BindToRenderStep('CapeStep', Enum.RenderPriority.First.Value - 1, function(deltaTime)
v.WorldCFrame = CFrame.fromMatrix( -- "lock" the origin attachment to the vertical axis
v.WorldPosition,
-CFrame.identity.UpVector,
v.WorldCFrame.UpVector,
v.WorldCFrame.LookVector
)
end)
continue
end
v:GetPropertyChangedSignal('CFrame'):Connect(function() -- this should "limit" each attachment to only be ~0.5 studs from each other
local direction = previous.WorldCFrame:PointToObjectSpace(v.WorldCFrame.Position)
if direction.Magnitude < 0.501 then -- 501 for floating point errors, we check because we don't want the re-entrancy depth limit to be exceeded which would happen without this if-statement as the CFrame is still updated a lot
return
end
local unit = direction.Unit / 2 -- each attachment is .5 studs from one another so unit /2
v.WorldPosition = previous.WorldCFrame:PointToWorldSpace(unit)
end)
end
local function getValue(alpha)
return math.sin(alpha / 2 * math.pi) -- moving along the sine wave looks better than linearly imo
end
while true do
local previous = script.Parent:FindFirstChild('1').WorldCFrame
for i = 2, #identity:GetChildren() + 1 do
coroutine.wrap(function()
local v = identity:FindFirstChild(i) -- identity is a part at CFrame.identity
if v:IsA('Attachment') then
local goal = previous * CFrame.new(0.5, 0, 0) -- because of how beams are oriented, X is the vertical axis
local init = v.WorldCFrame
previous = init
local timePassed = 0
while timePassed < DELAY_TIME do -- update the current attachment to get closer to the goal based on the previous attachment
timePassed += task.wait()
local interpolatedGoal = init:Lerp(goal, getValue(math.clamp(timePassed / DELAY_TIME, 0, 1)))
v.WorldCFrame = interpolatedGoal
end
end
end)()
end
task.wait(DELAY_TIME) -- 1/16 for how many studs the char can walk in a second
end
Thanks!