Generating Rig at runtime

Hi,

I want to generate a Rig at runtime once on the Awake function but it does not work. I’m using unity 2019.3.13f1.

public class TestRigBuilder : MonoBehaviour
{
    public GameObject SourceObject;
    public GameObject ConstrainedObject;

    RigBuilder rigbuilder;

    private void Start() {
        // Add the rig
        Rig rig = (Rig)transform.AddChild<Rig>();

        // Add constraint to rig
        MultiAimConstraint constraint = rig.gameObject.AddComponent<MultiAimConstraint>();
        constraint.data.constrainedObject = ConstrainedObject.transform;
        constraint.data.aimAxis = MultiAimConstraintData.Axis.Y;
        WeightedTransformArray sources = new WeightedTransformArray(0);
        sources.Add(new WeightedTransform(SourceObject.transform, 1f));
        constraint.data.sourceObjects = sources;

        // Add rig to rigbuilder
        rigbuilder = GetComponent<RigBuilder>();
        rigbuilder.layers.Clear();
        rigbuilder.layers.Add(new RigBuilder.RigLayer(rig, true));

        // build rig graph (Does not seems to be doing anything)
        rigbuilder.Build();
    }
}

/// <summary>
/// Add a child gameobject with the specified component. Returns the added component.
/// </summary>
/// <param name="transform"></param>
/// <returns></returns>
public static Component AddChild<T>(this Transform transform) where T : Component {
    GameObject gameObject = new GameObject(typeof(T).Name);
    gameObject.transform.SetParent(transform);
    return gameObject.AddComponent<T>();
}

Alright, after more testings, it seems like some constraint can be generated at runtime and some can’t…

The MultiAimConstraint can’t but the BlendConstraint works fine.

Currently trying to figure out the difference between the two on how they are handling specifically the sources transform reference because I have a feeling that’s the problem.

Here’s the code to my second simplified test with BlendConstraint that works:

using UnityEngine;
using UnityEngine.Animations.Rigging;

public class TestRigBuilderParent : MonoBehaviour
{
    public GameObject SourceObjectA;
    public GameObject SourceObjectB;
    public GameObject ConstrainedObject;

    RigBuilder rigbuilder;

    private void Start() {

        // create rig gameobject
        GameObject rigGameobject = new GameObject("Rig");
        rigGameobject.transform.SetParent(transform, true);

        // Add constraint to rig
        BlendConstraint constraint = rigGameobject.AddComponent<BlendConstraint>();
        constraint.data.constrainedObject = ConstrainedObject.transform;
        constraint.data.sourceObjectA = SourceObjectA.transform;
        constraint.data.sourceObjectB = SourceObjectB.transform;
        constraint.data.blendPosition = true;

        // Add the rig
        Rig rig = rigGameobject.AddComponent<Rig>();

        // Add rig to rigbuilder
        rigbuilder = gameObject.AddComponent<RigBuilder>();
        rigbuilder.layers.Clear();
        rigbuilder.layers.Add(new RigBuilder.RigLayer(rig, true));

        // build rig graph (Does not seems to be doing anything)
        rigbuilder.Build();
    }
}

The same test with MultiAim Constraint which does NOT work:

using UnityEngine;
using UnityEngine.Animations.Rigging;

public class TestRigBuilderMultiAim : MonoBehaviour
{
    public GameObject SourceObject;
    public GameObject ConstrainedObject;

    RigBuilder rigbuilder;

    private void Start() {

        // create rig gameobject
        GameObject rigGameobject = new GameObject("Rig");
        rigGameobject.transform.SetParent(transform, true);

        // Add constraint to rig
        MultiAimConstraint constraint = rigGameobject.AddComponent<MultiAimConstraint>();
        constraint.data.constrainedObject = ConstrainedObject.transform;
        WeightedTransformArray sources = new WeightedTransformArray();
        sources.Add(new WeightedTransform(SourceObject.transform, 1f));
        constraint.data.sourceObjects = sources;

        // Add the rig
        Rig rig = rigGameobject.AddComponent<Rig>();

        // Add rig to rigbuilder
        rigbuilder = gameObject.AddComponent<RigBuilder>();
        rigbuilder.layers.Clear();
        rigbuilder.layers.Add(new RigBuilder.RigLayer(rig, true));

        // build rig graph (Does not seems to be doing anything)
        rigbuilder.Build();
    }
}
2 Likes

Hi,

There is nothing indicating that BlendConstraint should behave differently from MultiAimConstraint. I can only guess that in the case you’re trying out here, the Blend constraint transforms are already animated in your animator hierarchy, and thus, rebuilding the constraints does not add additional bindings that the animator should know about.

And I think that’s the main issue you’re having, you can very well rebuild the PlayableGraph, but you need to ensure that your bindings are updated in the Animator as well.

Can you try the following?

calling animator.Rebind() like so:

