Replacing animations in AnimStateGraph during runtime

Hey hey, I have another question regarding the new AnimComponent.

We are currently building something like a character creation. Our characters there can have vastly different bodytypes (imagine different number of arms, sphere or cube as torso, etc.), therefore I want to use the same AnimStateGraph but obviously can’t use the same idle animation track.

I tried to change the AnimTrack in code. When settinging the new body type, I want to remove the old AnimTrack and assign the new AnimTrack, something like this.

this.entity.anim.baseLayer.removeNodeAnimations("idle");
this.entity.anim.baseLayer.assignAnimation("idle", newIdleAnimation);

First of, I’m not 100% sure manually removing the anim track is necessary? Regardless, this results in the character standing there in T pose. Also when transitioning to another state, the character stays like this. So I tried resetting the anim component.

this.entity.anim.reset();
this.entity.anim.baseLayer.play();

This doesn’t change anything though, the character stays in T pose and doesn’t do anything. So now I’m resetting the whole stateGraph.

this.entity.anim.resetStateGraph();

This again results in the character staying in T pose, but now when I start a transition, the new animation starts playing, and then going back to idle plays the correct animation. Calling this function though triggers the complete setup of the AnimComponent, which seems like overkill?

Can someone explain this to me please? I just want to replace animations and then continue with the same AnimStateGraph, ideally without completely resetting it to stay in the same state that I’m currently in. Am I approaching this wrong, or am I missing something?

Thanks

1 Like

@Elliott would be able to help.

First of, I’m not 100% sure manually removing the anim track is necessary? Regardless, this results in the character standing there in T pose. Also when transitioning to another state, the character stays like this. So I tried resetting the anim component.

You’re right in thinking you don’t need to remove the animations first as assigning an animation overwrites any previously set ones.

Can someone explain this to me please? I just want to replace animations and then continue with the same AnimStateGraph, ideally without completely resetting it to stay in the same state that I’m currently in. Am I approaching this wrong, or am I missing something?

The system was designed under the assumption animations would be assigned before a graph is played. Any animations for a given state currently get added to the animation evaluation system when the AnimStateGraph transitions to that state. If you assign a new animation to a state, it wont be picked up by the animation evaluation system until you transition back to that state. Calling reset on the anim component should result in the new animation being passed onto the evaluation system. This makes me think there’s something else going on here. As well as assigning a new animation, are you also swapping out the characters model?

As well as assigning a new animation, are you also swapping out the characters model?

Yes, we are swapping out the whole model. We made sure to assign the new model first before assigning the new animation, but does there need to be a small delay, to maybe make sure the new model is properly initialized?

I also found this post about Not assigning an animation breaks the new anim component just now. In there you said:

The anim component is indeed inactive if any of it’s states are unset.

Could this maybe relate to this issue? I’m making sure that all states actually have an animation linked, but maybe the component thinks some of them are empty?

I believe there’s some bind or rebind function you need to call when the hierarchy changes, so that the animation links up to the changed hierarchy.

The AnimStateGraph becoming inactive shouldn’t cause any problems. As Martin suggested, I think the anim components rebind function may be necessary in this case so your animations are targeted against the newly assigned model.

So, I found a solution… but it’s not pretty… this is it:

function replaceAnimations(animationAssets) {
  // This could be done differently, but it was the fastest way to get a deep copy of this object atm.
  // I don't want to trigger the setter of "this.entity.anim.animationAssets" until the very end.
  var nextAnimationAssets = JSON.parse(JSON.stringify(this.entity.anim.animationAssets));

  var idleAnimation = animationAssets.find(asset => asset.name === "idle");
  nextAnimationAssets["Base:idle"].asset = idleAnimation.id;

  var walkAnimation = animationAssets.find(asset => asset.name === "walk");
  nextAnimationAssets["Base:walk"].asset = walkAnimation.id;

  // After I replaced all animations, I want to trigger the setter behind "this.entity.anim.animationAssets"
  this.entity.anim.animationAssets = nextAnimationAssets;
  this.entity.anim.resetStateGraph();
}

If I do this, the animation of the state I’m currently in gets replaced, I stay in the same state and the character plays the new animation immediately. But this can’t be the correct solution, right?

I found out that calling this.entity.anim.assignAnimation(state, animation) does not set the correct animation on the anim component. If I assign a new animation and then look at this.entity.anim.animationAssets, it still holds the asset ids of the previous animations. Is this a bug?

