[Live] Changes to Motor6D.Transform and Motor.CurrentAngle

This change is now enabled globally and the Studio Beta Feature is no longer available. The original announcement and details of the change are below.

Currently the behavior of Motor6Ds have some interesting frustrating edge cases. We’re going to fix that. Fixing this may impact some games that were relying on the edge case functionality.

If you are using Motor6D and animating Motor6D.Transform in models that you rigged yourself or with third-party tools and happen to be using Motor6Ds “backwards”, your game may be affected. Soon they will be not-backwards, which might mean “backwards” for you.

The legacy CurrentAngle and DesiredAngle properties of Motor6D inherited from Motor are also affected in the same way. The behavior of CurrentAngle on Motor itself is unaffected.

All of our canonical Humanoid rigging creates Motor6Ds with the “parent” part as Part0 and the “child” part as Part1, e.g. “RightKnee” has RightUpperLeg as Part0 and RightLowerLeg as Part1. If automatic Humanoid rigging or Humanoid.BuildRigFromAttachments is the only way you’re creating Motor6Ds you will not be affected. If you’ve created Motor6Ds yourself manually or with third party tools you may be affected, and should try your games with the Studio Beta Feature enabled (instructions below) to make sure the motors in your game behave as expected.

What’s the Problem? What’s Changing Changed?

Previously, the devhub almost correctly describes the joint relationship of a Motor6D:

Transform is the transformation between the “parent” part and the “child” part. The “parent” part will always be the part that is more directly connected to JointInstance.C0 the assembly root part defined by BasePart:GetRootPart(). This is not affected by which part is assigned to JointInstance.Part0 and which is JointInstance.Part1 . If the side the root part is on changes the interpretation of Transform will be inverted.

Similar to a WeldJoint, an active Motor6D will rigidly hold its two parts such that:
PartParent.CFrame * CParent * Transform == PartChild.CFrame * Child

I helped write that and this is behavior still confusing to me. It’s excessively complicated, and as a developer you have no easy way to tell what the real “parent” and “child” are in this context. You have to assume. You shouldn’t need to worry about this. That’s our job.

Still, this generally works. Until you do something reasonable like string a RopeConstraint between an R15’s foot or hand and an anchored part. The anchored part will become the mechanism root, and the character’s foot will become the R15’s GetRootPart() “Assembly” root instead of HumanoidRootPart. Internally, all of the character’s parts will now be positioned relative to the foot instead of the HumanoidRootPart, and the foot now becomes the “parent” of the lower leg instead of the other way around. Now the direction of LeftFoot.LeftAnkle.Transform is inverted.

Animator always assumes Part0 is the parent part in the internal transform hierarchy used to calculate final part positions, but if this isn’t the case the Animator will not compensate for this and your limbs can end up with a “backwards” inverse rotation.

image

We fixed this to have consistent behavior, regardless of the direction of the internal low-level transform hierarchy built from rigid joints. This way the scenario above won’t kneecap you or your characters, and you’ll never have to think about this nonsense again.

The simplified behavior is:

While the Motor6D is Active , it maintains the part positions such that:

Part0.CFrame * C0 * Transform == Part1.CFrame * C1

Much simpler. No more backwards knees.

As before, Transform is not immediately applied when it is changed by Animators or scripts. It is only applied once per frame after RunService.Stepped, right before physics simulates for the frame. This deferred update behavior makes modifying Transform very cheap compared to updating C0 or C1 of a Motor6D or any other kind of Weld-like joint when you are updating them every frame.

All of the above also applies to the MaxVelocity, CurrentAngle and DesiredAngle properties of Motor6D in the same way.

I Create My Own Motor6Ds and Might Be Affected…

This change is now enabled globally and the Studio Beta Feature is no longer available.

If you think this change might affect you, you can enable it now in Studio Beta Features:

image

If joints look backwards after enabling this try swapping Part0 and Part1 and fixing any scripts that might update those Transforms to use the Inverse() of that CFrame instead.

In most cases you should be able to publish any fixes to your game immediately, without waiting for this change to release.

When is This Changing?

This change was enabled globally on Tuesday, April 21st at 4:14 PM, PST.

We’re going to enable this change globally on Monday, April 20th.

139 Likes

This topic was automatically opened after 43 minutes.

Great update, the old behavior was super weird. I still don’t understand why that was intended in the first place.

13 Likes

Is there going to be an option to replicate client changes to a motor6d to the server soon? I’d love to see that

1 Like

Not sure what you mean. No plans for any new client-to-server replication. What’s the use case?

2 Likes

Just for scripts that currently use remote events to replicate motor6d changes to the server for things like, aiming in first person shooter games and the likes

4 Likes

I think for stuff like that it might make sense to continue doing that custom. For cases like aiming instead of replicating several Motor6D transforms you can probably just send a target and re-compute the transforms for the joints in the arm in a LocalScript on the other end. Less replicated data that way.

7 Likes

That’s true, i wish there was just a better way to do it properly, like a replicatedconnection object would be cool

8 Likes

that would be an amazing feature… make a post about it.

2 Likes

Will this affect third party 6dMotors if they do not use the .Transform property at all?

3 Likes

Great changes for sure needed this!

1 Like

Do i have to still use RunService.Stepped event for Motor6D.Transform?

2 Likes

No. But if you’re not using the Transform property at all you should not use Motor6D. They’re meant for animation, and changing Motor6D.Transform is very efficient for that and we’re currently optimizing it even more. Otherwise you should just use a Weld, which has the same effect with less overhead.

If you are animating C0/C1 you probably shouldn’t that, since that avoids any of the optimizations we have for animated Motor6Ds. It’s measurably more expensive.

2 Likes

If they are also being controlled by an Animator, yes. Animator will still overwrite Transform before Stepped, and Transform will be applied to move the parts immediately after Stepped.

Changes made any other time will be overwritten before they can take effect.

If there is no Animator you can change Transform at any time. Stepped is still the most appropriate place, the latest possible time closest to when it is applied, where you’ll have the most up to date inputs possible.

4 Likes

That honestly sounds like a neat idea for a module, to set/disable replication however you want. However, it could come with some implications, like people using it for the wrong reasons when doing all that per client would be the better option.

2 Likes

But if the 6DMotor uses the .CurrentAngle & .DesiredAngle property is it affected?

1 Like

Had to check, CurrentAngle and DesiredAngle are affected. I’ll update the post.

2 Likes

I made System for that, which replicates every instance to server only when that instance contains “ReplicatedConnection” object. Would be this useful? If I posted that to #resources:community-resources?

2 Likes

Ok, today is Monday. I don’t see the update in production. Works fine in Studio with the beta checkmark. When does it hit production?