[SOLVED] Animation layering

For the characters in our game we have an animation that controls variations in size by altering the scale on different bones. In Unity we could set this up to play on a second animation layer. The first layer contains all the usual walk/idle/etc animations that blend between each other, and then the second layer has this scale animation. How could we achieve something similar in PlayCanvas? are there any examples we could look at? I’ve seen a mention of using multiple skeletons to achieve this, but I’m not sure how to make that work with the existing model and animation components.

thanks,
Chris

Hey, so what I do is this:

Create a pc.Skeleton for each of my animations. Use skeleton.currentTime or skeleton.addTime to update the animation. Use skeleton.blend(animSkeleton1, animSkeleton2, blendAlpha) to blend them in an order I’ve decided upon. Then update skeleton.updateGraph() to apply the final one to the model.

Actually I do tons of stuff to simulate what I needed from Unity, but that’s the basis of it.

This existing animation component is very basic, but pc.Skeleton is powerful if you don’t mind getting into the details of doing it yourself.

I think that might be fine if I could control whether an animation updates position, rotation and scale. Currently it updates all 3 indiscriminately, but what I want is an animation that only updates scale and doesn’t touch anything else. This will then be layered in with the other normal animations that update position and rotation.

I’m already doing a bunch to replicate the unity animation controller logic, multiple clips from a single file, etc, so I definitely don’t mind getting into the details. I would rather avoid having to customize the engine though, but I’m not sure I can get the effect that I want without it.

Yeah got you - so in that case you want to monkey patch pc.Skeleton.prototype.blend and put in your own code for whether to update rotations/scales etc. My version never touches scale or position for performance reasons etc.

Or monkey patch the individual skeleton to one of your own blends

This is my blend which gets used for not doing everything:

function blend(target, skeleton1, skeleton2, alpha, cb) {
    cb = cb || returnTrue
    let targetKeys = target._interpolatedKeys
    let numNodes = targetKeys.length
    let dstKey
    let skeleton1Keys = skeleton1._interpolatedKeys
    let skeleton2Keys = skeleton2._interpolatedKeys
    for (let i = 0; i < numNodes; i++) {
        var key1 = skeleton1Keys[i]
        var key2 = skeleton2Keys[i]
        if (key1._targetNode && key1._written) {
            if (key2._written && key2._targetNode && cb(true, true)) {
                dstKey = targetKeys[i]
                dstKey._quat.slerp(key1._quat, key2._quat, alpha);
                dstKey._written = true
            } else if(cb(true, false)) {
                dstKey = targetKeys[i]
                dstKey._quat.copy(key1._quat);
                dstKey._written = true
            }
        } else if (key2._written && key2._targetNode && cb(false, true)) {
            dstKey = targetKeys[i]
            dstKey._quat.copy(key2._quat);
            dstKey._written = true
        }
    }
}

Rotation Only BLend

2 Likes

Thanks! The performance thing is a good point. I was a bit worried about adding extra overhead but if my other anims aren’t updating scale that will help offset it.

I use a method of masking out nodes using globs - which is what my check for _targetNode is doing. Then there’s a callback which I sometimes provide to ensure that certain aninmations update reference animations properly. Like I said, eventually it got quite involved, but it’s working really well and is doing what I need for network driven animation (I move things rather than using root motion and then apply the animations to make the movement animate correctly with a foot locked to the floor)

Don’t just copy mine BTW, mine requires each skeleton to be fully bound to the model for my masking, in normal Skeletons targetNode is only set on things that will actually write to the model. I set them all and then walk a tree at initialize to remove the targetNodes that I don’t want to animate.

You could easily have a version for “only scale” and another for “only rotate” etc.

That’s what I’m thinking, a rotate+position version for almost all the anims in the game and a scale-only version just for the growth animation, then blend that in to the characters main skeleton. I’ll let you know how that works out! thanks!

So yeah that seems to be working like a charm. I monkey-patched in a custom version of updateGraph and addTime for my normal animations that doesn’t touch scale at all. Then I have another version of those functions that only touches scale. Then I have a script on the character that simply does this:

AgeController.prototype.initialize = function() {
    var oldAnim =  this.entity.animation.skeleton.animation;
    var skel = this.entity.animation.skeleton;
    skel.animation = this.growthAnim.resource;
    skel.addTimeGrowth(0.0);    //call custom addTime function that only touches scale
    skel.updateGraphGrowth();  //call custom updateGraph function that only touches scale
    skel.animation = oldAnim;
};

Hooray for monkey-patching! and thanks again!

Great!! Yeah monkey patching is one of those things that can really help when you are working with real Javascript lol.

I’m struggling to blend skeletons correctly. Are there any example projects where I can see this implemented? I found the documentation to be a bit scarce.