Animation Root Motion Script

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;
});
2 Likes

This is very interesting! It would be fantastic to properly support animations that move a character away from the origin. As you say, many Mixamo animations have this baked in. Particularly the motion pack. Here’s a project where I have added your script. It kinda, sorta works. I’ve just locked the camera as a child and it swings with the hips (perhaps unsurprisingly). Another thing that happens is that a blend from walk to idle snaps the character backwards, depending on how far through the walk cycle the character has got. I want to add support for the other animations in the pack too. Want me to add you to the project?

Great start though. This needs to be an integrated feature in the engine. Good to see that you’re digging into the engine code by the way. I was saying to the team yesterday that we need to do more work in that area.

Yeah I had a few problems with blending because it’s hard to know where the key frame is.

I’ve made a few updates which I’ll upload that give more control. But what I think I’m going to have to do is load of analysis on the animations and at least get the character’s hip position in T Pose.

The real issue is the loop of the animation when the hips snap back. I’m guessing I need to flag animations as loops, calculate the hip position at t = 1 and t = 0 then knowing the length of the movement, project the next position. That and a combination of an understanding of the T pose might get there. Otherwise its time to write mecanim avatars in PlayCanvas :smile:

@whydoidoit @will hey, just jumping into this old discussion
did something change since 2015 with root motion in playcanvas?
what is the best way today to update my charachter position /rotation and so…?

I didnt read the entire script but I see that you are getting the delta of positions and than changing accordingly
we thought about adding a dummy cube “inside” the animation then following it, getting the deltas of movement and rotation localy and applying them (Is that what you did?)

any other insights about rootmotion and how to approach it?
thanks!

Hey I finally did get it working. I monkey patched Skeleton.addTime and read the deltas in position and rotation off the hip joint. Worked ok.

I’ve moved to doing the opposite now moving the character and having the animation correctly sync (helps in multiplayer games).

Your idea about adding a child to the hip joint would work too I think. My monkey patch also didn’t displace the hip (it cancelled that bone movements in favour of passing them on to the system).

are you talking about this project you made? http://playcanv.as/p/os6XCAzZ/
or do you maybe have more projects I can look at?

I dont think I fully understand how you can do it the “opposite” way… can you elaborate?

also: you wrote that strafe are harder to blend, but unfortunately I have strafe in my game quite a lot
any tips there?

I will post the current thing I’m working on shortly.

It’s just ensuring that the animations of strafe and walk are on the same time point. So that the leg doesn’t fight.

“The other way around” means moving the character by an amount and then working out where in the animation you should be and which animation you should use. This works well in multiplayer as you are using categorical positions and the animation system is just rendering something that makes this core truth “look right”.

2 Likes

cant get this script to work, could do with a new version :slight_smile:

1 Like