By setting this.entity.anim.animationAssets directly, this.entity.anim.loadAnimationAssets() gets called automatically which is what I want. But I still need to call this.entity.anim.resetStateGraph() to setup the the stateGraph again, which will also call this.entity.anim.loadAnimationAssets(). So there is some redundancy in what I’m currently doing, but I didn’t find found how else to set the animation references correctly.

There must be a better solution, because this seems very hacky. Do you have an idea?

1 Like

@mvaligursky @Elliott Could you please help me here? What are the right steps I need to take/correct functions I need to call? I don’t think it’s correct to manually set the animation asset id’s and then reset the state graph.

I just wonder why do you need to swap animations on the component as it’s playing? Would not be easier to just load different template (prefab) which is set up for the other character? Considering you’re swapping visuals as well, there will be pop … I assume you’re not trying to smoothly continue playing idle animation where it left of on the previous character?

The way I would do it is to create a single AnimStateGraph asset … so that it can be used by all characters.

Then I would create per character template. I would start by adding the imported model (most likely would use the new templated glb import, so that I can see the hierarchy in Editor). I would add anim component to it, hook it up to that single anim state graph, and set up animations for this specific character on it.

So I’d have few of these templates, one per character. Then when I need to display new character, I’d destroy old one and instantiate new one.

Would this work for you?

3 Likes

Hmm, currently we are using one character entity and its children and simply replace all the assets inside their components. Until now it worked good, we were using the old animation component, which we loaded up with the appropriate animations for the respective body type.

Unfortunately we have quite a few other systems requiring the model, adding shaders to the bodies material, drawing decals on top of them, etc. So it’s gonna be a bit of work, making sure everything works if we replace the whole entity.

I see the upsides though of having templates for each body type and preparing it with the right animations. So I will definitely try it out. Thanks a lot

You could keep what you have … but simply remove existing anim component, and create a new instance of it.

So, we tried recreating the anim component kind of like this:

var animStateGraphAsset = this.entity.anim.stateGraphAsset;
this.entity.removeComponent('anim');
this.entity.addComponent('anim', {  stateGraphAsset: animStateGraphAsset });
this.entity.anim.assignAnimation('idle', this.idleAnimation);
this.entity.anim.assignAnimation('walk', this.walkAnimation);
this.entity.anim.rebind();
this.entity.anim.reset();

This though didn’t work 100% of the time, sometimes our character got squashed into a small ball. Switching from body 1 to 3 worked, while switching from body 2 to 3 caused the body to be squashed into a little ball, not sure why.

So again, I thought about resetting the state graph as a whole, with this.entity.anim.resetStateGraph(), since this worked before. But now, if I do this, the monster is not being animated at all anymore. The reason for this is that the animation asset references are not proberly set in this.entity.anim.animationAssets. If I create the animComponent in the editor, the animationAssetIds are set in this object. If I create the anim component from scratch in code, alle these asset ids are null, even after I assigned an animation to it.

Is this a bug? Shouldn’t this.entity.anim.assignAnimation() set the animationAssetId in this object? Or is this me misunderstanding how to use the anim component and its state graph?

Btw, the solutions we are settling with for now is the one I described a few posts further up, we are setting these asset references manually and then resetting the state graph. Doesn’t feel too good, but it works for now.

Hmm, I do something like this in the 3D Bitmoji Library here where I replace the model and re-add the animations: https://playcanvas.com/project/721733/overview/3d-bitmoji-library

Line 83: https://playcanvas.com/editor/code/721733?tabs=35752001&line=83

1 Like

Is this a bug? Shouldn’t this.entity.anim.assignAnimation() set the animationAssetId in this object? Or is this me misunderstanding how to use the anim component and its state graph?

The animationAssets object is private api as it’s not intended to be used in user scripts. There’s no support for the use of this object after the initial load of the component. In scripts the assigning of a stateGraph should be done by directly calling the loadStateGraph function too.

If you want to completely reinitialise the anim component, i’d suggest this:

this.entity.removeComponent('anim');
this.entity.addComponent('anim', { activate: true });
this.entity.anim.loadStateGraph(animStateGraphAsset);
this.entity.anim.assignAnimation('idle', this.idleAnimation);
this.entity.anim.assignAnimation('walk', this.walkAnimation);

The rebind and reset shouldn’t be necessary after creating a new anim component.

I can’t say this is the right approach for you though without seeing the architecture of your application. There seems to be some specific conditions created through the loading of multiple models & animation. My guess right now though is that Martin’s suggestion of templates would offer the best approach to solve this.

2 Likes