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