☑ Animation Layering

Is it possible to tell certain bones from a graph to play different animations?

Say you had a biped model, and you split the top half of the body and have those bones playing anim clip A, and the bottom half playing anim clip B. How possible is this?

From the documentation:
An animation is a sequence of keyframe arrays which map to the nodes of a skeletal hierarchy. It controls how the nodes of the hierarchy are transformed over time.

can i unparent specific graph nodes and reparent to an entity playing clip B?

thanks :eyeglasses:

I found this solution so if anyone else was looking for something similar this seems to do it.

Will post result when i can:

https://chinedufn.github.io/skeletal-animation-system/

Is this solution still working? How did you implement this system into PlayCanvas?

1 Like

hey, basically

i didnt use the above solution.

I ended up writing my own skeletal system / masking.

here is the code for your uses :slight_smile:

const SkeletonAnimator = pc.createScript('SkeletonAnimator');
SkeletonAnimator.attributes.add('skinnedMesh', {type: 'entity', default: 0, title: 'Skinned Mesh'});
let upperLayerAnims=[];
var updateAnim = false;
let upperSkeleton;
SkeletonAnimator.prototype.initialize = function(){
    //mixamorig:Spine
    //mixamorig:LeftUpLeg
    //mixamorig:RightUpLeg
    //Get graph for all nodes in skinned mesh
    this.boneGraph = this.entity.model.model.getGraph();
    //Get the aim idle animation from the skinned mesh animations
    //this.aim_animation = this.entity.animation.data.animations.idleAim;
    let idleHipAnim = { 'id':'idleHipAim', anim:this.entity.animation.data.animations.idleHipAim };
    let idleAimAnim = { 'id':'idleAim', anim:this.entity.animation.data.animations.idleAim };
    let firingAnim = {'id':'firingRifle', anim: this.entity.animation.data.animations.firingRifle };
    upperLayerAnims.push(idleHipAnim);
    upperLayerAnims.push(idleAimAnim);
    upperLayerAnims.push(firingAnim);
    this.frame = 0;
    //create a new skeleton and set it to the skinned mesh entity
    upperSkeleton = new pc.Skeleton(this.entity);
    //set its graph to the nodes from the spine and further down
    upperSkeleton.setGraph(this.boneGraph.findByName('mixamorig:Spine'));
    //set its new animation clip which will only contain relevant nodes to the spine and further down
    this.upperAnimationClip = new pc.Animation();
    //grab the nodes from the animation
    const aimSkeletonNodes = idleHipAnim.anim._nodes;
    //Filter the animation nodes into a new array to be added into the new animation clip array
    var relevantFilteredNodes = aimSkeletonNodes.filter(function(node){
        return node._name!="mixamorig:LeftLeg"&&node._name!="mixamorig:LeftFoot"&&
        node._name!="mixamorig:LeftToeBase"&&node._name!="mixamorig:LeftUpLeg"&&
        node._name!="mixamorig:RightUpLeg"&&node._name!="mixamorig:RightLeg"&&
        node._name!="mixamorig:RightFoot"&&node._name!="mixamorig:RightToeBase"&&
        node._name!="mixamorig:Hips"
    });
    //Loop through our filtered nodes and add the filtered animation nodes into the new animation clip
    for(let index in relevantFilteredNodes) {
        let node = relevantFilteredNodes[index];
        if(index!="binaryIndexOf"){
            this.upperAnimationClip.addNode(node);
        }
    }
    //name the newly created clip
    this.upperAnimationClip.name = idleHipAnim.id;
    //add its duration
    this.upperAnimationClip.duration = idleHipAnim.anim.duration;
    //add the newly created clip to the upper body skeleton
    upperSkeleton.animation = this.upperAnimationClip;
    updateAnim = true;

    this.app.on('changeUpperAnim', (anim) => {
        updateAnim = false;
        let matchedAnim = upperLayerAnims.find((anm)=>{
            return anm.id===anim.id;
        });
        let relevantFilteredNodes = matchedAnim.anim._nodes.filter(function(node){
            return node._name!="mixamorig:LeftLeg"&&node._name!="mixamorig:LeftFoot"&&
            node._name!="mixamorig:LeftToeBase"&&node._name!="mixamorig:LeftUpLeg"&&
            node._name!="mixamorig:RightUpLeg"&&node._name!="mixamorig:RightLeg"&&
            node._name!="mixamorig:RightFoot"&&node._name!="mixamorig:RightToeBase"&&
            node._name!="mixamorig:Hips"
        });
        let upperAnimClip = new pc.Animation();
        //Loop through our filtered nodes and add the filtered animation nodes into the new animation clip
        for(let index in relevantFilteredNodes) {
            let node = relevantFilteredNodes[index];
            if(index!="binaryIndexOf"){
                upperAnimClip.addNode(node);
            }
        }
        //name the newly created clip
        upperAnimClip.name = anim.id;
        //add its duration
        upperAnimClip.duration = matchedAnim.anim.duration;
        //add the newly created clip to the upper body skeleton
        upperSkeleton.animation = upperAnimClip;
        updateAnim = true;
    });
};

SkeletonAnimator.prototype.updateSkeleton = function(dt){
    if(updateAnim){
        if(this.frame>upperSkeleton._animation.duration){
            this.frame=0;
        }
        else{
            this.frame+=0.01;
        }
        upperSkeleton.addTime(this.frame*dt);
        upperSkeleton.updateGraph();
 }
};


3 Likes

Thank you so much! You cannot possible understand how thankful I am for this right now. You have literally rescued my project. :rofl:

Thanks for giving up your code, and I appreciate you taking the time to respond, my friend!

If you ever need help with anything, I’d be glad to repay the favor sometime :smiley:

It’s actually kind of funny because by the looks of it you have made a 3rd person shooter using (possibly) Mixamo animations, and that happens to be what I’m creating atm…

1 Like

No problem i did develop a third person shooter for the longest time but stopped after the physics system let me down quite badly (very slow and unresponsive because its not running in a web worker).

so now playcanvas or someone affiliated have created a warehouse walk example with fast collision system(not exactly physics but it is supposedly good / performant) and im going to utilise that.

This particular script i spent maybe half hour on, so its quite simple but more importantly it works. Surprised there isn’t anything like this built into Playcanvas, but then again it is still in its infancy, yet I still come back to build more on it because it at the very least has a decent editor and the fastest loading webgl solution out there(my opinion after many tests) :slight_smile:

7 years old and still an infant, yes :rofl:

I actually was also planning to do that. What a hilarious coincidence. We’re literally both going to have the exact same underlying systems in both of our projects… lol

PlayCanvas messed up real bad, when I pasted your code in, it automatically renamed every “Skeleton” to “Skevaron”… It was hilarious, to be honest :laughing:

So maybe i did an injustice by describing it like that i think there are some aspects you would expect that you have to just pull your boots up and build yourself.

Some other important features like physics, that will require a much more heavier implementation and really a dedicated couple of months to get down. My point was always that I did not want to build a framework but would rather spend all my time building the game itself.

Update:
animator with bone masking and layers:

1 Like

Right, so when you have to spend half your time creating tools rather than shaping your game, you lose sort of an edge, right?

Now my issue became that I needed to make a tool that I had no idea how to create, so I just started asking around to see if anyone else experienced in advanced javascript/backend javascript to help me out…

But yeah, it’s still been a great time building up my game in other aspects, it’s just this issue has always been in the back of my mind, telling me, you can’t actually finish this game without this, ya know…