Rob

@Janusoo

Did you get y-root motion working now? Could you post the fixed version of the code please?

Thanks!

edit: I think the fix is needed here ->

for(int i = 0; i <= frameCount; i++){
Vector2 v = GetXYAtTime(tt, time);
rootMotionCurve.AddKey(time, v.x);
rootMotionCurveY.AddKey(time, v.y);
time += increment;
}

@rootMotionCurveY.AddKey(time,vy);
to something like that ->
rootMotionCurveY.AddKey(time,?,vy); but what filling in at the x var?
Rob
Posts: 27

janusoo

Rob wrote:@Janusoo

Did you get y-root motion working now? Could you post the fixed version of the code please?

Thanks!

edit: I think the fix is needed here ->

for(int i = 0; i <= frameCount; i++){
Vector2 v = GetXYAtTime(tt, time);
rootMotionCurve.AddKey(time, v.x);
rootMotionCurveY.AddKey(time, v.y);
time += increment;
}

@rootMotionCurveY.AddKey(time,vy);
to something like that ->
rootMotionCurveY.AddKey(time,?,vy); but what filling in at the x var?
@Rob
I'm not a programmer, so maybe my code have some mistakes.
But i think it works well, here is my code...
public class SkeletonRootMotion : MonoBehaviour {

SkeletonAnimation skeletonAnimation;
int rootBoneIndex = -1;
// animation curves for copy position
AnimationCurve rootMotionCurve;
AnimationCurve rootMotionCurveY; // FOR ROOT Y

void OnEnable(){
if(skeletonAnimation == null)
skeletonAnimation = GetComponent<SkeletonAnimation>();

// add events
skeletonAnimation.UpdateState += ApplyRootMotion;
skeletonAnimation.UpdateBones += UpdateBones;
}

void OnDisable(){
// remove events
skeletonAnimation.UpdateState -= ApplyRootMotion;
skeletonAnimation.UpdateBones -= UpdateBones;
}

void Start(){
rootBoneIndex = skeletonAnimation.skeleton.FindBoneIndex( skeletonAnimation.skeleton.RootBone.Data.Name );
skeletonAnimation.state.Start += HandleStart;
}

void HandleStart (Spine.AnimationState state, int trackIndex)
{
//must use first track for now
if(trackIndex != 0)
return;

rootMotionCurve = null;
rootMotionCurveY = null; // FOR ROOT Y

// get current animation
Spine.Animation anim = state.GetCurrent(trackIndex).Animation;

//find the root bone's translate curve
foreach(Timeline t in anim.Timelines){
if(t.GetType() != typeof(TranslateTimeline))
continue;

TranslateTimeline tt = (TranslateTimeline)t;
if(tt.boneIndex == rootBoneIndex){

//sample the root curve's X value
//TODO: cache this data? Maybe implement RootMotionTimeline instead and keep it in SkeletonData
rootMotionCurve = new AnimationCurve();
rootMotionCurveY = new AnimationCurve(); // FOR ROOT Y

float time = 0;
float increment = 1f/30f;
int frameCount = Mathf.FloorToInt(anim.Duration / increment);

for(int i = 0; i <= frameCount; i++){
Vector2 v = GetXYAtTime(tt, time);
rootMotionCurve.AddKey(time, v.x);
rootMotionCurveY.AddKey(time, v.y); // FOR ROOT Y
time += increment;
}

break;
}
}
}

//borrowed from TranslateTimeline.Apply method
Vector2 GetXYAtTime(TranslateTimeline timeline, float time){
float[] frames = timeline.frames;
if (time < frames[0]) return (new Vector2(frames[1], frames[2])); // Time is before first frame.

Bone bone = skeletonAnimation.skeleton.RootBone;
if (time >= frames[frames.Length - 3]) { // Time is after last frame.
return (new Vector2(bone.data.x + frames[frames.Length - 2] - bone.x, bone.data.y + frames[frames.Length - 1] - bone.y)); // FOR ROOT Y
}

// Interpolate between the last frame and the current frame.
int frameIndex = Spine.Animation.binarySearch(frames, time, 3);
float lastFrameX = frames[frameIndex - 2];
float lastFrameY = frames[frameIndex - 1]; // FOR ROOT Y
float frameTime = frames[frameIndex];
float percent = 1 - (time - frameTime) / (frames[frameIndex + -3] - frameTime);
percent = timeline.GetCurvePercent(frameIndex / 3 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent));

return (new Vector2(bone.data.x + lastFrameX + (frames[frameIndex + 1] - lastFrameX) * percent - bone.x, bone.data.y + lastFrameY + (frames[frameIndex + 2] - lastFrameY) * percent - bone.y)); // FOR ROOT Y
}

void ApplyRootMotion(SkeletonAnimation skelAnim){
if(rootMotionCurve == null || rootMotionCurveY == null) // FOR ROOT Y
return;

TrackEntry t = skelAnim.state.GetCurrent(0);

if(t == null)
return;

int loopCount = (int)(t.Time / t.EndTime);
int lastLoopCount = (int)(t.LastTime / t.EndTime);
//disregard the unwanted
if(lastLoopCount < 0) lastLoopCount = 0;

float currentTime = t.Time - (t.EndTime * loopCount);
float lastTime = t.LastTime - (t.EndTime * lastLoopCount);

float delta = 0;
float deltaY = 0;

float a = rootMotionCurve.Evaluate(lastTime);
float aY = rootMotionCurveY.Evaluate(lastTime); // FOR ROOT Y
float b = rootMotionCurve.Evaluate(currentTime);
float bY = rootMotionCurveY.Evaluate(currentTime); // FOR ROOT Y

//detect if loop occurred and offset
if(loopCount > lastLoopCount){
float e = rootMotionCurve.Evaluate(t.EndTime);
float eY = rootMotionCurveY.Evaluate(t.EndTime); // FOR ROOT Y
float s = rootMotionCurve.Evaluate(0);
float sY = rootMotionCurveY.Evaluate(0); // FOR ROOT Y

delta = (e-a) + (b-s);
deltaY = (eY-aY) + (bY-sY); // FOR ROOT Y
}
else{
delta = b - a;
deltaY = bY - aY; // FOR ROOT Y
}

if(skelAnim.skeleton.FlipX)
{
delta *= -1;
deltaY *= -1; // FOR ROOT Y
}


//TODO: implement Rigidbody2D and Rigidbody hooks here
transform.Translate(delta,deltaY,0); // FOR ROOT Y
Debug.DrawLine(new Vector3(transform.position.x+2, 2, 0), new Vector3(transform.position.x+2, 2+deltaY, 0), Color.red, .2f); // FOR ROOT Y
}

