Skip to main content

Exploring Active Ragdoll Systems

 


Active ragdolls is the name given to wobbly, physics-based character controllers which apply forces to ragdolls. You may have seen them implemented in popular games such as Human Fall Flat and Fall Guys. This post introduces a technique I developed to create active ragdolls for a personal project, implemented in Unity.

The system I will demonstrate is surprisingly simple and only requires a small amount of code. Unity has these beautiful things called Configurable Joints, which are joints that can, as the name suggests, be configured, with simulated motors on the X and YZ axes providing force to the joints. What we can do with this is map the motions of a regular game character with an Animation Controller (an "animator clone") to our active ragdoll. Doing this means we only have to animate the animator clone for the active ragdoll to automatically be animated with it!

Firstly, I created a ragdoll from a rigged character. (Side note: Mixamo is a great tool to quickly rig and animate characters, and I can't recommend it enough.) Unity has a feature to automatically create ragdolls, but these don't have configurable joints and, therefore, can't be controlled by the animator clone. Instead, we add our configurable joints to a few of the bones in our character's armature. I also add a Rigidbody and a Collider to each of the bones in the ragdoll. So each bone should look something like this:


On each of the Configurable Joints, I set the X, Y, and Z motion to "Locked." This ensures that our joints act like joints, rotating without moving around.


Next, I add an angular X and YZ drive to apply forces to the joints to map the animator clone's animations to the ragdoll.


Then, I create my animator clone. I set this up like any other game character, with animations, a movement script, and the works. Here's the code for the movement script if you're the kind of person to read blog posts and copy everything line for line.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;

public class MovementScript : MonoBehaviour
{
    [SerializeField] private GroundChecker groundChecker;
    [SerializeField] private PlayerInput playerInput;
    [SerializeField] private Rigidbody ragdollRB;
    [SerializeField] float moveSpeed = 5f;
    [SerializeField] float turnSpeed = 5f;
    [SerializeField] float jumpForce = 5f;

    private Animator animator;
    private bool isJumping = false;

    private void Start() {
        animator = GetComponent<Animator>();
    }

    private void FixedUpdate() {
        Vector2 movementVector = playerInput.actions["Move"].ReadValue<Vector2>();
        animator.SetFloat("speed", movementVector.sqrMagnitude);
        transform.Translate(new Vector3(movementVector.x, 0f, movementVector.y) * moveSpeed * Time.deltaTime, Space.World);

        if (movementVector.magnitude > 0.1f) {
            Quaternion targetRotation = Quaternion.LookRotation(new Vector3(movementVector.x, 0f, movementVector.y));
            transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, turnSpeed * Time.deltaTime);
        }

        Debug.Log(groundChecker.IsGrounded);
        if (playerInput.actions["Jump"].ReadValue<float>() > 0 && !isJumping) {
            ragdollRB.AddForce(Vector3.up * jumpForce);
            isJumping = true;
        }
        
        if (isJumping && groundChecker.IsGrounded) {
            isJumping = false;
        }
    }
}

Now, the actual active ragdoll script can be created. I create a new empty game object and parent both the ragdoll and the animator clone to it. In the script, I create three arrays.

public ConfigurableJoint[] joints;
public Transform[] targets;
private Quaternion[] targetRotations;

You should then assign each of the active ragdoll's Configurable Joints to the script in the Inspector, with the corresponding bone on the animator clone for it to follow. That should look something like this:


In the Start() function, we add the following bit of code to initialize the original target rotations:

targetRotations = new Quaternion[targets.Length];
for (int i = 0; i < targets.Length; i++) {
    targetRotations[i] = targets[i].localRotation;
}

Then, in FixedUpdate(), we use Quaternion.Inverse to move each of the joints:

for (int i = 0; i < joints.Length; i++) {
    joints[i].targetRotation = Quaternion.Inverse(targets[i].localRotation) * targetRotations[i];
}

And that's it for the actual active ragdoll! Of course, there are still two pivotal issues here. Firstly, the ragdoll falls to the ground very, very easily. Also, the ragdoll isn't moving or rotating with our animator clone at all! The BalanceSelf script below, added to the ragdoll's root rigidbody, resolves both of these issues by applying forces and torques to keep the ragdoll balanced and moving with the animator clone.

using UnityEngine;
using UnityEditor;

public class BalanceSelf : MonoBehaviour
{
    [SerializeField] private float slerpSpeed = 5f;
    [SerializeField] private float movementForce = 30f;
    [SerializeField] private float restoringForce = 50f;
    [SerializeField] private Transform balanceTarget;
    private Rigidbody rb;

    void Start() {
        rb = GetComponent<Rigidbody>();
    }

    void FixedUpdate() {
        Vector3 torque = Vector3.Cross(transform.up, balanceTarget.rotation * Vector3.up);
        rb.AddTorque(torque * restoringForce);

        Quaternion targetRotation = balanceTarget.rotation;
        targetRotation.x = transform.rotation.x;
        targetRotation.z = transform.rotation.z;
        transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, slerpSpeed * Time.deltaTime);

        Vector3 targetPosition = balanceTarget.position;
        targetPosition.y = transform.position.y;
        Vector3 force = targetPosition - transform.position;
        rb.AddForce(force * movementForce);
    }

    void OnDrawGizmos() {
        if (balanceTarget != null) {
            Handles.color = Color.red;
            Handles.DrawLine(transform.position, balanceTarget.position);
        }
    }
}

And after assigning the animator clone's root. to the balanceTarget field in the Inspector, everything works flawlessly!

Thanks for reading!

Comments

Popular posts from this blog

Emotion Classification NN with Keras Transformers and TensorFlow

  In this post, I discuss an emotional classification model I created and trained for the Congressional App Challenge last month. It's trained on the Google GoEmotions dataset and can detect the emotional qualities of a text. First, create a training script and initialize the following variables. checkpoint = 'distilbert-base-uncased' #model to fine-tune weights_path = 'weights/' #where weights are saved batch_size = 16 num_epochs = 5 Next, import the dataset with the Hugging Face datasets library. dataset = ds . load_dataset( 'go_emotions' , 'simplified' ) Now, we can create train and test splits for our data. def generate_split(split): text = dataset[split] . to_pandas()[ 'text' ] . to_list() labels = [ int (a[ 0 ]) for a in dataset[split] . to_pandas()[ 'labels' ] . to_list()] return (text, labels) (x_text, x_labels) = generate_split( 'train' ) (y_text, y_labels) =...

Pure Pursuit Robot Navigation Following Interpolated Cubic Splines

I've been working to improve my school VEX team's autonomous code for my robotics class, and have created a pure pursuit robotics PID that I thought I would share. The code here is in Python, and I'm only using matplotlib as an output to visualize the robot's movement. However, I do hope to rewrite this code in C++ soon, getting a movement vector output which will then be applied to a VEX robot. First is the spline class. I'm currently using a simple parametric cubic spline class. Keep in mind that this is a really  bad way to implement splines, as it demands increasing x-values along the domain which isn't ideal for a robot's path. I am definitely going to rewrite all of this in the future to have a periodic domain, but I thought I would share what I have right now anyways because it might be usef A spline is defined as a piecewise function of polynomials, and in the case of a cubic spline, the polynomials of choice are cubic polynomials. Therefore, the fir...