Transform.Translate()
seems to behave like teleportation because it simply moves a Transform without any knowledge of underlying physics. It could be enough for moving a simple object, but it is likely to interfere with a more complex simulation. The "not as nearly smooth" feeling you are experiencing is due to inconsistencies between the object's rendering and physics states.
How is physics done?
Game engines simulate rigid-body physics via numerical integration. The starting idea is to consider an initial position and velocity \$(x_0, v_0)\$, then take a small step forward and compute the position and velocity \$(x_t, v_t)\$ at a future time. Finally, repeat this process using the result from previous calculations as the new initial data. Although we consider time continuous, computer numbers are finite, and integration must use discrete time steps.
Many physics engines take several properties into account: mass, friction, density, etc. All these are plugged into the equations and contribute to a better simulation. Eventually, they can be wrapped together (in a data structure) and considered a physics state. The engine will use a given physics state \$S_t\$ to compute the next physics state \$S_{t+1}\$:
// Example physics state
struct State {
Vector3 position;
Vector3 velocity;
// ...
}
// Example integrator
State Integrate(State fromState, float dt) {
State toState = State();
toState.velocity = fromState.velocity + ( forces / mass ) * dt;
toState.position = fromState.position + toState.velocity * dt;
// ...
return toState;
}
When is physics updated?
The above function is a naive example of what modern engines implement in their physics update routines. You achieve movement by applying forces and torque to rigid bodies, and the engine updates the physics.
However, we don't usually think in terms of Newtonian forces. A more natural way to think about movement is by considering velocity. Therefore, we may use Rigidbody.AddForce()
with the ForceMode.VelocityChange
parameter and obtain a character-like controller. Or, we could get rid of the RigidBody component and write our custom movement code.
Whatever choice, the order of execution is paramount here. A game loop updates physics, reads user input, renders graphics, and more. Code execution during these phases must be as much consistent as possible. Engines enforce this consistency by exposing game loop events. These events are each intended for specific use cases.
When do inconsistencies rise?
In Unity's event execution order, you can see that the internal physics update has a specific order in the flowchart: it comes after FixedUpdate()
and before Update()
. FixedUpdate()
is intended for applying forces to rigid bodies; forces will transform into motion subsequently. Alternatively, you can code your custom physics inside FixedUpdate()
to make it go along with the engine's time step. Either way, the Rigidbody component will update the Transform position before rendering.
If you translate a rigid body during Update()
, you overwrite the latest integrated position with a new one. At least, this is only true for its Transform component. As said above, a rigid body keeps track of its internal physics state, and so does in Unity. Transform.Translate()
changes the object's Transform position without affecting other components, such as a Rigidbody component.
There we have an inconsistency between Transform and Rigidbody states. Physics updates Rigidbody; Rigidbody updates Transform; we update Transform. Finally, the GameObject renders at Transform position. Then, the loop continues, but we aren't updating Transform anymore. However, we witness strange behaviour. The object abruptly changes position or starts moving at high speed with no apparent reason. This happens because physics resume from Rigidbody.position
to simulate the next state, completely ignoring Transform.position
.
It's okay to control a kinematic character via custom code and switch to ragdoll physics when they die. We can also combine kinematic and physics objects in the same environment (e.g. player moving around crates). When it comes to physics, we must choose only one update method per object. Or at least only one approach at any given time.