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