Home>

Configurable Joint The connected capsule is bent using Add Relative Torque.
When bending, I want the orange capsule to stop in a bent state, and when gravity is applied, I want it to fall while maintaining the bent state.

When I bent it while using Torque, inertia worked and I couldn't stop it when there was no input from the game controller.
Also, when using Freeze of "Constraints" of Rigidbody, the position was fixed in world coordinates (floating in the air), and gravity could not be applied.

Other things I tried

1. 1. I also tried the method described in the following URL as a method to eliminate inertia.
https://www.tutorialfor.com/go.php?id=105824
If the speed of ↓ is set to zero, the amount of gravity that you originally want to work will be zero.

rigid.velocity = Vector3.zero;


2. Make Constraints work locally on the Rigid Body
I also tried this.
https://answers.unity.com/questions/404420/rigidbody-constraints-in-local-space.html
I imitated it and described it as follows, but I could not stop it.

Vector3 LocalSpeed ​​= transform.InverseTransformDirection (rigid.velocity);
LocalSpeed.x = 0;
LocalSpeed.y = 0;
LocalSpeed.z = 0;


I also tried using "GetRelativePoitVelocity" and "magnitude" to limit the bending speed, but the result did not change and I could not stop it.

Thing I want to do

Place the orange capsule on the child of the blue capsule and
"When there is no input from the game controller"
⇒ "Only the local coordinates of the orange capsuleTo get the Rigid Body's Constraints Freeze Positon to work. "
I think that the above problem can be solved if it can be done.

I'm sorry for the introductory question, but I would appreciate it if anyone could teach me.
that's all.

Postscript (November 12, 2020) Stop using local coordinates

To make the axis of rotation easier to understand, we will explain it in Sphere. The yellow ball is the part that I want to apply the script and rotate with torque.

Hierarchy hierarchy creates parent-child relationships in stages as shown in ↓. The basic blue ball is "Sphere".

By taking the above hierarchical structure, the value of the Z angle (25 degrees) of the Transform of each yellow ball can be obtained neatly.

↓ Image when bent

If i do not take a parent-child relationship, the total value of the root (blue ball side) angles will come out. I think it will be difficult to specify the angle.

Using this hierarchical structure, I am wondering if it is possible to stop the inertia and fix the position well using the local Z angle coordinates of the yellow ball. For example, turn the target angle 30 degrees! I am thinking about whether it is possible to keep (freeze) only the local coordinate Z30 degrees when the speed gradually decreases when approaching 30 degrees and when it comes to the 30 degree position.

