Wondering if this sort of thing is possible with Spine:

  • Animate a character such that the Root bone defines translation that I wish to apply to the character in-game.
  • Specify at runtime to 'extract root motion'

i.e. play the animation without the root bone's translation/rotation applied to the animation, save those values somewhere I can access them.

  • My game code instead reads the translation/rotation off the root bone and applies it to the character.

Would do this sort of thing in 3D way back in my previous life as a console game programmer 🙂 It's quite clearly good for implementing something like a walk cycle with a cadence perfectly matched to the animation.

(I'm currently animating insects, and their walk cycles can be really funny, so simply applying linear velocity doesn't feel right)

Related Discussions
...

At runtime you can access the animation, which has a list of timelines which each have keys. Find the timelines for the root bone, copy the translation keys, and remove those timelines from the animation.

2 months later

How do you remove a timeline?

Trying to figure this out in Unity, but it's a bit of a merry-go-round...

Remove a timeline? Like from the json? or the deserialized animation data?

or the Spine project?

Sorry, that wasn't the most well constructed question.

From the deserialized animation data - a script on a gameobject.

I sat down with it again with a fresh coffee this morning and worked my way through the Timeline class, figured out that there are RotateTimeline and TranslateTimeline classes extended from CurveTimeLine, which itself is derived from Timeline - none of these are mentioned in the docs or class diagram, so it never occurred to me to look further than CurveTimeLine (and I only came across that because it was required in the Timeline.Remove() method).

While I can script fine in C#, some of the deeper nuances of OO coding are still surfacing so I have to jog in wide circles a lot - Unity's probably to blame for some of that, making life too easy / hiding away a lot of things we really should learn, but end up skipping. Doesn't help much that there are namespace conflicts, and Monodevelop seems happy to suggest things that appear to belong to other classes/types...

So I've got these now anyway, the question is how to remove them gracefully? MonoDevelop suggests Timeline.Remove(CurveTimeline) but I'm getting different results with different approaches and as a result I'm having trouble interpreting the errors (and can't find the declaration for the Timeline.Remove() method).

If I step through all the timelines, I get an error trying to Remove() the first TranslateTimeline. Yet I get no error if I specify the timeline manually (in the code after the foreach).

Even so, in the second case, Remove() hasn't done what I expected - it appears to have just removed the first keyframe of that timeline - my character is still walking, but his Y position leaves off from wherever the previous animation was at.

string anim = "walk-root"; // animation with root motion
int p = 0;

// This throws an InvalidOperatorException Removing timeline[1]*
// "Collection was modified; enumeration operation may not execute."
//
foreach ( CurveTimeline c in skeletonData.FindAnimation(anim).timelines)
{
	// reports alternating Rotate/TranslateTimelines
	Debug.Log (p + ": " + c.GetType() ); 
			
if (c.GetType() == typeof(Spine.TranslateTimeline))
{
	Debug.Log ("Removing curve "+p);
	skeletonData.FindAnimation(anim).Timelines.Remove(c); // *exception here, p=1
}
p++;
}
		
// This returns Spline.TranslateTimeline
//
Debug.Log ( skeletonData.FindAnimation(anim).timelines[1].GetType() );
		
// This needs to be cast explicitly otherwise it returns Spline.Timeline
//
TranslateTimeline timeline = skeletonData.FindAnimation(anim).timelines[1] as TranslateTimeline;
		
// And this seems to remove only the first keyframe - the root is still moving.
//
skeletonData.FindAnimation(anim).Timelines.Remove(timeline);

[/size]

No doubt I'm making a schoolboy error...?

edit: fixed an oops in my code, same results though.

Caffeine is working, and I'm having a face-palm moment for trying to call Remove on a Generic List.

Don't understand why that didn't throw an error in the second Remove(), and I'm still stuck :/

Ok so I got around this by just grabbing a copy of the timeline, blanking it's keys, and copying the new timeline back into the animation.timeline

// c is animation.timelines[p]  (the root's TranslateTimeline)
// Discovered the Root timelines are at the *end* of the List, not the start.
// so p = 34 in my case

TranslateTimeline rootTimeline = c as TranslateTimeline;
int fc = rootTimeline.FrameCount;

for (int i = 0 ; i < fc ; i++ )
{
	rootTimeline.SetFrame(i,0,0,0);
}
skeletonData.FindAnimation(anim).timelines[p] = rootTimeline;

That all works, except our guy's 0,0 root position isn't being respected when playing his root animation after another animation - even if I set the keys to non-zero, i.e. SetFrame(i,0,0,1) he still seems to inherit the Y position of the previous bone in the chain (hip in this case). So for example, if I jump then AddAnimation to the modified animation, his feet are below the ground level.

I've just got to figure out why this is happening (blending, timer, update issue?) , then work out how to read the offsets back from a cached timeline during playback.

Got it. Rebuilding the pivot timelines, using the same code above, for all of the animations fixes the blend/inheritance problem. No idea why, gift horse mouths and all that, but it's all groovy now.

Also discovered that having Event timelines (and I assume other types i.e. Color) mean you can't just grab the last Timeline to get the pivot, easy to get around once I'd stopped scratching my head.

Problems, problems.

Pulling movement data from the timelines is impractical without cloning them, and there's no easy way to get it to work with mixed animations.

I tried a couple of quick and dirty workarounds - first transposing the root bone's position with transform.position (of the gameobject)... this worked, sort of, as it would sometimes flicker, and worse had some weird artifact where it would play the last frame of the animation in it's "true" position