void UpdateBones(SkeletonAnimation skelAnim){
//reset the root bone's x component to stick to the origin
skelAnim.skeleton.RootBone.X = 0;
skelAnim.skeleton.RootBone.Y = 0; // FOR ROOT Y
}
}
Or you can see the code on my GitHub.. (Also keep the original code)
https://github.com/januswow/TAG/blob/master/TouchActionGame/Assets/spine-unity/Assets/spine-unity/SkeletonRootMotion.cs
janusoo
Posts: 12

Rob

Thank you :)
Gonna make some tests with it right now : P

---

But if I want to support slopes and advanced movement, rootmotion will not fit my needs right?
I already setup my scripts to handle slopes at any angle. It gots some velocity and other stuff.
Rob
Posts: 27

Mitch

But if I want to support slopes and advanced movement, rootmotion will not fit my needs right?
I already setup my scripts to handle slopes at any angle. It gots some velocity and other stuff.
Depends on how you implement root motion. Rather than setting the object's position directly, you could apply it to its rigidbody, or if you aren't using physics just raycast downward and apply gravity yourself.
User avatar
Mitch

Mitch
Posts: 966

Rob

I use custom movement without physics. Already applied gravity by myself.
It's a raycast based script.

I just don't understand the following:
(no physics used here)

If I press right, a force is being applied to my character. He moves right and the script calculates when to move up or down because a slope is detected.

Rootmotion is just following the animations x and y position, as far as I know. Well, I could stick to my script, without rootmotion, but it offers so many advantages. Especially when it comes to complicated animations.

Could you give me a hint how to combine the rootmotionscript with a raycastercontroller that has its own speed, without physics?

Maybe using delta and deltaY values and put them somewhere as velocity.
Rob
Posts: 27

tsingsan

Hi, Mitch. Thanks for the awesome script.
But I'm a little confused about the resampled rootMotionCurve. What's that for? Can't I just calculate the x delta from the TranslateTimeline?
You mentioned that maybe a RootMotionTimeline would be better. So what's the difference between RootMotionTimeline and TranslateTimeline of the root bone?
Thanks again.
tsingsan
Posts: 1

Mitch

But I'm a little confused about the resampled rootMotionCurve. What's that for? Can't I just calculate the x delta from the TranslateTimeline?
Sure can. I wrote this long, long before I had finished reading over the whole Spine-Unity API heh. I think it was like...my 5th post here or something. The plan was to do a proper root motion implementation with SkeletonUtility but I never got around to it. The hooks were missing from Spine-Unity to do proper Root Motion when I originally implemented this and I didn't want to destructively alter a timeline for RootMotion since it would alter the SkeletonData instance rather than just the SkeletonAnimation instance.

Feel free and modify :) I have no timeline on a better one for SkeleonUtility.
User avatar
Mitch

Mitch
Posts: 966

decoamorim

I am getting a few errors in this part of the CODE.

Here is the printscreen
http://postimg.org/image/64srv3ehb/

It seems to be sintax error.
void OnEnable(){

if (skeletonAnimation == null)
skeletonAnimation = GetComponent<SkeletonAnimation> ();

skeletonAnimation.UpdateWorld += ApplyRootMotion;
skeletonAnimation.UpdateLocal += UpdateBones;
// updatebones == updatelocal && updatestate == updateworld
}

void OnDisable(){

skeletonAnimation.UpdateWorld -= ApplyRootMotion;
skeletonAnimation.UpdateLocal -= UpdateBones;

}
Nobody else got this?
Cruz Brothers Game - http://www.cruzbrothersgame.com
Donut Coffeeshop Studios - http://www.donutcoffeeshopstudios.com
Bunny Battle Arena (IOS) - https://itunes.apple.com/br/app/id847957836
User avatar
decoamorim
Posts: 102

clandestine

I'm assuming the method signature for UpdateWorld and UpdateLocal has changed. You need to make sure your ApplyRootMotion and UpdateBones methods match the delegate signature defined in SkeletonAnimation (actually SkeletonRenderer). Probably because they now expect a SkeletonRenderer and not a SkeletonAnimation, if I had to guess.
User avatar
clandestine
Posts: 48

decoamorim

Oh...i see.

There is documentation about the parameters of spine/unity integration?
Cruz Brothers Game - http://www.cruzbrothersgame.com
Donut Coffeeshop Studios - http://www.donutcoffeeshopstudios.com
Bunny Battle Arena (IOS) - https://itunes.apple.com/br/app/id847957836
User avatar
decoamorim
Posts: 102

renanse

Would be neat if we could get either a proper solution here, or get an official UpdateXXXX callback added to SkeletonAnimation before the UpdateWorldTransform. Right now I need to maintain changes to spine-unity each time I update. :)
renanse
Posts: 4


Return to Runtimes