that's all.

  • Answer # 1

    How about adding a Fixed Joint only when you don't want to move the angle of the orange capsule and tightly restraining the positional relationship between the two?

    using UnityEngine;
    [RequireComponent (typeof (Rigidbody), typeof (Configurable Joint))]
    public class CapsuleController: MonoBehaviour
    {
        [SerializeField] private float torqueMagnitude = 5.0f;
        private Rigidbody this Rigidbody;
        private Rigidbody parent Rigidbody;
        private FixedJoint fixedJoint;
        private float input;
        private void Start ()
        {
            this.thisRigidbody = this.GetComponent<Rigidbody>();// Orange capsule
            this.parentRigidbody = this.transform.parent.GetComponent<Rigidbody>();// Blue capsule
            this.SetKinematic (true);
        }
        private void SetKinematic (bool value)
        {
            if (value)
            {
                this.parentRigidbody.isKinematic = true;
                this.thisRigidbody.useGravity = false;
                this.thisRigidbody.velocity = Vector3.zero;
                this.thisRigidbody.angularVelocity = Vector3.zero;
                this.Lock ();
            }
            else else
            {
                this.parentRigidbody.isKinematic = false;
                this.thisRigidbody.useGravity = true;
                this.Lock ();
            }
        }
        private void Lock ()
        {
            if (this.fixedJoint! = Null)
            {
                return;
            }
            this.fixedJoint = this.gameObject.AddComponent<FixedJoint>();
            this.fixedJoint.connectedBody = this.parentRigidbody;
        }
        private void Free ()
        {
            if (this.fixedJoint == null)
            {
                return;
            }
            Destroy (this.fixedJoint);
            this.fixedJoint = null;
        }
        private void Update ()
        {
            this.input = this.parentRigidbody.isKinematic? Input.GetAxis ("Vertical"): 0.0f;
            // If there is no input, attach Fixed Joint to lock it, if there is input, unlock it
            if (Mathf.Approximately (this.input, 0.0f)){
                this.Lock ();
            }
            else else
            {
                this.Free ();
            }
            // Turn off the blue capsule isKinematic in the space bar,
            // Turn on use Gravity of the orange capsule and start falling
            if (Input.GetKeyDown (KeyCode.Space))
            {
                this.SetKinematic (false);
            }
        }
        private void FixedUpdate ()
        {
            // In this experiment, the rotation axis of the orange capsule is set to the local X axis.
            // Torque around the X axis is applied, but in the case of the questioner, is it the Y axis?
            this.thisRigidbody.AddRelativeTorque (this.input * this.torqueMagnitude, 0.0f, 0.0f);
        }
    }
    Postscript

    It's an unfounded intuition, but when you keep in mind that it's a stepping stone to make something like a snake-shaped robot, I have a feeling that it is better to keep the mechanism that drives each segment as simple as possible. ..
    Regarding the driving method, personally, the originalAddRelativeTorqueThe image of "Pinch and twist the orange capsule with your fingers" ... In other words, I felt that the snake was rotating not with the power of the snake itself, but with the force from the outside. I feel that the image of rotating with a motor installed in the joint part is more suitable than that, but how about it?

    I tried the orange capsule script as follows. The essence isUpdateWhenFixedUpdateso,StartInside, we just set the parameters of the joint.
    Even if you don't set it here, you can enter it directly on the joint inspector, but there are so many setting items that you might forget which one you messed with, so I did this.

    using UnityEngine;
    [RequireComponent (typeof (Configurable Joint))]
    public class CapsuleController2: MonoBehaviour
    {
        [SerializeField] private float angularSpeedInDegrees = 120.0f;
        [SerializeField] private float damper = 100.0f;
        [SerializeField] private float angularLimitInDegrees = 60.0f;
        private ConfigurableJoint configurableJoint;
        private float input;
        private void Start ()
        {
            this.configurableJoint = this.GetComponent<ConfigurableJoint>();
            this.configurableJoint.xMotion = ConfigurableJointMotion.Locked;
            this.configurableJoint.yMotion = ConfigurableJointMotion.Locked;
            this.configurableJoint.zMotion = ConfigurableJointMotion.Locked;
            this.configurableJoint.angularXMotion = ConfigurableJointMotion.Limited;
            this.configurableJoint.angularYMotion = ConfigurableJointMotion.Locked;
            this.configurableJoint.angularZMotion = ConfigurableJointMotion.Locked;
            this.configurableJoint.highAngularXLimit = new SoftJointLimit
            {
                limit = this.angularLimitInDegrees
            };
            this.configurableJoint.lowAngularXLimit = new SoftJointLimit
            {
                limit = -this.angularLimitInDegrees
            };
            this.configurableJoint.angularXDrive = new JointDrive
            {
                maximumForce = float.MaxValue,positionSpring = 0.0f,
                positionDamper = this.damper
            };
        }
        private void Update ()
        {
            this.input = Input.GetAxis ("Vertical");
        }
        private void FixedUpdate ()
        {
            this.configurableJoint.targetAngularVelocity = new Vector3 (
                this.input * this.angularSpeedInDegrees * Mathf.Deg2Rad,
                0.0f,
                0.0f);
        }
    }

    Also, the colliders of both capsulesBoxColliderI replaced it with. By using prisms instead of round capsules, it is my soul to prevent moss from accidentally falling after falling on the ground. I thought about turning on Freeze Rotation Y, but it seems that a huge amount of force is applied as a result of the solution of the force, and sometimes it blows high in the sky.

    Besides, I stopped making the blue capsule and the orange capsule a parent-child relationship. In a parent-child relationship, when the blue capsule moves and rotates, the orange capsule, which is a child object, also moves and rotates. From experience, I think that directly manipulating the position and orientation of objects under the control of physics simulation will not be fragile, and I think that such movement and rotation of orange capsules will have similar properties. That is.

    When I moved it, it became like the figure below. The parable I mentioned in the comments section behaves like a skydiving person.

    Also, I was wondering what kind of behavior would occur if the body segments driven by such a concept were connected in a chain, so I set up the object as shown in the figure below. Head is a blue capsule, Segments 1-9 are orange capsules, Segments 1-9 areConfigurable JointSegment1 is connected to Head, Segment2 is connected to Segment1, and so on.
    Head is the only colliderCapsule Collider, OthersBoxColliderIt was made.

    Head and Segments 1 to 8 do not have scripts, and I attached the following script only to Segment9.
    CapsuleController2IstargetAngularVelocityWhereas the method of setting the speed of the motor usingtargetRotationThe difference is that the target angle is given using, but the joints between the segments are the same.

    using System.Collections.Generic;
    using UnityEngine;
    [RequireComponent (typeof (Configurable Joint))]
    public class WormController: MonoBehaviour
    {
        [SerializeField] private float omegaInDegrees = 90.0f;
        [SerializeField] private float timeOffset = 0.5f;
        [SerializeField] private float spring = 500.0f;
        [SerializeField] private float damper = 100.0f;
        [SerializeField] private float angularLimitInDegrees = 60.0f;
        private readonly List<ConfigurableJoint>joints = new List<ConfigurableJoint>();
        private float time;
        private void Start ()
        {
            var j = this.GetComponent<ConfigurableJoint>();
            do
            {
                j.xMotion = ConfigurableJointMotion.Locked;
                j.yMotion = ConfigurableJointMotion.Locked;
                j.zMotion = ConfigurableJointMotion.Locked;
                j.angularXMotion = ConfigurableJointMotion.Free;
                j.angularYMotion = ConfigurableJointMotion.Locked;
                j.angularZMotion = ConfigurableJointMotion.Locked;
                j.angularXDrive = new JointDrive
                {
                    maximumForce = float.MaxValue,
                    positionSpring = this.spring,
                    positionDamper = this.damper
                };
                this.joints.Add (j);j = j.connectedBody.GetComponent<ConfigurableJoint>();
            } while (j! = null);
        }
        private void FixedUpdate ()
        {
            for (var i = 0;i<this.joints.Count;i ++)
            {
                var j = this.joints [i];
                var t = Mathf.Max (this.time-(i * this.timeOffset), 0.0f);
                var angle = this.angularLimitInDegrees * Mathf.Sin (t * this.omegaInDegrees * Mathf.Deg2Rad);
                j.targetRotation = Quaternion.Euler (angle, 0.0f, 0.0f);
            }
            this.time + = Time.deltaTime;
        }
    }

    When I ran it, it behaved like a horrifying monster ...

    Postscript: Capsule operation by targetRotation
    using UnityEngine;
    [RequireComponent (typeof (Configurable Joint))]
    public class CapsuleController3: MonoBehaviour
    {
        [SerializeField] private float angularSpeedInDegrees = 120.0f;
        [SerializeField] private float spring = 500.0f;
        [SerializeField] private float damper = 100.0f;
        [SerializeField] private float angularLimitInDegrees = 60.0f;
        private ConfigurableJoint configurableJoint;
        private float angle;
        private void Start ()
        {
            this.configurableJoint = this.GetComponent<ConfigurableJoint>();
            this.configurableJoint.xMotion = ConfigurableJointMotion.Locked;
            this.configurableJoint.yMotion = ConfigurableJointMotion.Locked;
            this.configurableJoint.zMotion = ConfigurableJointMotion.Locked;
            this.configurableJoint.angularXMotion = ConfigurableJointMotion.Free;
            this.configurableJoint.angularYMotion = ConfigurableJointMotion.Locked;
            this.configurableJoint.angularZMotion = ConfigurableJointMotion.Locked;
            this.configurableJoint.angularXDrive = new JointDrive
            {
                maximumForce = float.MaxValue,
                positionSpring = this.spring,
                positionDamper = this.damper
            };
        }
        private void Update ()
        {
            var input = Input.GetAxis ("Vertical");
            this.angle = Mathf.Clamp (
                this.angle + (this.angularSpeedInDegrees * Time.deltaTime * input),
                -this.angularLimitInDegrees,
                this.angularLimitInDegrees);
            this.configurableJoint.targetRotation = Quaternion.Euler (this.angle, 0.0f, 0.0f);
        }
    }

  • Answer # 2

    Articulation BodyI tried.
    Imagine the endoscope introduced in the comment section, and divide the roles as follows.

    Root ... to an empty object as the root of the articulationArticulation BodyJust attached.

    Tail ... a blue rectangular parallelepiped. The type isPrismatic JointSo, only parallel movement in the body axis direction is allowed. I intend to use the arm to insert the endoscope into the pipe.

    Passive Segment ... Gray capsule. The type isSpherical JointYou can swing up to ± 90 °. Of the swingstiffnessis 0,dampingIs set to a large value to some extent, and this is connected in a chain to behave as a string with a certain degree of hardness. You can twist it freely, but you can twist itstiffnessIs a very large value,dampingIs a fairly large value (twist)LockedMotionWhen I tried to completely prohibit the rotation, I tried to forcibly eliminate the twist, and sometimes it went wild, so I tried to allow some twist). I'm going to code an endoscope.

    Active Segment ... Yellow capsule. The type isSpherical JointThen, you can swing up to ± the upper limit of the rotation angle described later ÷ the number of segments.stiffnessIs a very large value while rotating quickly to the target angledampingAs a fairly large value, I tried to prevent overshoots as much as possible.stiffnessBecause of its large size, it does not easily sag under external forces such as gravity, and can maintain its bent shape. Since it has high followability to the target angle, it seems that the target angle will behave like that by moving it slowly without changing it suddenly. However, due to the deflection of the Passive Segment, etc., the endoscope as a whole seemed to have some spring motion. In addition, only the root segment can be twisted so that it can rotate around the body axis by manipulating the target angle. I intend to move the tip of the endoscope.

    Head ... Red cylinder. The type isFixedJointAnd it is in a state of being fixed at the end of Active Segment. See belowEndoscopeControllerIs attached. It is not an essential part for operation, but I did this because I wanted to give it a concrete shape as an articulation operation control role. As a bonus, it is equipped with a camera so that you can see the images from the endoscope.

    Of each partArticulation BodyAt the time of editing the scene, the setting of is limited to adjusting the position and orientation of the anchor (in this case, the X axis of the anchor is oriented toward the local Z axis, which is the body axis direction), and various physical characteristics are set to Head. AttachedEndoscopeControllerI set it with.

    using System.Collections.Generic;
    using System.Linq;
    using UnityEngine;
    public class EndoscopeController: MonoBehaviour
    {
        [SerializeField] private float linearSpeed ​​= 5.0f;
        [SerializeField] private float angleLimit = 150.0f;
        [SerializeField] private float angularSpeed ​​= 60.0f;
        [SerializeField] private float activeSegmentStiffness = 1e + 07f;
        [SerializeField] private float activeSegmentDamping = 1e + 05f;
        [SerializeField] private float passiveSegmentSwingDamping = 1e + 04f;
        [SerializeField] private float passiveSegmentTwistStiffness = 1e + 07f;
        [SerializeField] private float passiveSegmentTwistDamping = 1e + 05f;
        [SerializeField] private float tailDamping = 1e + 04f;
        [SerializeField] private bool lockPassiveSegmentSwingH;
        [SerializeField] private bool lockPassiveSegmentSwingV;
        [SerializeField] private bool lockPassiveSegmentTwist;
        [SerializeField] private float headMass = 0.1f;
        [SerializeField] private float activeSegmentMass = 0.1f;
        [SerializeField] private float passiveSegmentMass = 1.0f;
        [SerializeField] private float tailMass = 2.0f;
        [SerializeField] private string twistInputName = "Horizontal";
        [SerializeField] private string linearInputName = "Vertical";
        [SerializeField] private string swingInputXName = "Horizontal2";
        [SerializeField] private string swingInputYName = "Vertical2";
        // Each part
        private ArticulationBody head;
        private readonly List  activeSegments = new List  ();
        private ArticulationBody lastActiveSegment;
        private readonly List  passiveSegments = new List  ();
        private ArticulationBody tail;
        private ArticulationBody root;
        // Target angle
        private Vector2 targetSwingAngle;
        private float targetTwistAngle;
        private void Awake ()
        {
            // First get each part
            this.head = this.GetComponent  ();
            var nextBody = this.head.transform.parent.GetComponent  ();
            while (nextBody.name.Contains ("Active"))
            {
                this.activeSegments.Add (nextBody);
                nextBody = nextBody.transform.parent.GetComponent  ();
            }
            this.lastActiveSegment = this.activeSegments.Last ();
            while (nextBody.name.Contains ("Passive"))
            {
                this.passiveSegments.Add (nextBody);nextBody = nextBody.transform.parent.GetComponent  ();
            }
            this.tail = nextBody;
            this.root = this.tail.transform.parent.GetComponent  ();
            Debug.Log ($"Active segments: {this.activeSegments.Count},"
     Passive segments: {this.passiveSegments.Count} ");
            // Head settings
            this.head.mass = this.headMass;
            this.head.jointType = ArticulationJointType.FixedJoint;
            // Active segment settings
            var angleLimitOfSegment = this.angleLimit/this.activeSegments.Count;
            foreach (var segment in this.activeSegments)
            {
                segment.mass = this.activeSegmentMass;
                segment.jointType = ArticulationJointType.SphericalJoint;
                segment.swingYLock = segment.swingZLock = ArticulationDofLock.LimitedMotion;
                segment.twistLock = ArticulationDofLock.LockedMotion;
                segment.yDrive = segment.zDrive = new ArticulationDrive
                {
                    upperLimit = angleLimitOfSegment,
                    lowerLimit = -angleLimitOfSegment,
                    stiffness = this.activeSegmentStiffness,
                    damping = this.activeSegmentDamping,
                    forceLimit = float.MaxValue
                };
            }
            // Partially change the settings only for the active segment at the root
            this.lastActiveSegment.twistLock = ArticulationDofLock.FreeMotion;
            this.lastActiveSegment.xDrive = new ArticulationDrive
            {
                stiffness = this.activeSegmentStiffness,
                damping = this.activeSegmentDamping,
                forceLimit = float.MaxValue
            };
            // Passive segment settings
            foreach (var segment in this.passiveSegments)
            {
                segment.mass = this.passiveSegmentMass;
                segment.jointType = ArticulationJointType.SphericalJoint;
                segment.swingYLock = this.lockPassiveSegmentSwingH
                    ? ArticulationDofLock.LockedMotion
                    : ArticulationDofLock.LimitedMotion;
                segment.swingZLock = this.lockPassiveSegmentSwingV
                    ? ArticulationDofLock.LockedMotion
                    : ArticulationDofLock.LimitedMotion;
                segment.twistLock = this.lockPassiveSegmentTwist
                    ? ArticulationDofLock.LockedMotion
                    : ArticulationDofLock.FreeMotion;
                segment.xDrive = new ArticulationDrive
                {
                    stiffness = this.passiveSegmentTwistStiffness,
                    damping = this.passiveSegmentTwistDamping,
                    forceLimit = float.MaxValue
                };
                segment.yDrive = segment.zDrive = new ArticulationDrive{
                    upperLimit = 90.0f,
                    lowerLimit = -90.0f,
                    damping = this.passiveSegmentSwingDamping,
                    forceLimit = float.MaxValue
                };
            }
            // Tail setting
            this.tail.mass = this.tailMass;
            this.tail.jointType = ArticulationJointType.PrismaticJoint;
            this.tail.linearLockX = ArticulationDofLock.FreeMotion;
            this.tail.linearLockY = this.tail.linearLockZ = ArticulationDofLock.LockedMotion;
            this.tail.xDrive = new ArticulationDrive
            {
                damping = this.tailDamping,
                forceLimit = float.MaxValue
            };
            // Route settings
            this.root.useGravity = false;
            this.root.immovable = true;
        }
        private void FixedUpdate ()
        {
            // Bending of active segment
            var swingInput = new Vector2 (
                Input.GetAxis (this.swingInputXName),
                Input.GetAxis (this.swingInputYName));
            this.targetSwingAngle + = Time.deltaTime * this.angularSpeed ​​* swingInput;
            this.targetSwingAngle.x = Mathf.Clamp (this.targetSwingAngle.x, -this.angleLimit, this.angleLimit);
            this.targetSwingAngle.y = Mathf.Clamp (this.targetSwingAngle.y, -this.angleLimit, this.angleLimit);
            var targetAngleOfSegment = this.targetSwingAngle/this.activeSegments.Count;
            foreach (var segment in this.activeSegments)
            {
                var segmentDrive = segment.yDrive;
                segmentDrive.target = targetAngleOfSegment.x;
                segment.yDrive = segmentDrive;
                segmentDrive.target = targetAngleOfSegment.y;
                segment.zDrive = segmentDrive;
            }
            // Twist of active segment
            var twistInput = Input.GetAxis (this.twistInputName);
            this.targetTwistAngle + = Time.deltaTime * this.angularSpeed ​​* twistInput;
            var lastActiveSegmentDrive = this.lastActiveSegment.xDrive;
            lastActiveSegmentDrive.target = this.targetTwistAngle;
            this.lastActiveSegment.xDrive = lastActiveSegmentDrive;
            // Push and pull the tail
            var linearInput = Input.GetAxis (this.linearInputName);
            var tailDrive = this.tail.xDrive;
            tailDrive.targetVelocity = linearInput * this.linearSpeed;
            this.tail.xDrive = tailDrive;
        }
    }

    The hierarchy has a spectacular look.

    As an operation method, I assumed that the left stick pushes and pulls the tail up and down, the left stick left and right twists the movable part, the right stick bends the movable part up, down, left and right, and so on.
    I tried to move my head, but so far I haven't been able to make it stand still when I stop the operation, and it's shaking a little. Perhaps you should compromise where appropriate. Excessive restraint to keep it still could cause a rampage in an attempt to relieve the force against the restraint ...

    I tried to explore the maze because it was a big deal, but the code is too short to go too far. Articulation seems to be limited to a maximum of 64 connections, so it may not be possible to make it too long.

    The code part is normalRigidbodyThere may be a policy to make it a chain ofRigidbodyWhenArticulation BodyI can't find anything like a joint that can easily connect to, so it seems that it will take some time and effort.

  • Answer # 3

    I tried what happens with hinge joints.
    The objects used in the experiment are just Spheres that can be created from the menu, and four of them are arranged side by side.Rigidbody,HingeJoint, See belowArmSegmentI'm attaching a script.RigidbodyOrHingeJointWas still attached, and the joints were set in the script.
    In addition, we have created a hierarchical structure of parents, children, grandchildren, and great-grandchildren, following the screenshot of the questioner. I assigned a blue material to the parents and a yellow material to the children, but I wanted to make the pattern of those materials a checkered pattern like a beach ball to make it easier to see the rotation state.

    ArmSegmentIs as follows. On the Inspector (or from another script)targetAngleWhen you operate, it tries to adjust the angle of the hinge toward it.

    using UnityEngine;
    #if UNITY_EDITOR
    using UnityEditor;
    #endif
    [RequireComponent (typeof (Rigidbody), typeof (HingeJoint))]
    public class ArmSegment: MonoBehaviour
    {
        // Target angle
        public float targetAngle;
        // Hinge axis orientation
        [SerializeField] private Vector3 axis = Vector3.forward;
        // If off, control with hinge spring, damper, targetPosition
        // If on, control with gain, radialTime, integrationTime
        [SerializeField] private bool usePidControl;
        [Header ("Spring-Damper Control")]
        // Spring strength of hinge joint
        [SerializeField] private float spring = 100000.0f;
        // Hinge joint damper strength
        [SerializeField] private float damper = 10000.0f;
        [Header ("PID Control")]
        // Proportional constant when applying torque proportional to the deviation from the target angle
        [SerializeField] private float gain = 1000.0f;
        // Strength of the effect of the differential term
        [SerializeField] private float derivativeTime = 0.2f;
        // Weakness of the integral term
        [SerializeField] private float integrationTime = 100.0f;
        // Deviation from the target angle at the time of the previous Fixed Update ... Used to calculate the deviation rate of change
        // targetAngle has changed significantly, or the arm has been impacted
        // This value fluctuates when the angle deviates sharply from the target angle
        // Increasing the derivativeTime will apply additional torque to correct the deviation
        private float previousDeltaAngle;
        // Cumulative deviation from the target angle
        // If this deviates from zero, stay above the target angle for a long time
        // Or it may remain missing
        // Lowering the integration Time will apply additional torque to correct the deviation
        private float integratedDeltaAngle;
        private ArmSegment parent;
        private new Rigidbody rigidbody;
        private HingeJoint joint;
        private void Awake ()
        {
            // The hinge anchor is Vector3.zero and the center of the ball is the axis of rotation.
            var p = this.transform.parent;
            this.parent = p == null? null: p.GetComponent  ();
            this.rigidbody = this.GetComponent<Rigidbody>();
            this.joint = this.GetComponent<HingeJoint>();
            this.joint.autoConfigureConnectedAnchor = true;
            this.joint.axis = this.axis;
            this.joint.anchor = Vector3.zero;}
        private void Start ()
        {
            // Grandson hinge to grandson Rigidbody, grandson hinge to child Rigidbody,
            // Connect the child hinge to the parent Rigidbody and the parent hinge to the world space
            this.joint.connectedBody = this.parent == null? null: this.parent.rigidbody;
        }
        private void FixedUpdate ()
        {
            this.joint.useSpring =! This.usePidControl;
            if (this.usePidControl)
            {
                // When using AddRelativeTorque
                var deltaAngle = this.targetAngle --this.joint.angle;
                this.integratedDeltaAngle + = deltaAngle;
                var deltaAngleVelocity = (deltaAngle --this.previousDeltaAngle)/Time.deltaTime;
                var control = Mathf.Deg2Rad * this.gain * (
                    deltaAngle +
                    (this.integratedDeltaAngle/this.integrationTime) +
                    (deltaAngleVelocity * this.derivativeTime));
                this.rigidbody.AddRelativeTorque (this.joint.axis * control);
                this.previousDeltaAngle = deltaAngle;
            }
            else else
            {
                // When using hinge springs
                this.joint.spring = new JointSpring
                {
                    spring = this.spring,
                    damper = this.damper,
                    targetPosition = this.targetAngle
                };
            }
        }
        #if UNITY_EDITOR
        private void OnDrawGizmos ()
        {
            if (this.joint == null)
            {
                return;
            }
            Handles.Label (this.transform.position, $"T: {this.targetAngle: F1} \ nA: {this.joint.angle: F1}");
        }
        #endif
    }

    HingeJoint.angle was used as the angle of each part. As the questioner said, if you do not have a parent-child relationshipTransformIt will take a lot of time and effort to calculate the relative angle from. This time, I built a parent-child relationship according to the questioner's method, but depending on the joint, you can get an angle with such an easy method, so even if you do not create a parent-child relationship, it is not so troublesome if you rely on them. I think so.

    The hinge joint also has a drive mechanism, so I first tried to control it using it.usePidControlThis is the behavior when is turned off.
    Select all four objects in the hierarchy and inspectortargetAngleWhen I operated all at once, it became as shown in the figure below.

    On the other hand, if the method of applying torque is as expected by the questioner, it will be necessary to adjust the magnitude of the torque by yourself.usePidControlWhen is turned on, it looks like the figure below.

    I tried to imitate the article of "PID control-Wikipedia", but as you can see, the followability is poor and the movement is pulling ...
    As I said before, robots and control engineering are ignorant, and I think it may be due to incorrect calculations, poor parameter adjustment, or physics simulation limitations ... time steps are too long. .. Well, it looks like a ponkotsu robot and is cute, so it may have interesting uses in terms of expression ...

    It can be said that the method of applying torque by yourself has a higher degree of freedom than the hinge drive mechanism built in Unity and there is room for adjustment. Perhaps the questioner is more familiar with this kind of control, so if done well, it may be possible to achieve both stability and target angle reachability.