Unity: Preserve Articulation Body State

One constraint of articulation bodies is that they lose their physics state if their hierarchy changes. This behaviour manifests as snapping back to their initial configuration/position. You can find a short overview of the constraints and advantages of articulation bodies here.

 

Problem

Changing the active state of the root articulation body is also a hierarchy change. This implies that disabling the root resets the physics state of the complete articulation body chain. I don’t want the active state of my game objects coupled to a physics reset.

 

Left: Intended range of movement.

Right: Default behaviour after re-enable -> state reset.

 

Solution

The reset can be countered by restoring the previous physics state of the hierarchy. It’s easier than it sounds. Most of the properties already contain the values of all articulation bodies in the tree. For an explanation of the underlying system, take a look at the sources linked in my documentation overview.

 

Add the following script to one of the articulation bodies of the hierarchy you want to enable/disable. It works independently of the position in the articulation tree. But I would recommend putting it on the root articulation body. This way the component is easier to find in the hierarchy.

 

Be aware that the setting Match Anchors of all articulation bodies must be disabled for this script to work correctly. The setting would recalculate the anchor of the joint on enable. This is a problem because the state of the articulation body is relative to its anchor position. Changeing the anchor makes the restored state invalid.

 

Strange behaviour after enable because the property ‘Match Anchors’ was enabled.

 

Script

using System.Collections.Generic;
using UnityEngine;

public class ArticulationBodyStatePreserver : MonoBehaviour
{
  [SerializeField]
  private ArticulationBody body;

  private bool backupValid = false;

  private readonly List<float> jointPositionBackup = new List<float>();
  private readonly List<float> jointVelocityBackup = new List<float>();

  private void Reset()
  {
    if (body == null)
      body = GetComponentInChildren<ArticulationBody>();
  }

  private void OnEnable()
  {
    if (backupValid)
    {
      body.SetJointPositions(jointPositionBackup);
      body.SetJointVelocities(jointVelocityBackup);
    }
  }

  private void FixedUpdate()
  {
    jointPositionBackup.Clear();
    jointVelocityBackup.Clear();

    body.GetJointPositions(jointPositionBackup);
    body.GetJointVelocities(jointVelocityBackup);

    backupValid = true;
  }
}