is Spline forcing the render outside normal Update loop?

Also, this didn't work so well with animation mixes, which are of course internal to Spine.

The second workaround was to add a rootMotion bone to my character in Spine Editor ... parent the hip to this, animate, then re-parent the hip to the root. In Unity I simply read the position of the bone and += that to the GameObject's transform.position. This actually works well - and presents a strong argument for implementing root motion support in the Editor (an ever-present bone that you can toggle Edit Root Motion On/Off).

However in playback there's a problem, it pauses (or eases) between loops. Which suggests again that Spine is not playing nice with regular Render/Update timing.

Curious, I set up a gameobject to follow the head bone, and sure enough the bone actually lags behind the head as you'd expect from a smooth Lerp.

Can anyone shed any light on this?

Sorry, I'm a bit confused about why you're doing this (removing the root bone movement from the Skeleton and using it on the Unity Transform).

As for the lag, it might be the case that your script that makes the thing follow the head bone is running before the SkeletonAnimation script updates so it followed the Bone's state from the last frame. You may have to force a specific script execution order between these two MonoBehaviours.

The Spine-Unity runtime doesn't make any render calls or mess with rendering. It just updates the Mesh attached to the GameObject. (And uses two meshes, because having double-buffered meshes seems to be good for mobile)

(BTW, to everyone else, it's Spine and not Spline. just fyi.)

Pharan wrote

Sorry, I'm a bit confused about why you're doing this (removing the root bone movement from the Skeleton and using it on the Unity Transform).

So I can move the gameobject to follow the skeleton's footstep cadence; think fake IK foot planting.

I've set up script execution orders, no difference.

(BTW, to everyone else, it's Spine and not Spline. just fyi.)

I feel like I'm back at school.

Edit: Correction, yep sorry the script exe order fixed the gameobject following bone issue. Hasn't changed the odd data I'm getting from my rootmotion bone - on closer inspection (slowing everything down to 5%) it appears to be ignoring the first half of the bone's x data; animates in-place for 15 frames, then moves forward 15 frames, repeat.

That is kinda weird. I was going to suggest trying to use WorldX instead of X, but I guess for the root bone, that'd be the same thing. (and if it were a child bone and you already removed the timeline, WorldX would never get calculated). Or is the rootmotion bone different from the root bone?

My first instinct is to check if there's good data in the json. If the root bone moved and a separate rootmotion bone was animated too (and that rootmotion bone is inevitably a child of the rootbone) then you would get weird data.

I still don't get the use though. hehe.
I know what IK is but this transfer of animation from bone to Unity Transform doesn't make sense to me. But whatever, no need to explain. I'll just look it up. I suppose it's something common.

Got it! 🙂

The problem was in my code; I was rather lazily/foolishly writing all my test functions in the same script, and left an earlier coroutine running every time I changed animation, which was overriding a flag I use to process the rootmotion function. Can't believe I didn't see it - I must be getting old. I hang my head in shame.

End result is just a couple of maths calcs, and works really well - the mixing seems fine so far too. I may post a package when I've turned it into a proper controller and put it through it's paces, but first I need to create some big-ass mechs.

Thanks for the sanity checks 🙂

4 months later

Hi AdamT!

I'm also at the point where I need a way to use motion root. 🙂 After reading this post I was intrigued how it goes with this package you proposed to post? It would be of really great help for me and my team. Thx! 🙂

FWIW, you can use offset with ghosting for a walk cycle (or jump, sort of).

I kinda get the use of root motion now.

Consider a character-ful walk where the displacement has explicitly timed pauses and easing. Like a sneak-walk with pauses.
This would be a huge pain to do in code if you had a lot of variations of it, but trivial if you could just animate the displacement through Spine in keys.

Admittedly, it leaves a bad taste in my mouth for mixing animation data with balanceable logic data.
I mean, you'd never do it for jumps in a platformer game... maybe. 😃
But it's apparently pretty standard for 3D. Unity supports it for imported 3D animations.
Personally, I'd only ever use it for really specific things.

Not that I'm requesting.

Isn't it enough to put motion on for example a hip bone? Personally I always have a hip bone which is parented to the root bone and if I need to move something I use that and not the root.

@Shiu.
Root Motion isn't about putting motion on any specific bone.
Root Motion means specifying the displacement motion/locomotion of an animation (conventionally by animating the position of the root bone in the animation program, hence the name) and using it as movement of the logical game object.
It means using the animation system to drive logical motion, which isn't something the Spine runtimes do out of the box, though it easily could with additional code on top of what it already does. (Again, not that I'm requesting. 😃)

In the sneak-walk-with-pauses example, it would be really painful to express the starting and stopping and easing of the character's movement through raw code, at least in the typical, game-logical way one would move a game character (by interpreting a stored speed into a change in position).
Just coding it correctly to apply to possibly several types of movements would be one thing, adjusting it perfectly per specific animation would be another, and having to retime it if the animation changes is an additional pain.

The easier solution is for the animator to animate the x-movement of such an animation perfectly in a GUI environment that's meant for animation (ie, Spine Editor), and then just use Root Motion.

Ohhhh I see, I didn't read all of the thread (shame on me) :x
That makes sense, especially with the ease-in/out part. Makes so much sense that I even know what kind of Math should be used for it YAY! Don't ask me to code it though 🙁