• Runtimes
  • Applying Root Motion

Pharan लिखा

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.

Related Discussions
...

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 महीने बाद में

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 🙁

23 दिन बाद में

This a nice video to illustrate the concept:
http://vimeo.com/12052874
Plus IK pinning to create accurate motion in-game.

3 महीने बाद में

I'm going to need Root Motion in a project I'm working on now. Has any native runtime support been added since this thread started 7 months ago? Otherwise, I'll need to take the route of AdamT, and roll my own.

ShaneSmit लिखा

I'm going to need Root Motion in a project I'm working on now. Has any native runtime support been added since this thread started 7 months ago? Otherwise, I'll need to take the route of AdamT, and roll my own.

I'll tackle this for Unity. I'm in the middle of overhauling the Spine


Unity stuff. Will post about what I've been up to later tonight.


Had to modify SkeletonAnimation and add a callback between Update and Apply because the Time and LastTime are set to be equivalent by the time UpdateBones is called.

SkeletonAnimation.cs

using System;
using System.IO;
using System.Collections.Generic;
using UnityEngine;
using Spine;

[ExecuteInEditMode]
[AddComponentMenu("Spine/SkeletonAnimation")]
public class SkeletonAnimation : SkeletonRenderer {
   public float timeScale = 1;
   public bool loop;
   public Spine.AnimationState state;

   public delegate void UpdateBonesDelegate(SkeletonAnimation skeleton);
   public UpdateBonesDelegate UpdateBones;
   public UpdateBonesDelegate UpdateState;

   [SerializeField]
   private String _animationName;
   public String AnimationName {
      get {
         TrackEntry entry = state.GetCurrent(0);
         return entry == null ? null : entry.Animation.Name;
      }
      set {
         if (_animationName == value) return;
         _animationName = value;
         if (value == null || value.Length == 0)
            state.ClearTrack(0);
         else
            state.SetAnimation(0, value, loop);
      }
   }

   public override void Reset () {
      base.Reset();
      if (!valid) return;

  state = new Spine.AnimationState(skeletonDataAsset.GetAnimationStateData());
  if (_animationName != null && _animationName.Length > 0) {
     state.SetAnimation(0, _animationName, loop);
     Update(0);
  }
   }

   public virtual void Update () {
      Update(Time.deltaTime);
   }

   public virtual void Update (float deltaTime) {
      if (!valid) return;

  deltaTime *= timeScale;
  skeleton.Update(deltaTime);
  state.Update(deltaTime);

  if(UpdateState != null)   UpdateState(this);

  state.Apply(skeleton);
  if (UpdateBones != null) UpdateBones(this);
  skeleton.UpdateWorldTransform();
   }
}

SkeletonRootMotion.cs

using UnityEngine;
using System.Collections;
using Spine;

[RequireComponent(typeof(SkeletonAnimation))]
public class SkeletonRootMotion : MonoBehaviour {


   SkeletonAnimation skeletonAnimation;
   int rootBoneIndex = -1;
   AnimationCurve rootMotionCurve;

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

  skeletonAnimation.UpdateState += ApplyRootMotion;
  skeletonAnimation.UpdateBones += UpdateBones;
   }

   void OnDisable(){
      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;

  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();

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

        for(int i = 0; i <= frameCount; i++){
           float x = GetXAtTime(tt, time);
           rootMotionCurve.AddKey(time, x);
           time += increment;
        }

        break;
     }
  }
   }
   
