Some of you might have seen me mentioning or showcasing a more advanced and versatile root motion solution for a few months, this post finally covers the fundamental inner workings of this system as promised. I might make a follow up that covers some of the more elaborate gimmicks I added to this system over time, but for now I want to focus on the basic approach itself. As on the previous posts, this isn’t exactly aimed at people who are new to Unreal or game development in general so some previous experience and understanding is required, especially regarding communication between different classes. You can implement this system both in blueprints and C++, personally I prefer C++ for this sort of thing but for the sake of simplicity I’m showcasing it as blueprints here.
Existing Approaches and Issues
As anyone who tried to use root motion in Unreal Engine 4 can probably confirm, the root motion support is not that great out of the box. This has been quite a weak spot of UE4 for quite some time now and while there are existing methods to work around this weakness, none of them were really fit for the much more sophisticated and high-fidelity goals I had in mind for my Arkham-Project (I’m the kind of person that tends to do things properly, especially on my hobby projects).
The first time I ran into this issue was when adding ledge climbing to my project, as this would ideally play the actual climbing animation relative to a specific point of origin in the map. The way I solved this at first was simply sliding the player to the desired start location and then play the climbing montage from there, this approach has numerous issues though. The first issue were animation blends, as those would affect the root motion as well, so you would have to disable blends on the relevant animation montages entirely, which in turn causes the visual fidelity of the result to take a bit of a hit (not to mention the issues arising with rigid body physics when the mesh pose suddenly changes without a smooth blend). The next issue would be ever so slight mismatches between the player and the object you intend to climb. Minor location differences that you are not accounting for (caused by the ledge detection for example) could lead to the player simply being unable to climb, as the animation has no actual relation to a world location but rather just plays back against the environment, thus the character collision can end up being stuck which in turn causes the whole climbing action to fail. This doesn’t really sound like a system with AAA-Grade robustness, does it?
Once I have started to work on combat animations these shortcomings turned into a much bigger issue, as I not only had to ensure that the animations play back relative to the correct start location, but also relative to a target that has the ability to move around while attacking. A commonly showcased solution for this problem is the “MoveTo” Blueprint-node UE4 provides, which interpolates the actor location to a target transform over a set duration. This is however a far cry from the results one would get by correctly applying the root motion transform that is baked into the attack animations, not to mention the fact that a simple linear or exponential interpolation simply doesn’t look authentic or realistic.
This is where my solution comes in, which I have designed with these use cases in mind. Before I dive into it, I’d like to remind you of the root motion warping solution that Epic Games has integrated into UE5, although I can’t say if that solution ends up being on-par with my own solution. From the looks of it so far, I would say that it does have the potential to be a very useful tool to solve these issues without having to write slightly more extensive custom solutions.
With that being said, let’s crack on with the solution I came up with.
As I have already mentioned, I have stumbled across the many shortcomings of my first approach to playing root motion animation at the correct world location a few times, so when I upgraded to a newer version of UE4 and redid my project for the sake of tidying things up, I decided to tackle this issue properly.
The general idea behind my approach is to manually extract the root motion transform that is baked into the animation, so that I can then calculate the actual desired world location from that data while fully bypassing the root motion handling that comes with UE4.
Extracting Root Motion
In order to extract the root motion transform I have made an animation modifier, which extracts the required data from the animation and then stores it as simple float curves in the animation asset. That way I can easily access that data from my animation blueprint or character class. The setup for such an animation modifier is trivial and looks like this. As I do not need any rotation axis aside from the Yaw I am only extracting that one.
As this setup seems fairly straightforward, I don’t think I need to elaborate it further (you’ll obviously have to use the name of the root bone of your rig). The normalisation metadata curve is required to correctly account for animation blends when converting the curves into the required transform data. You will also need to have the root motion extraction enabled on the animation asset for things to work properly in the end.
Gathering the Required Data
Now that we know how to get the data we need, it’s time to turn it into something useful. The first step for this is to read the curve values in a blueprint or C++ class (I prefer doing it after the evaluation in the animation blueprint class, given that it runs after the animation graph has been evaluated) in order to write them to a variable that we can easily access later. We do this by simply using the “GetCurveValue” function (exists both as a node for blueprints and a function when using C++) and then dividing it by the normalisation float curve. You would like to clamp the value of the latter to a minimum value of something positive close to, but not equal to, zero to avoid any errors due to dividing by zero.
The next step is to make the logic that transforms this data into the desired world location and rotation as well as applying that to our character. In my examples I will be doing this inside a blueprint component class, as I like to keep logic like this that can be reused across many different classes somewhat modular and easy to just plop into any class I like.
We start by creating a function that allows us to feed any eventual parameters for the pending root move into our blueprint, as we need to be able to tell our system where it should play back the root motion. We then feed those values into variables that we can easily access later. For this basic version we just have to pass it an origin transform. I’ve simply called that function “SetupMove”.
Now that this is done, we can go ahead and calculate the desired target location of our root move. For that we will read the data we have extracted from the animation in our animation blueprint, which you can do by either casting the animation instance directly to your animation blueprint class or by using an interface. The latter is more sensible when you are using this solution with many completely different actor classes, that being said casts are not as performance intensive as some people make them out to be. For this example I’ve created a simple function inside the animation blueprint that returns the cached root move transform and alpha and I’m then calling this function from the root move component. We will then compute the delta between the last root move transform we stored in the component and the new transform data we are reading from the animation instance. This can easily be done by using the “Make Relative Transform” node. We will then accumulate this delta with the previous delta by composing the result of the relative transform node into our stored delta and store that to our variable for the move delta as we will need it later to improve the blending into the desired transform. After that we will also store the alpha and the move transform into variables. Additionally we will also make a Boolean variable that allows us to easily check if there is currently a root move happening, which also turns to false if we are blending out of a root move. The latter has two reasons, with the first being blending issues happening when blending out and the second being that there really isn’t a sensible way to blend back to player controlled motion so we might as well just directly stop the root move. Since we would only want to accumulate the root move delta if there actually was a root move happening in the previous frame we’ll add a simple conditional selector using the Boolean we just created. If there wasn’t a root move in the last frame we simply take the current delta without accumulating it on-top of the previous value. The result should look something like this:
Next up we will need some way of disabling any root moves when an animation blend is happening, for this you can simply create a custom event with a retriggerable delay. Then you use a Boolean variable that you set to true when the custom event is called and to false after the delay, while using an input from the custom event to set the duration of the delay. All you need to do is call this event whenever you are playing back an animation montage that uses this system, while using the blend in time of that montage as the duration input.
Last but not least, as long as there is no root move happening you’ll have to store the current transform of your character actor into a variable, as this value will be needed to create a smooth blend from the start transform into the root move.
Calculating and Applying the Desired Location
Now that we have the required data, we can calculate the desired target location of the root move. In order to do so we will compose the accumulated delta onto the transform of the character from when the root move started as well as composing the move transform onto our move origin. All that is needed now is to simply blend between both transforms based on the alpha of the root move. Once that is done you merely have to set the transform of your character to the result. The resulting logic should look like this:
Now all that is left to do is to simply add a curve for the alpha of the root move to your animations (the curve name would be “RootMove_Alpha” if you followed my example) and it should be good to go. You can also blend location and rotation separately, as you may want to have a quicker blend into the desired rotation which is something that I am usually doing.
I’ve made a fancy representation of the maths happening behind the scenes by simply drawing debug spheres at the locations of the various transforms, so here is an example of the results.
And here is the system in action with my Arkham Player Class, the debug colours are a slightly different here but if you understood the concept behind the system you should be able to figure out what is what :p
Further Improvements and Closing Words
This very basic implementation of my approach obviously still has a lot of headroom for various smaller improvements or different modes for the blend towards the desired location. The version I am using for my Arkham-Project for example has numerous extra gimmicks that improve the overall usability of it. This includes things like “floor projection”, which can glue to the character to the floor at all times or just prevent the desired location from going into an impossible location beneath the floor. Another useful feature is motion constraints, which allow me to have a move never go upwards or downwards. This is quite handy when it comes to climbing animations and such.
As I already mentioned, Root Motion Warping is also coming to UE5 as an out-of-the-box feature and could potentially be a solution for the issues I ran into that is on par with this system.
I hope this post was useful for you folks and helped you with creating gameplay that has a higher level of fidelity than UE4 would have allowed out of the box.
Since I spent a decent amount of time trying to come up with this solution from scratch as well as this sort of thing being more of a hobby for me, it’d be very appreciated if you consider becoming a Patron, as this helps me with spending more time on stuff like this :)