Hinge Joint Breaks When LocalScale is Changed

I have a composite GameObject with arms, legs, and head connected to a torso with hingejoint components. Each contains a non-kinematic rigidbody, collider, and is parented to a common empty GameObject. The mass for each rigidbody is exactly 1.
The colliders all exist on the same Layermask which is set to collide with itself. At original scale, the colliders do not overlap. Additionally, these GameObjects are linked to a prefab. All hinge joints are instantiated with a non-infinite breaking force.

When the local scale of the parent Transform is changed, the hinge joints break. This is observed when:

  • The common Layermask is set to not collide with itself.
  • When gravity is zero’d.
  • When Instantiating the prefab programatically and immediately changing the localscale of the parent transform.
  • When Instantiating the prefab from the editor and changing the localscale of the parent transform arbitrarily.
  • When Instantiating the prefab, setting each rigidbody to kinematic, changing the localscale, advancing a frame, and setting each rigidbody back to non-kinematic.
  • Setting break forces to infinity, setting localscale, setting break forces back to non-infinite values. Single frame operation.

On inspection, it appears that hinge joint anchors do not move when localscale is modified. This will account for the hingejoint breaks; transforms move, but they are expected to be in a previous location. The hingejoint will exert a force on itself in order to return to the expected position, but this force is enough to break some breaking forces. This is exacerbated with colliders and a increase in local scale; the hingejoints will need to move the joints to within a collider, which will probably result in massive forces. Think about that for a minute.

Originally, I was going to ask for ideas on how to avoid those breaks, but researching the issue seems to have given me some ideas. Regardless, I’ll submit this in case anyone else runs into the same issue, or has a better solution.

Proposed solution: During the frame where you adjust the localscale of a parent GameObject which contains hingejoints, scale the positions of the hingejoint anchors by the magnitude of the adjustment to the localscale. Assuming that this works, I will respond with confirmation. Else, frustration.


So, I tried out the anchor-adjustment strategy. Unfortunately, it doesn’t work. Kind-of. I’ve made some observations:

  1. If your Prefab is instantiated with the HingeJoints already attached and then local scale is changed, the break occurs. However, if your Prefab contains no pre-attached HingeJoints, but instead adds HingeJoints afterwards, the HingeJoints will not break after local scale is changed.
  2. If you recycle and reconstruct (put all the transforms back in the original positions, rotations, etc) the HingeJointed object (including recreating broken HingeJoints), your HingeJoints will not break when they have their local scale changed.

For clarity, adjusting the anchors of the HingeJoints doesn’t help at all. What matters is the difference between the positions of the transforms of the HingeJoint objects before and after the change to local scale. If the hinge joints don’t exist before the local scale is changed, then there can be no difference.

I’ve solved similar problem and want to share solution.

My situation was:
I have built chain of nodes with HingeJoint2D and angle limit. When i try to change scale of nodes or theirs parent limits work incorrectly.

What really happens:
I found that HingeJoint2D.referenceAngle changes after any of this operations, but limits are the same. It cause incorrect angle clamping.

I fix it by store angle limits of joint in world space and apply back after reference angle changed.

Here the code:


using UnityEngine;

public class JointCorrector : MonoBehaviour {
    private HingeJoint2D _joint;
    private float _referenceAngle;
    private float _lowerLimit;
    private float _upperLimit;

    void Start() {
        _joint = GetComponent<HingeJoint2D>();

        if (_joint != null) {
            _referenceAngle = _joint.referenceAngle;

            var localLimits = _joint.limits;
            _lowerLimit = _referenceAngle + localLimits.min;
            _upperLimit = _referenceAngle + localLimits.max;
        }
    }
    
    void Update() {
        if (_joint == null) {
            return;
        }

        float newAngle = _joint.referenceAngle;
        if (_referenceAngle != newAngle) {
            var limits = new JointAngleLimits2D();
            
            limits.min = Angle(_lowerLimit - newAngle);
            limits.max = Angle(_upperLimit - newAngle);
            
            _joint.limits = limits;
            _referenceAngle = newAngle;
        }
    }
    
    public static float Angle(float angle) {
        angle = Mathf.Repeat(angle, 360f);
        if (angle > 180f) {
            angle -= 360f;
        }
        return angle;
    }
}

Will be glad if it help anybody.

I’m faced with similar behavior in v. 4.3.0
See my project on github

There I’ve attached the smooth grow script to Bones. It seems that when localScale is changed in script, then Angle Limits in HingeJoint of child stops working. Or maybe I’m doing something wrong.

I found the same problem when changing a joint’s connectedBody.
Based on zzxzczxczxc’s answer, but I instead calculate the difference when something changes.

public static void UpdateHingeJoint2DConnectedBody(HingeJoint2D joint, Rigidbody2D newBody)
{
	float originalReferenceAngle = joint.referenceAngle;
	
	// Make changes here
	joint.connectedBody = newBody;
	
	float delta = Mathf.DeltaAngle(joint.referenceAngle, originalReferenceAngle);
	JointAngleLimits2D limits = joint.limits;
	limits.min += delta;
	limits.max += delta;
	joint.limits = limits;
}