NOW LIVE
This change was enabled on September 10th at 3:05 PM PST. It should take effect within 10 minutes on servers and upon restart for clients and studio.
Original Post
We are planning to make some behavior changes to the BasePart.CFrame
setter in the near future. If your game uses the CFrame property you should read this.
The value returned by the CFrame
getter is unchanged. Only the behavior of assigning to this property is changing.
We’re planning to enable this change globally on September 10th.
What’s Changing?
Currently, if you assign a new value to Part.CFrame and the part is connected to other parts with Constraints or Welds, you may get some surprising results. The current behavior is to find the connected part called the “mechanism root” and assign the given CFrame value to that part. That part may be a different part from the one you are assigning too.
We’re changing this to instead move the part’s Assembly, such that the part you assigned to ends up at the given CFrame, as intended. An “Assembly” is what we call the grouping of parts connected to a part by rigid or kinematic joints like Welds, ManualWelds, WeldConstraints, or Motor6Ds. Basically you should get what you expect and the rest of the part’s rigid body comes along for the ride.
This should be a much more intuitive behavior.
The existing behavior is definitely unituitive. It’s impossible to determine which part is a mechanism root in Lua. It is deterministic internally, but externally it’s effectively random. We use it internally for some physics networking logic, but it has no relevance otherwise. You shouldn’t have to worry about this.
A common workaround for this is to Anchor the part before changing it’s CFrame. This works around the issue by making the part it’s own mechanism root, but has some performance ramifications associated with destroying and recomputing assemblies when changing the anchored state. If you are using this workaround, this behavior change will not affect you. This hack should not be needed in the future once this change is enabled, however. Once this is fixed, we would not suggest doing this.
How Does This Affect My Games?
If your game uses workarounds for odd CFrame setter behavior, this change may effect your game.
Unaffected
Model:SetPrimaryPartCFrame
is unaffected. This is generally the best way to move collections of parts.
If you were anchoring parts before moving them (and un-anchoring them afterwards) this change will not affect your game. Stick with it for now, but we recommend removing this hack after the change takes effect.
Affected
If you were applying a fudge factor to compensate for some consistently incorrect behavior when changing CFrames, those fudge factors will become redundant and apply an additional offset.
If you were somehow relying on the setter moving another part instead, this will affect you, e.g. if you set wheel.CFrame on the wheel of a car, this would currently most likely move the larger body of the car that is connected to the wheel with Constraints to that CFrame instead. The constraints would snap the wheels back onto the car on the next physics step, pulling the car body back slightly. After this change only the single wheel will move, mostly likely snapping back to the car body on the next frame, so the car would not be moved much at all. If you’re doing something like this you will need to fix your scripts to set the CFrame on the correct body or use Model:SetPrimaryPartCFrame
.
You can use this Lua function to replicate the new behavior now for testing:
function setCFrame(part, cframe)
local assemblyRoot = part:GetRootPart()
local wasAnchored = assemblyRoot.Anchored
assemblyRoot.Anchored = true -- temp hack: makes sure part is a "mechanism root"
assemblyRoot.CFrame = cframe * part.CFrame:inverse() * assemblyRoot.CFrame
assemblyRoot.Anchored = wasAnchored
end
If you are concerned this change might negatively affect your game, let us know. We can enable this change early for particular places if you want to test in advance.