Hey, I needed to do some root motion animation and thought I’d drop my script for it in here. Will update if I find anything wrong with it later.
###Root Motion
For those who’ve never used it, root motion is where an animation has baked in movement, a lot of the mixamo animations have this as Unity Mecanim works based off it.
In PlayCanvas you either don’t want this movement because you are working it out yourself and you want the character to animate in place or you want to use it like Unity does and keep your characters feet perfectly placed on the ground and position to be controlled by the animation. This script will do both of those things.
####Script
Attach this to the characters you want to have root motion and select whether you want to move by XYZ and the rotation.
pc.script.attribute('xmotion', 'boolean', false);
pc.script.attribute('ymotion', 'boolean', false);
pc.script.attribute('zmotion', 'boolean', true);
pc.script.attribute('rotation', 'boolean', true);
pc.script.create('rootmotion', function (app) {
function diffQuat(q1, q2) {
var a = q1.clone().invert();
return a.clone().mul(q2);//
}
pc.Skeleton.prototype.updateGraph = function () {
if (this.graph) {
var time = this.getCurrentTime();
var rootMotion;
for (var i = 0; i < this._interpolatedKeys.length; i++) {
var interpKey = this._interpolatedKeys[i];
if (i == 0) {
var obj = interpKey.getTarget().getParent();
if (obj.script && obj.script.rootmotion) {
rootMotion = obj.script.rootmotion;
}
var transform = interpKey.getTarget();
transform.localRotation.copy(interpKey._quat);
}
if (interpKey._written) {
var transform = interpKey.getTarget();
var pos = interpKey._pos;
if (i >= 2) {
transform.localPosition.copy(pos);
transform.localRotation.copy(interpKey._quat);
}
else if (rootMotion) {
if (rootMotion.rotation) {
if (time == 0) {
rootMotion.lastRotation = interpKey._quat.clone();
}
rootMotion.lastRotation = rootMotion.lastRotation || interpKey._quat.clone();
var diffA = diffQuat(rootMotion.lastRotation, interpKey._quat).getEulerAngles();
var existing = interpKey._quat.getEulerAngles();
rootMotion.setRotation(new pc.Quat().setFromEulerAngles(0, diffA.y,0));
transform.localRotation.setFromEulerAngles(existing.x, 0, existing.z);
}
if (time == 0) {
rootMotion.lastPosition = pos.clone();
}
rootMotion.time = time;
rootMotion.lastPosition =
rootMotion.lastPosition || transform.localPosition;
var diff = pos.clone().sub(rootMotion.lastPosition);
if (!rootMotion.zmotion) {
diff.z = 0;
}
if (!rootMotion.ymotion || rootMotion.rotation) {
diff.y = 0;
}
if (!rootMotion.xmotion || rootMotion.rotation) {
diff.x = 0;
}
rootMotion.setMotion(diff);
}
transform.localScale.copy(interpKey._scale);
transform.dirtyLocal = true;
interpKey._written = false;
}
}
}
};
// Creates a new Rootmotion instance
var Rootmotion = function (entity) {
this.entity = entity;
this.rootMotion = new pc.Vec3();
this.rootRotation = new pc.Quat();
this.time = 0;
};
Rootmotion.prototype = {
// Called once after all resources are loaded and before the first update
initialize: function () {
this.scale = this.entity.getLocalScale().x;
},
// Called every frame, dt is time in seconds since last update
update: function (dt) {
this.entity.translateLocal(this.rootMotion.clone().scale(this.scale));
this.entity.setLocalRotation(this.rootRotation.clone().mul(this.entity.getLocalRotation().clone()));
},
setMotion: function (delta) {
this.rootMotion = delta;
this.lastPosition.add(delta);
},
setRotation: function (delta) {
this.rootRotation = delta.clone();
this.lastRotation = delta.clone().mul(this.lastRotation);
}
};
return Rootmotion;
});