Updated March 8th: Added a few more details on mapping to navmesh and extrapolating the root motion vector.
Moving something around on the screen in Unity is really not that hard. The point of this post is therefore not to introduce you to how this is accomplished, but rather to point out where you’re doing it wrong™.
Generally Unity moves objects using one of three systems:
- Direct transform manipulation.
In the end, movement is measured on transform updates. I make the distinction from the point of view of where you provide the input. Technically you could argue that animation should be up there as well, but I choose to lump that in with transform manipulation, since disabling the application of movement by the animation system has no other side effects.
As a side-note, since 4.3 it is possible to partially or completely disable having the animation system update the transform hierarchy.
So long as the goal is simply to move an object and nothing else, modifying the values of the transform component, or using its various useful methods to do so, is all you need.
This includes directly animating the transform via the animation window or by enabling animation root motion.
However chances are that your situation is more complex than this and you would do well to read on.
When your object in any way needs to affect and/or be affected by the physics simulation, you need to make some extra considerations. Simply slapping on a collider and calling it a day will ruin your next.
To properly participate as a dynamic part of the physics simulation, an object needs to have a rigidbody component attached somewhere in its transform hierarchy.
The physics system considers separate transform hierarchies as separate objects, so one (and only one) rigidbody component will mark the rest of its hierarchy as dynamic.
Multiple rigidbodies in the same hierarchy leads to undetermined behaviour (read: weirdnessness) – the only exception being if those rigidbodies are connected by a joint – thus making their behaviour again well defined.
To clarify, this does not apply to sibling hierarchies, but strictly scenarios where tracing from one rigidbody, through to the hierarchy root, encounters another rigidbody.
In general, many systems – both built in and third party – rely on dynamic objects being separate transform hierarchies. Building with such a design is both safe and recommended practice.
“But I don’t need gravity or forces or all that other nonsense!” – be cool, that is what the “kinematic” switch is for. This basically tells the physics simulation that your object is dynamic, but you will take care of all its movement.
Kinematic rigidbodies will not be affected by forces or collisions, but will collide with non-kinematic rigidbodies, sending collision events and pushing them out of the way (assuming there are colliders present somewhere in the transform hierarchy of the object).
“So why add the rigidbody in the first place? Things work just fine without!” – if you have Unity pro, I would direct your attention at the profiler as you move about – if not, take my word for it that it is not a joyous sight.
Any object (unique transform hierarchy) with no rigidbody present is treated by the physics simulation as static. For optimal performance, all static colliders are baked into a static collision geometry, securing optimal performance when doing collision checks.
However every time one static collider (note that this has nothing to do with the static flags on the GameObject – just the presence of absence of a rigidbody on the object in question) is moved, the whole static collision geometry is marked dirty and regenerated.
This is not a terribly costly operation, so moving pieces of level geometry from one position to another from time to time is fine. However moving a character of one or more static colliders around every frame will cost you.
Note that while moving by directly modifying the transform of a kinematic rigidbody is just fine, you will get better results for rapid movement by using the MovePosition and MoveRotation rigidbody functions.
The former will effectively “teleport” the physical object – fine for short distances and minor rotations, but less so for longer moves. MoveRotation and -Position effectively “drags” the object from A to B.
“Ok, so maybe it would pretty useful if my character could walk into walls, get pushed by others and that sort of thing…” No problem. Disable the kinematic flag and start moving via the rigidbody component. If you wanted proper forces and all that, I’m sure you’re already all over the AddForce function and friends.
However if you still want strict control – just with a touch of presence – you should look at directly setting the velocity property of the rigidbody.
Given sideways and forward movement input, forming a velocity vector is easy. By setting the velocity of the rigidbody you add that information to the physics simulation as well as tell it to update the position of the object smoothly.
That includes pushes from other rigidbodies or pushbacks from static geometry. However directly setting the velocity will override any directional change and so momentum will remain unchanged.
Therefore consider factoring in some acceleration when building your target velocity vector – for a more natural look after your character is pushed or makes an abrupt change of direction.
Parallel to the physics simulation you find the navigation runtime. Similarly to how the rendered world model is defined by static and dynamic geometry and the physics world model is defined by static colliders and rigidbodies, the navigation world model is defined by interconnected navigation mesh and dynamic obstacles.
While the static physics geometry defines areas of no access, navigation mesh defines areas which are navigable. This information is used for finding a valid path from point A to B, but more importantly it is used to constrain characters and inform them of their surroundings.
The physics simulation can be used for this as well and traditionally is. However the data covered by the physics system is vastly more complex and its ability to define traversable space is a side effect of its ability to define non-traversable space.
This is where you end up spending way too much time blocking off sections of scenery and later testing that there are indeed no holes in that. Navmeshes on the other hand define a surface on which characters of a given height and with a given radius can move.
Similarly to the rigidbody component, the NavMeshAgent component wires an object to the navigation runtime. In stead of the single kinematic switch, however, the NavMeshAgent has separate updatePosition and updateRotation toggles.
To get things going, you can either set a path by one of the many accessors for that or directly set the velocity. Assuming that the NavMeshAgent is configured to update the position, this will start smoothly moving the object like with the rigidbody – only this time constrained by the navigation meshes rather than collision geometry.
In addition to pathfinding and staying on the navigation mesh, the navigation runtime will also attempt to have the various NavMeshAgents avoid one-another by adjusting velocity based on the position and velocity of nearby dynamic obstacles and NavMeshAgents.
Avoidance can be completely tweaked though – so that one NavMeshAgent can ignore it completely or itself be ignored or weigh different NavMeshAgents differently.
“Pathfinding? I just need to move this character around based on input.” Sure, fine – that is where you just go set the velocity of the NavMeshAgent rather than trying to set a path or destination for it.
Like with the rigidbody, this starts moving the NavMeshAgent smoothly and tells the runtime about its current velocity – giving other agents a chance to avoid. Note that even if you do not let the NavMeshAgent directly control the position of your character, you should still feed the velocity back to it – in order to keep the avoidance runtime up to date.
“WAT! You did not count this as one of the three ways of moving stuff!” – nop, quite right. However root motion is awesome, so let’s briefly touch on how we tie that to the other systems for much greatness.
While most of what the animation system does is not of too much concern for your movement logic, one very useful feature is. By analysing animations on import, the surface movement relevant for those animations is calculated and later blended as the animations are blended.
By default the animation system will, with root motion enabled, move an animated object around by directly updating the transform position based on the root motion of the currently playing animation blend. Animation nicely synchronised with world movement.
I’ll root my own motion, thank you
However while that looks mighty cool, it really isn’t very considerate of your carefully crafted physics simulation or your neatly marked navigation runtime. Luckily it does give an in by allowing you to override the actual application of the root motion.
This is accomplished by, on the same game object as your animator component, attaching a script implementing the method OnAnimatorMove. Once that method is defined, the animation system will no longer directly apply root motion and in stead call this implementation post-evaluation.
In the implementation of the OnAnimatorMove callback, you could then update a target velocity vector by simply dividing animator.deltaPosition by Time.deltaTime and similarly rotation. And once we have desired movement in the form of a velocity vector, plenty of the earlier described scenarios become relevant.
Most interactive niceness
“Great, I’ll take one of each!” Sure, no problem. Well… It’s not exactly straight forward, but it is indeed possible to combine all of these things to get something that is responsive, embedded in your simulations and looks great.
The chain of data goes a little something like this:
- Player controlled characters:
- Feed input to the animator, resulting in nicely blended animations and root motion.
- Generate a target velocity in OnAnimatorMove.
- Set velocity of NavMeshAgent (configured to not update position or rotation).
- Set velocity and rotation of non-kinematic rigidbody (properly constrained on rotation so it doesn’t tip over).
- Map transform position to navigation mesh via NavMesh.SamplePosition.
- Non-player characters or indirectly controlled player characters:
- Set destination or path of NavMeshAgent.
- Feed desiredVelocity of NavMeshAgent to animator.
- Repeat as for player controlled characters from 2.
What this setup does not give you is responsiveness to being bumped into. However velocity-wise this is not something your movement implementation should handle directly unless you are ok with breaking that nice root motion setup you just established.
In stead I would recommend using queries on the surrounding physics and navigation environment to inform the animation state machine of special conditions like “player wants to go full speed, but there’s a wall two units from here” and handle slowing down, stopping and similar in there where the result will look good.
One simple trick is to do a navigation raycast along the forward vector of the moving transform for some amount of look-ahead distance and if a navigation mesh edge is hit and do a physics raycast further forward from a point slightly elevated from that edge hit point.
With that setup you can very simply gather information about where the character is headed – if into a steep wall or perhaps to a ledge or obstacle which could be leapt.
Since apparently I lumped just about everything into that project, unsurprisingly the Unity Hacks project has some work on this form of wired up movement. Particularly the Mover component attempts to create a system-agnostic movement interface as well as some simple movement on networked setups not covered here.
While not complete or in any way a final answer, I hope that with the information provided here, it turns out useful for you.