Active Ragdoll "Jumping" problem

I’m working on a game where I’m trying to create an Active Ragdoll character similar to games like ‘Human Fall Flat’ and ‘Gang Beasts’.

Now I have reached the stage of animating the character. To do this, I wrote a script that rotates the character’s legs to walk forward. The script works, as it seems to me, well. But when walking, the character jumps. This is my main problem. How can this be fixed?

The 13 player parts have a Configurable Joint component and are interconnected. All scripts are hanging on the Player empty object, which is the parent of all player parts. Also, there is a RigidBody on this empty object (photo below), to which Hips is attached.

Video of what the problem looks like
Link (Sorry for lags)

The script that rotates the character’s legs

using UnityEngine;

public class RagdollLegsAnimator: MonoBehaviour
{
    #region AccessableFields
    [SerializeField] private GameObject[] _playerParts;
    [SerializeField] private ConfigurableJoint[] _jointParts;

    [Space(3)]
    [Header("IsWalking")]
    [SerializeField] private bool _walkForward;
    [SerializeField] private bool _walkBackward;

    [Space(3)]
    [Header("StepValues")]
    [SerializeField] private float _timeStep = 0.4f;
    [SerializeField] private float _legsHeight = 0.8f;

    [Space(3)]
    [Header("AnimationCurves")]
    [Header("Forward")]
    [SerializeField] private AnimationCurve _upperLegCurveForward;
    [SerializeField] private AnimationCurve _lowerLegCurveForward;
    #endregion

    #region PrivateFields
    private float _rightStepTime, _leftStepTime;
    private bool _stepRight, _stepLeft, _flagLegRight, _flagLegLeft;
    private Quaternion _startRightUpperLegRotation, _startRightLowerLegRotation, _startLeftUpperLegRotation, _startLeftLowerLegRotation;
    #endregion

    private void Awake()
    {
        _startRightUpperLegRotation = _jointParts[4].targetRotation;
        _startRightLowerLegRotation = _jointParts[5].targetRotation;
        _startLeftUpperLegRotation = _jointParts[7].targetRotation; 
        _startLeftLowerLegRotation = _jointParts[8].targetRotation;
    }

    private void FixedUpdate()
    {
        LegsMoving();
    }

    private void LegsMoving()
    {
        if (_walkForward)
        {
            if (_playerParts[6].transform.position.z < _playerParts[9].transform.position.z && !_stepLeft && !_flagLegRight)
            {
                _stepRight = true;
                _flagLegRight = true;
                _flagLegLeft = true;
            }
            if (_playerParts[6].transform.position.z > _playerParts[9].transform.position.z && !_stepRight && !_flagLegLeft)
            {
                _stepLeft = true;
                _flagLegLeft = true;
                _flagLegRight = true;
            }

            if (_stepRight)
            {
                _rightStepTime += Time.fixedDeltaTime;

                float t = Mathf.Clamp01(_rightStepTime / _timeStep);

                float upperLegRotationValue = _upperLegCurveForward.Evaluate(t) * -0.06f * _legsHeight;
                float lowerLegRotationValue = _lowerLegCurveForward.Evaluate(t) * 0.08f * _legsHeight * 2;

                _jointParts[4].targetRotation = new Quaternion(_jointParts[4].targetRotation.x + upperLegRotationValue, _jointParts[4].targetRotation.y, _jointParts[4].targetRotation.z, _jointParts[4].targetRotation.w);
                _jointParts[5].targetRotation = new Quaternion(_jointParts[5].targetRotation.x + lowerLegRotationValue, _jointParts[5].targetRotation.y, _jointParts[5].targetRotation.z, _jointParts[5].targetRotation.w);

                if (_rightStepTime > _timeStep)
                {
                    _rightStepTime = 0;
                    _stepRight = false;

                    _stepLeft = true;
                }
            }
            else
            {
                _jointParts[4].targetRotation = Quaternion.Lerp(_jointParts[4].targetRotation, new Quaternion(_startRightUpperLegRotation.x + 0.3f, _jointParts[4].targetRotation.y, _jointParts[4].targetRotation.z, _jointParts[4].targetRotation.w), 8 * Time.fixedDeltaTime);
                _jointParts[5].targetRotation = Quaternion.Lerp(_jointParts[5].targetRotation, _startRightLowerLegRotation, 17 * Time.fixedDeltaTime);
            }

            if (_stepLeft)
            {
                _leftStepTime += Time.fixedDeltaTime;

                float t = Mathf.Clamp01(_leftStepTime / _timeStep);

                float upperLegRotationValue = _upperLegCurveForward.Evaluate(t) * -0.06f * _legsHeight;
                float lowerLegRotationValue = _lowerLegCurveForward.Evaluate(t) * 0.08f * _legsHeight * 2;

                _jointParts[7].targetRotation = new Quaternion(_jointParts[7].targetRotation.x + upperLegRotationValue, _jointParts[7].targetRotation.y, _jointParts[7].targetRotation.z, _jointParts[7].targetRotation.w);
                _jointParts[8].targetRotation = new Quaternion(_jointParts[8].targetRotation.x + lowerLegRotationValue, _jointParts[8].targetRotation.y, _jointParts[8].targetRotation.z, _jointParts[8].targetRotation.w);

                _jointParts[4].targetRotation = new Quaternion(_jointParts[4].targetRotation.x - 0.02f * _legsHeight / 2, _jointParts[4].targetRotation.y, _jointParts[4].targetRotation.z, _jointParts[4].targetRotation.w);

                if (_leftStepTime > _timeStep)
                {
                    _leftStepTime = 0;
                    _stepLeft = false;

                    _stepRight = true;
                }
            }
            else
            {
                _jointParts[7].targetRotation = Quaternion.Lerp(_jointParts[7].targetRotation, new Quaternion(_startLeftUpperLegRotation.x + 0.3f, _jointParts[7].targetRotation.y, _jointParts[7].targetRotation.z, _jointParts[7].targetRotation.w), 8 * Time.fixedDeltaTime);
                _jointParts[8].targetRotation = Quaternion.Lerp(_jointParts[8].targetRotation, _startLeftLowerLegRotation, 17 * Time.fixedDeltaTime);
            }
        }
    }
}

The script for movement

using UnityEngine;
using UnityEngine.InputSystem;

namespace Player
{
    public class ActiveRagdollController : MonoBehaviour
    {
        #region AccessableFields 
        [Header("MovementValues")]
        [SerializeField] private float _speed;
        [SerializeField] private float _strafeSpeed;
        [SerializeField] private float _backwardsSpeed;
        [SerializeField] private float _sprintSpeed;

        [Header("Jump")]
        [SerializeField] private bool _isGrounded;
        [SerializeField] private LayerMask _ground;

        [SerializeField] private float _jumpForce;

        [Header("Torso")]
        [SerializeField] private Rigidbody _root;
        #endregion

        #region PrivateFields
        private Vector2 _move;
        private InputSystem _inputSystem;
        #endregion

        private void OnEnable()
        {
            _inputSystem = new InputSystem();
            _inputSystem.Player.Jump.performed += Jump;
            _inputSystem.Player.Enable();
        }

        private void Update()
        {
            _move = _inputSystem.Player.Movement.ReadValue<Vector2>();

            _isGrounded = Physics.Raycast(transform.position, Vector3.down, 0.1f, _ground);
        }

        private void FixedUpdate() => Move();

        private void Move()
        {
            Vector3 moveDirection = new Vector3(_move.x, 0, _move.y);
            moveDirection = Camera.main.transform.TransformDirection(moveDirection);
            moveDirection.y = 0; 

            float currentSpeed = _speed;

            if (_move.y < 0)
            {
                currentSpeed = _backwardsSpeed;
            }
            if (_move.x != 0)
            {
                currentSpeed = _strafeSpeed;
            }

            _root.AddForce(moveDirection.normalized * currentSpeed, ForceMode.Acceleration);
        }

        private void Jump(InputAction.CallbackContext _)
        {
            if (_isGrounded)
            {
                _isGrounded = false;
                _root.AddForce(Vector3.up * _jumpForce, ForceMode.Impulse);
            }
        }

        public bool IsGrounded { get => _isGrounded; }
        public Vector2 Movement { get => _move; }

        private void OnDisable()
        {
            _inputSystem.Player.Jump.performed -= Jump;
            _inputSystem.Player.Disable();
        }
    }
}

Player Hierarchy

The Rigidbody component on the Player Empty object

The script that rotates the character’s legs in the Inspector

The main settings for the Configurable Joint of all components

You cannot simultaneously animate a ragdoll character. Not easily anyway. Check the asset store, there are tools like PuppetMaster that do this for you.

The problem with the leg script is that it changes rotation directly but with a physics character you would actually have to use AddTorque to simulate the rotation, otherwise physics will behave incorrectly.

Either it’s 100% physics or 0% physics animated, you can’t do half-half. :wink:

If the joints have “enable collision” checked you will also get weird results due to self-collision.

You should visualize this ray with Gizmos.DrawRay. I have a hunch that this may be incorrect depending on where that transform.position is. If the MovementScript is on Player’s root then this root will not be physics animated because the active ragdoll starts at its own Root or Hips and moves independently from the Player position unless you synchronize it. Dynamic Rigidbody objects ignore the parent-child relationship and always behave as if they are at the root of the scene.

Why? I’ve seen a few projects from people who animate the character directly with physics. For example: https://youtu.be/Xj_uT7T-XxU?si=3-glvdPFid3Jk4Fq (Time code: 1:14). Also, all assets that allow you to animate a ragdoll are often paid and have a huge number of unnecessary functions for such a project.

About this, I have seen that objects with a Configurable Joint are rotated by changing the Target Rotation. But I tried to look for examples of creating object rotations with Configurable Joint using AddTorque, but I didn’t find anything. Could you tell me more about this thing?

No, I didn’t enable this feature.

In order for the player’s root to move with the physical body, I added a Rigidbody to it and tied Hips to it.

You can watch the video above, the leg rotation is much better implemented there, and there is also no such problem as I have. Maybe based on it you can suggest something.

Yeah, he’s ticking the IsKinematic checkbox off to activate the ragdoll. It does the same as Animator on/off. Either the character is kinematic (non-physics) and animates, or it’s a completely non-animated ragdoll besides the physics acting on the ragdoll bodies.

These paid assets provide everything you need for that. It’s non-trivial to implement an animated active ragdoll yourself.

That’s what I mean by non-trivial. You’d have to apply torque to rotate the body rather than setting its rotation, bypassing physics. But the torque needs to match whatever the animation would normally be doing.

That is likely going to mess with the ragdoll or the player position, depending on what is moving. It will also cause one or the other to drag behind, more so the faster the movement. It’s best to remove that physics connection and add a script which, in Update, simply sets the Player’s position to the same as the Hip bone while the character is in active ragdoll (non kinematic) mode.

Ensure forces and torques applied to the legs are balanced, and implement ground detection to keep the character grounded. Adjust damping, friction, and foot placement to prevent unintended jumps.