//borrowed from TranslateTimeline.Apply method float GetXAtTime(TranslateTimeline timeline, float time){ float[] frames = timeline.frames; if (time < frames[0]) return frames[1]; // Time is before first frame.
Bone bone = skeletonAnimation.skeleton.RootBone; if (time >= frames[frames.Length - 3]) { // Time is after last frame. return (bone.data.x + frames[frames.Length - 2] - bone.x); } // Interpolate between the last frame and the current frame. int frameIndex = Spine.Animation.binarySearch(frames, time, 3); float lastFrameX = frames[frameIndex - 2]; 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 (bone.data.x + lastFrameX + (frames[frameIndex + 1] - lastFrameX) * percent - bone.x); } void ApplyRootMotion(SkeletonAnimation skelAnim){ if(rootMotionCurve == null) 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 a = rootMotionCurve.Evaluate(lastTime); float b = rootMotionCurve.Evaluate(currentTime); //detect if loop occurred and offset if(loopCount > lastLoopCount){ float e = rootMotionCurve.Evaluate(t.EndTime); float s = rootMotionCurve.Evaluate(0); delta = (e-a) + (b-s); } else{ delta = b - a; } if(skelAnim.skeleton.FlipX) delta *= -1; //TODO: implement Rigidbody2D and Rigidbody hooks here transform.Translate(delta,0,0); } void UpdateBones(SkeletonAnimation skelAnim){ //reset the root bone's x component to stick to the origin skelAnim.skeleton.RootBone.X = 0; } }
Mitch लिखा

I'll tackle this for Unity. I'm in the middle of overhauling the Spine


Unity stuff. <snip>

Thanks Mitch! :handshake: I'll give this a shot as soon as possible and let you know how it goes.


I had to change a couple things:

1) In one instance I am just triggering the animation via the inspector's drop-down list, and in that case HandleStart() was never being called. So I called it manually in Start().

2) My spine characters are scaled quite a bit, so I had to apply local scale to the 'delta' before Translate().

But yeah, it works great so far! Thanks again Mitch!

ShaneSmit लिखा
Mitch लिखा

I'll tackle this for Unity. I'm in the middle of overhauling the Spine


Unity stuff. <snip>

Thanks Mitch! :handshake: I'll give this a shot as soon as possible and let you know how it goes.


I had to change a couple things:

1) In one instance I am just triggering the animation via the inspector's drop-down list, and in that case HandleStart() was never being called. So I called it manually in Start().

2) My spine characters are scaled quite a bit, so I had to apply local scale to the 'delta' before Translate().

But yeah, it works great so far! Thanks again Mitch!

Welcome 🙂

Heh yea, I encountered those too, figured it'd be better to get somethin up here before I really try and figure out a more permanent solution for root motion.

3 महीने बाद में

Hello! I'm countering this problem, too.
My character need to perform very complex movement in move animations, attack animations....
So i have to animate root motion in Spine to get better result. This post helps a lot, thanks!

But i am trying to do the same thing on Y Axis(Height).
First, sorry for my English. Second, i am not a programmer(I am animator) and new to Spine.
I modify Mitch's code. Add a animation curve to catch y transform.
But it works totally wrong... and can not figure out how to fix it...
Seems GetXYAtTime() are returning both x transform..
SkeletonRootMotion.cs

public class SkeletonRootMotion : MonoBehaviour {
   

SkeletonAnimation skeletonAnimation; int rootBoneIndex = -1; // animation curves for copy position AnimationCurve rootMotionCurve; AnimationCurve rootMotionCurveY;
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; // 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(); 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); 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[1])); // 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 - 2] - bone.y)); } // Interpolate between the last frame and the current frame. int frameIndex = Spine.Animation.binarySearch(frames, time, 3); float lastFrameX = frames[frameIndex - 2]; 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 + lastFrameX + (frames[frameIndex + 1] - lastFrameX) * percent - bone.y)); }
void ApplyRootMotion(SkeletonAnimation skelAnim){ if(rootMotionCurve == null || rootMotionCurveY == null) 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); float b = rootMotionCurve.Evaluate(currentTime); float bY = rootMotionCurveY.Evaluate(currentTime); //detect if loop occurred and offset if(loopCount > lastLoopCount){ float e = rootMotionCurve.Evaluate(t.EndTime); float eY = rootMotionCurveY.Evaluate(t.EndTime); float s = rootMotionCurve.Evaluate(0); float sY = rootMotionCurveY.Evaluate(0); delta = (e-a) + (b-s); deltaY = (eY-aY) + (bY-sY); } else{ delta = b - a; deltaY = bY - aY; } if(skelAnim.skeleton.FlipX) { delta *= -1; deltaY *= -1; } //TODO: implement Rigidbody2D and Rigidbody hooks here transform.Translate(delta,deltaY,0); Debug.DrawLine(new Vector3(transform.position.x+2, 2, 0), new Vector3(transform.position.x+2, 2+deltaY, 0), Color.red, .2f); }
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; } }

Hope some one can give me a hand. THANKS!! 🙁


Oh... I found the problem..
Because the data of frames are written as [time, x, y, ....., time, x, y] !

एक महीने बाद में

@[हटाया गया]

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 लिखा

@[हटाया गया]

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

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.

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.

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.