rigBuilder.Build();
animator.Rebind();

Or make sure the animator is disabled before you rebuild your rigBuilder graph:

    private void Start() {
          animator.enabled = false;
          // do stuff
          rigBuilder.Build();
          animator.enabled = true;
    }

Also, be aware that rebuilding the constraints and rebinding the animator at runtime is costly. It’s better having a disabled constraint in your rig that you enable at runtime, than changing the rig at runtime to add that constraint.

1 Like

Hi Simon,

I just tried both techniques and it didn’t work. I agree with you that there shouldn’t be anything different between the MultiAimConstraint and the BlendConstraint but it seems there is one unless I’m missing something. The way the scene is built is the same for both tests I did.

You can find the extremely simplified project with a Scene and the two scripts that generate the Rig/Constraint on Start. One script is for the MultiAim and then the other one is for the BlendConstraint. If you opent the scene and hit Play, you’ll see the BlendConstraint works but not the MultiAim Constraint.

Here’s the WeTransfer link to the project:

Thank you yes I’m sure it’s not efficient at all but I only want to this operation only once on Start in order for the constraint to work.

Thank you

Daniel

Ah right, I see what it is.

6114701--665939--Screen Shot 2020-07-21 at 4.07.59 PM.png

When you add a component through the inspector, it also calls the Reset function of the MonoBehaviour which set the default values to whatever defaults the script has set. However this is an editor only thing.

You can make your runtime code work by changing it like so:

MultiAimConstraint constraint = rigGameobject.AddComponent<MultiAimConstraint>();
constraint.Reset();

The BlendConstraint worked well in this case because it has fewer parameters, and thus zero-ed out values did not really matter. Although, you would probably want to also call Reset on this constraint.

Animation Rigging constraints make heavy use of C# generics to streamline the various steps of a constraint implementation, but it also means that we cannot set the default values at instantiation like we could in a simple MonoBehaviour.

Hope this helps :slight_smile:

3 Likes

Ahh yes! That’s what I was looking for! Can’t believe I didn’t see that settings were all wrong before… I guess I was expecting it to behave like a regular MonoBehaviour.

Thank you for all your help! I have seen other people asking the same question so I’m sure this thread will really help them.

Thank you again Simon! Your a life saver!

1 Like

Would you guys (or someone else) be willing to throw together some documentation on creating runtime rigs with this? – (i.e. a step-by-step process, for example?)
Sometimes it is not clear the underlying reasons for most of the choices you guys make in your implementations / examples, so this would help plenty of people out to understand the underlying approach a lot better.

I can imagine plenty of people wanting to know how to create runtime rigs in a more straightforward way, myself included, had I not come across this post by accident. :frowning:

3 Likes

I’m looking for the same thing! @danUnity_1 , if you wouldn’t mind a quick explanation that goes over the steps, I’m sure we would all really appreciate it!

I think I got the process pretty straight. I would like to do a youtube tutorial on that but not sure how soon I will get to it done.

Is there any way to create dynamic IK at runtime

I cannot tell you how much this helped me out. I wish the documentation was a bit more involved cause I spent hours banging my head against a wall trying to figure out why the constraints weren’t working at runtime. It was a simple RigBuilder.Build(); call that saved it all. Thanks!

Hi there! When I remove a constraint at runtime and rebuild, my console gets flooded with the following warning messages:

Is there a better practice for removing constraints?

Ah just fixed it by calling AnimatorJobExtensions.UnbindAllStreamHandles(animator); before building and rebinding!

2 Likes

jesus christ, and how casual user should know this??
read the animation manual(animation rigging) and it has zero information about this stuff even the API documentation
great examples of how to use it in scene with already defined character and rig, etc, but basically its for simple prototyping situation, nothing for dynamically loading the character and its items
would be nice to have how-to
reload rig in a proper way
how to add object in character hands and reload the rig properly.(rebuild rebind animator disable enable doesnt work, at least in 2021.3lts didnt work, some handle error…, gonna try with 2022.3lts)

I find the practice of configuring coroutines to synchronize with frame updates somewhat unconventional, but when it works it works.

 public void StartRigWeightChangeAfterAnimation(Rig rig, float newWeight)
    {
        //apparently changing weights during animation transitions is illegal
        //so this function is required

        if (!rig)
        {
            Debug.LogError("no rig found!");
            return;
        }
       
        StartCoroutine(ChangeRigWeightsDuringTransition(rig, newWeight));
    }

    private IEnumerator ChangeRigWeightsDuringTransition(Rig rig, float newWeight)
    {
        // Check if the animator is in transition
        while (_animator.IsInTransition(0))
        {
            // Wait until the transition is finished
            yield return null;
        }

        yield return new WaitForEndOfFrame();

        // Set the weights after the transition
        // Example: Setting the weight of a rig layer to newWeight

        rig.weight = newWeight;
    }