Animation Layering Issues

Hey. It’s me again. Asking the same questions :stuck_out_tongue:

So I’m now utilizing a script that @yqu has provided in his post: ☑ Animation Layering

I’ve altered it to correctly suit my needs for my model, but while there are no errors thrown, the upper body animations do not play…

Here’s the altered script:

const DSkeletonAnimator = pc.createScript('DSkeletonAnimator');

DSkeletonAnimator.attributes.add('skinnedMesh', {type: 'entity', default: 0, title: 'Skinned Mesh'});

let upperLayerAnims=[];
var updateAnim = false;
let upperSkeleton;

DSkeletonAnimator.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.getAnimation("Idle Rifle") };
    let idleAimAnim = { 'id':'idleAim', anim:this.entity.animation.getAnimation("Idle Aiming Rifle") };
    let firingAnim = {'id':'firingRifle', anim: this.entity.animation.getAnimation("Shoot Rifle") };
    
    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;
    });
};

DSkeletonAnimator.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();
 }
};

No matter what I try, I can’t seem to get this script to work, this is my 4th iteration of it… The animations on the upper body simply will not play. Other than the upper body not separating from the lower body, animation of the model as a whole stays the same, and play, as usual, making this even more confusing. Can anyone identify what my issue is here?

Thanks so much guys!

since we last spoke i updated the script because it was very rough and really just didnt work and the new script supports multiple layers! all you need to do is apply it to a model.

The way it currently works is like the following:

Your base animated layer(i.e lower body) should still be controlled via the normal animation component and any animations you want to do on top with masked animation clips and avatar masked skeletons using my animator control script. but you can also just use this script in its entirety…

At the start of the script is a string array you can use to target the bones you want to mask out so the animated skeletons in the layers only affect the bones you want, this is important so its cleaner but you need to find out your bone names via this

var bones = { };
var skins = skinMesh.model.model.skinInstances;

for(var s = 0; s < skins.length; s++) {
    for(var b = 0; b < skins[s].bones.length; b++) {
        bones[skins[s].bones[b].name] = skins[s].bones[b];
    }
}

if you use this.app.fire(‘animation id’) from anywhere in teh app it should allow you to play the animation.

alternatively just set updateAnim to true when creating the skeleton and it will always play.

1 Like

Ok thanks for clarifying this, my friend. I’m working to implement this into my next iterational scene!

@yqu these lines:

    let idle = { 'id':'idle', anim:this.skinnedMesh.animation.data.animations.swordIdle };
    let run = { 'id':'run', anim:this.skinnedMesh.animation.data.animations.swordRun };
    let slash = { 'id':'slash', anim:this.skinnedMesh.animation.data.animations.swordSlash };

Do my animations have to be named these? What if I wanted to access animations with spaces in teh names?

you should rename your animations or retrieve them a different way(maybe loop), i had the same problem and decided it was best to just change them

Alright, I’ll be back, I have like 70+ animations to rename… :rofl:

After renaming them all I found that you could just use underscores… R.I.P.

It doesn’t matter now but for future reference :stuck_out_tongue: