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?
I ended up writing my own skeletal system / masking.
here is the code for your uses
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();
}
};
Thank you so much! You cannot possible understand how thankful I am for this right now. You have literally rescued my project.
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
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…
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)
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
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.
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…