This asset configures the Unity3D Animator to work with a custom root motion algorithm.
The main feature of this asset is that it doesn't need the root bone of all animations to be in the same position in all animations.
REQUIREMENTS
The root motion needs to be in the first layer of the Animator;
The first animation layer weight needs to be '1.0'.
The skinning root node needs to have at least one animation.
The skinning root node must not have scale animations.
KNOWN ISSUES
These implementations are not tested with the Unity IK system.
YOUTUBE VIDEO
The video below shows the asset features:
The video below shows the mini-game that comes as example inside the asset:
1 HOW DOES THE PLUGIN WORKS
You can see the plugin hierarchy below:
There are two components:
RootMotionProcessor: Do the pre-processing of the Unity3D Animator states and clips.
AnimationMotion: Do the move of a GameObject.
1.1 ROOT MOTION PROCESSOR
You need to put the RootMotionProcessor at the same GameObject the Animator is.
Click in the 'Process All Animator Clips' as shown in the image below:
It will process the Unity Animator states and clips and will generate a log as shown in the image below:
The plugin will search for the root node by checking the first node in the model hierarchy that have animation on it.
NOTICE: Every time you change the animator state or clip, you need to click in the 'Process All Animator Clips' again.
1.2 ANIMATION MOTION
The AnimationMotion component will generate the move by changing the current GameObject Transform.
You can put it on any parent level of the RootMotionProcessor.
Take a look at the image below:
When you click in the 'Query Clip Motion Influence Names' it will generate the parameters the root motion will use to move the GameObject.
Take a look at the image below:
These parameters are related to the 'Animator' local space. They are the amount of the animation you will consider to move the GameObject.
The parameter '0' means that this axis in the 'Animator' local space will be only played without move the object.
The parameter '1.0' means that this axis in the 'Animator' local space will be used to move the object.
1.3 CUSTOMIZING THE ANIMATION MOTION
It is possible to customize the AnimationMotion result by implementing a method, delegate or lambda function and add it to the AnimationMotion.OnMove event.
This event will pass the motion vectors and the final position to your method.
This allows you to implement a custom move method.
The first three parameters are in the 'Animator' local space and the last is in the world space.
Take a look at the example below:
using MilkyWay;
using MilkyWay.RootMotionUtil;
using UnityEngine;
[RequireComponent(typeof(AnimationMotion))]
public class CustomAnimationMotion : MonoBehaviour
{
AnimationMotion animationMotion;
Transform animator_transform;
public LayerMask collisionLayerMask;
public int maxSlideSteps = 5;
void Awake()
{
animationMotion = GetComponent<AnimationMotion>();
animator_transform = animationMotion.rootMotionProcessor.transform;
}
void OnEnable()
{
animationMotion.OnMove += AnimationMotion_OnMove;
}
void OnDisable()
{
if (animationMotion != null)
animationMotion.OnMove -= AnimationMotion_OnMove;
}
private void AnimationMotion_OnMove(float animator_right_factor,
float animator_up_factor, float animator_forward_factor,
Vector3 world_position)
{
// Custom move implementation...
Vector3 target_vector = animator_transform.forward * animator_forward_factor;
Vector3 point1, point2;
float radius;
Enhanced.ExtractCapsuleInformation(capsuleCollider,
out point1, out point2, out radius);
Enhanced.Instance.MoveTransformUsingCapsule(transform,
point1, point2, radius,
target_vector, maxSlideSteps,
collisionLayerMask);
}
}
2 BONUS SCRIPTS AT MilkyWay.RootMotionUtil NAMESPACE
There are three problems when you deal with custom character move in Unity3D.
First: Physics doesn't run at the same frequency as the Update and the LateUpdate does.
This causes flickering when you move a Transform and some time later the physics discover that this object shouldn't be at that position, so the physics move the object back to avoid collision.
Second: The Physics.BoxCast, Physics.CapsuleCast and Physics.SphereCast don't consider a valid collision when the objects are touching each other.
This causes the code to detect wrong when an object is colliding.
Third: The Rigidbody.MovePosition and the Rigidbody.SweepTest are discrete simulated.
The object can pass through a wall when they are at certain speeds when using these methods.
We have the Enhanced class to deal with these problems.
2.1 ENHANCED CLASS
This class has an enhanced version of the Physics.BoxCast, Physics.CapsuleCast and Physics.SphereCast.
They are:
Enhanced.BoxCast
Enhanced.CapsuleCast
Enhanced.SphereCast
In addition to these implementations, there are also three methods to calculate a character's movement using the box, capsule or sphere. These move methods don't use the physic materials.
They are:
Enhanced.MoveTransformUsingBox
Enhanced.MoveTransformUsingCapsule
Enhanced.MoveTransformUsingSphere
There are also three other methods to extract information from the BoxCollider, CapsuleCollider or SphereCollider to use in the basic collision algorithms.
They are:
Enhanced.ExtractBoxInformation
Enhanced.ExtractCapsuleInformation
Enhanced.ExtractSphereInformation
The example can be seen in the 1.3 CUSTOMIZING THE ANIMATION MOTION section of this document.
2.2 PRESS RELEASE DETECTOR CLASS
This is a pure C# class to aid in the detection of a rising edge (up) or a falling edge (down) of a boolean signal.
This allow us to simplify our input analysis.
Take a look at the example below:
using MilkyWay.RootMotionUtil;
using UnityEngine;
public class ThirdPersonPlayerController : MonoBehaviour
{
// ...
PressReleaseDetector walk = new PressReleaseDetector();
PressReleaseDetector run = new PressReleaseDetector();
PressReleaseDetector right = new PressReleaseDetector();
PressReleaseDetector left = new PressReleaseDetector();
void Update()
{
// set the state of all analysers
right.SetState(Input.GetKey(KeyCode.A));
left.SetState(Input.GetKey(KeyCode.D));
walk.SetState(Input.GetKey(KeyCode.W) || (right.pressed ^ left.pressed) );
run.SetState(Input.GetKey(KeyCode.W) && Input.GetKey(KeyCode.LeftShift));
// execute the logic based on the analysers result
if (run.down) {
// run state activation ...
} else if ( walk.down || (walk.pressed && run.up) ) {
// walk state activation ...
} else if ( walk.up || run.up) {
// idle state activation ...
}
if (right.pressed ^ left.pressed)
{
if (right.pressed) {
// rotate right ...
} else if (left.pressed) {
// rotate left ...
}
}
}
}
3 EXAMPLES
There are three examples in the asset:
Demo/Example1_ThirdPersonController:
Shows a player controller example.
Demo/Example2_NavMeshAIController:
Shows a 'NavMesh' AI controller example.
Demo/Example3_CustomAnimationMotion:
Shows a custom AnimationMotion behaviour.
Demo/Example4_MiniGame:
Shows a custom game example with several animation changes during the gameplay.
4 DEALING WITH ROOT ANIMATIONS FROM BLENDER'S FBX EXPORTER
The official Blender's FBX exporter exports a 'ghost' root node called 'Armature'.
It makes the plugin to behave wrong because it doesn't detect the right root node of the animation.
You can use the following modification on the Blender's plugin to deal with this issue: