Introduction
In the previous chapters you learned how Unity's physics components work: the Rigidbody,
colliders, physics materials, and the PhysX simulation engine running underneath it all. Now it is time
to connect that knowledge to mathematics. In this chapter you will learn how to apply forces, torques,
and impulses programmatically in C#, understand exactly when and where to write physics code, and build
two complete physics-based projects from scratch.
Physics programming sits at a fascinating intersection of math and game feel. The same jump can feel
floaty or snappy depending purely on which ForceMode you choose and how much mass your
Rigidbody has. Once you understand the math behind these choices, you will stop guessing and
start designing exactly the feel you want.
The AddForce() Method
The primary way to move a physics object in Unity is Rigidbody.AddForce(). This method
tells the physics engine to apply a force vector to the Rigidbody's center of mass at the next
simulation step. Unlike setting transform.position directly, using AddForce()
respects mass, drag, and all other physics properties the result is physically plausible motion.
The basic signature is:
rb.AddForce(Vector3 force, ForceMode mode = ForceMode.Force);
The first argument is the force vector in world space. If you want to apply force in
the object's local space instead, use AddRelativeForce():
rb.AddRelativeForce(Vector3 localForce, ForceMode mode = ForceMode.Force);
This is useful for things like spaceship thrusters: you always want to push "forward" relative to the ship, regardless of which way the ship is pointing in world space.
3D vs 2D Physics
Unity has two separate physics engines: the 3D engine (PhysX) and the 2D engine (Box2D).
-
3D:
Rigidbodycomponent +AddForce(Vector3, ForceMode) -
2D:
Rigidbody2Dcomponent +AddForce(Vector2, ForceMode2D)
Note that ForceMode2D only has two values: ForceMode2D.Force and
ForceMode2D.Impulse. The Acceleration and VelocityChange modes are not available in 2D.
Building a Physics-Based Movement Controller
using UnityEngine;
public class PhysicsMovement : MonoBehaviour
{
[SerializeField] private float moveForce = 10f;
[SerializeField] private float maxSpeed = 5f;
private Rigidbody rb;
private void Start()
{
rb = GetComponent<Rigidbody>();
}
private void FixedUpdate()
{
float horizontal = Input.GetAxis("Horizontal");
float vertical = Input.GetAxis("Vertical");
Vector3 force = new Vector3(horizontal, 0f, vertical) * moveForce;
if (rb.velocity.magnitude < maxSpeed)
{
rb.AddForce(force, ForceMode.Force);
}
}
}
A few things to notice. First, we cache the Rigidbody in Start() rather than calling
GetComponent<Rigidbody>() every frame component lookups have a non-trivial cost
and should always be cached. Second, the physics code is in FixedUpdate(), not
Update(). We will explain exactly why this matters in the next section.
Update vs FixedUpdate vs LateUpdate
Unity calls three different update methods on MonoBehaviours, and choosing the right one is critical for physics programming. Getting this wrong is one of the most common sources of bugs in beginner Unity projects.
Update()
Update() is called once per rendered frame. The interval between calls is
not constant it varies with the rendering workload. You can get the elapsed time
since the last frame with Time.deltaTime.
Use Update() for: reading input, updating UI, moving non-physics objects (Transform-based
movement), checking conditions, playing sounds in response to events.
FixedUpdate()
FixedUpdate() is called at a fixed time step, completely independent of the rendered frame
rate. The default fixed timestep is 0.02 seconds (50 times per second). You can change
this in Edit > Project Settings > Time > Fixed Timestep.
The key insight is that the physics engine advances its simulation in FixedUpdate() steps.
If you apply a force in Update(), you are applying it at irregular intervals sometimes
twice before a physics step, sometimes zero times. This produces inconsistent, framerate-dependent
behavior. Always put physics code in FixedUpdate().
LateUpdate()
LateUpdate() is called once per frame, but after all Update() calls
have finished. This guaranteed ordering makes it ideal for anything that needs to react to what happened
in Update most commonly, camera movement.
The Rule in Plain English
- Input reading →
Update() - Physics forces and velocity changes →
FixedUpdate() - Camera following →
LateUpdate()
The Flickering Problem and How to Solve It
Suppose you have a ball controlled by physics (Rigidbody + AddForce) and a camera that follows it. You write something like this in your camera script:
// WRONG causes flickering!
private void Update()
{
transform.position = target.position + offset;
}
You press Play and immediately notice a jitter or flickering in the movement. The physics engine moves
the ball in discrete FixedUpdate steps (every 0.02 seconds). The camera moves every
rendered frame. These two timelines do not align, producing a momentary mismatch.
The fix is to move the camera in LateUpdate():
using UnityEngine;
public class SmoothFollow : MonoBehaviour
{
[SerializeField] private Transform target;
[SerializeField] private Vector3 offset = new Vector3(0f, 5f, -10f);
[SerializeField] private float smoothSpeed = 8f;
private void LateUpdate()
{
Vector3 desiredPosition = target.position + offset;
transform.position = Vector3.Lerp(
transform.position,
desiredPosition,
smoothSpeed * Time.deltaTime
);
transform.LookAt(target);
}
}
Force vs Impulse: The Four ForceModes
ForceMode determines how the force vector you pass to AddForce() is applied.
There are four options, each suited to different gameplay scenarios.
ForceMode.Force
This is the default mode. The force is applied continuously over time, scaled by the physics timestep and the object's mass. A heavier object accelerates more slowly under the same force.
// Sustained upward thrust heavier objects rise slower
rb.AddForce(Vector3.up * thrustPower, ForceMode.Force);
ForceMode.Impulse
The force is applied as an instantaneous velocity change, scaled by the object's mass. The entire effect is applied in a single physics step.
// Jump called once on a button press, not every frame
rb.AddForce(Vector3.up * jumpStrength, ForceMode.Impulse);
ForceMode.Acceleration
Like Force, but mass is ignored. All objects receive the same acceleration
regardless of how heavy they are.
// All objects accelerate identically, regardless of mass
rb.AddForce(Vector3.forward * boostAcceleration, ForceMode.Acceleration);
ForceMode.VelocityChange
Like Impulse, but mass is ignored. Directly adds the given vector to the
Rigidbody's velocity in a single step.
// Immediately adds 5 m/s forward regardless of mass
rb.AddForce(Vector3.forward * 5f, ForceMode.VelocityChange);
Choosing the Right ForceMode
- Sustained thrust, engine, gravity-like →
ForceMode.Force(respects mass) - Jump, explosion, bullet hit →
ForceMode.Impulse(respects mass) - Consistent acceleration ignoring mass →
ForceMode.Acceleration - Direct velocity addition ignoring mass →
ForceMode.VelocityChange
2D Physics with Rigidbody2D
using UnityEngine;
public class PlatformerMovement2D : MonoBehaviour
{
[SerializeField] private float moveForce = 8f;
[SerializeField] private float jumpForce = 12f;
[SerializeField] private float maxSpeed = 4f;
private Rigidbody2D rb;
private bool isGrounded = false;
private void Start()
{
rb = GetComponent<Rigidbody2D>();
}
private void FixedUpdate()
{
float horizontal = Input.GetAxis("Horizontal");
Vector2 force = new Vector2(horizontal * moveForce, 0f);
if (Mathf.Abs(rb.velocity.x) < maxSpeed)
{
rb.AddForce(force, ForceMode2D.Force);
}
}
private void Update()
{
if (Input.GetButtonDown("Jump") && isGrounded)
{
rb.AddForce(Vector2.up * jumpForce, ForceMode2D.Impulse);
isGrounded = false;
}
}
private void OnCollisionEnter2D(Collision2D col)
{
if (col.gameObject.CompareTag("Ground"))
isGrounded = true;
}
private void OnCollisionExit2D(Collision2D col)
{
if (col.gameObject.CompareTag("Ground"))
isGrounded = false;
}
}
Exercise: The Flying Saucer
A hovering saucer controlled with WASD, moving by applying forces and rotating toward its direction of movement.
using UnityEngine;
public class FlyingSaucer : MonoBehaviour
{
[Header("Movement")]
[SerializeField] private float thrustForce = 15f;
[SerializeField] private float maxSpeed = 8f;
[SerializeField] private float dragCoefficient = 2f;
[Header("Rotation")]
[SerializeField] private float rotationSpeed = 5f;
private Rigidbody rb;
private Vector3 inputDirection;
private void Start()
{
rb = GetComponent<Rigidbody>();
}
private void Update()
{
float h = Input.GetAxis("Horizontal");
float v = Input.GetAxis("Vertical");
inputDirection = new Vector3(h, 0f, v);
}
private void FixedUpdate()
{
if (inputDirection.magnitude > 0.1f && rb.velocity.magnitude < maxSpeed)
{
rb.AddForce(inputDirection.normalized * thrustForce, ForceMode.Force);
}
Vector3 flatVelocity = new Vector3(rb.velocity.x, 0f, rb.velocity.z);
rb.AddForce(-flatVelocity * dragCoefficient, ForceMode.Force);
}
private void LateUpdate()
{
if (inputDirection.magnitude > 0.1f)
{
Quaternion targetRotation = Quaternion.LookRotation(inputDirection.normalized);
transform.rotation = Quaternion.RotateTowards(
transform.rotation,
targetRotation,
rotationSpeed * Time.deltaTime * 100f
);
}
}
}
The AddTorque() Method
Just as AddForce() applies a linear force to the center of mass, AddTorque()
applies a rotational force a torque around an axis.
rb.AddTorque(Vector3 torque, ForceMode mode = ForceMode.Force);
AddRelativeTorque() works the same way but in local space:
rb.AddRelativeTorque(Vector3 localTorque, ForceMode mode = ForceMode.Force);
Example: Spinning a Wheel with Physics
using UnityEngine;
public class PhysicsWheel : MonoBehaviour
{
[SerializeField] private float torqueStrength = 20f;
[SerializeField] private float maxAngularVelocity = 15f;
private Rigidbody rb;
private void Start()
{
rb = GetComponent<Rigidbody>();
rb.maxAngularVelocity = maxAngularVelocity;
}
private void FixedUpdate()
{
float input = Input.GetAxis("Vertical");
rb.AddRelativeTorque(Vector3.right * input * torqueStrength, ForceMode.Force);
}
}
AddForceAtPosition()
// Push at the right edge causes translation AND rotation
Vector3 rightEdge = transform.position + transform.right * 2f;
rb.AddForceAtPosition(Vector3.forward * pushStrength, rightEdge, ForceMode.Force);
Exercise: Roll-A-Ball
Ball Movement Script
using UnityEngine;
using UnityEngine.UI;
public class RollABall : MonoBehaviour
{
[Header("Physics")]
[SerializeField] private float rollForce = 12f;
[SerializeField] private float maxSpeed = 6f;
[Header("UI")]
[SerializeField] private Text scoreText;
private Rigidbody rb;
private int score = 0;
private Vector3 inputForce;
private void Start()
{
rb = GetComponent<Rigidbody>();
UpdateScoreUI();
}
private void Update()
{
float h = Input.GetAxis("Horizontal");
float v = Input.GetAxis("Vertical");
inputForce = new Vector3(h, 0f, v) * rollForce;
}
private void FixedUpdate()
{
if (rb.velocity.magnitude < maxSpeed)
{
rb.AddForce(inputForce, ForceMode.Force);
}
}
private void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Pickup"))
{
other.gameObject.SetActive(false);
score++;
UpdateScoreUI();
if (score >= 12)
{
Debug.Log("You win!");
}
}
}
private void UpdateScoreUI()
{
scoreText.text = "Score: " + score;
}
}
Rotating Pickup Script
using UnityEngine;
public class PickupRotate : MonoBehaviour
{
[SerializeField] private float rotateSpeed = 90f;
private void Update()
{
transform.Rotate(Vector3.up, rotateSpeed * Time.deltaTime);
}
}
Putting It All Together: Physics Design Principles
-
Cache your Rigidbody. Always store the reference in
Start(). Never callGetComponent<Rigidbody>()in Update or FixedUpdate. - Physics code goes in FixedUpdate. No exceptions. Even if it "works" in Update on your machine, it will break on machines with different frame rates.
- Camera following goes in LateUpdate. This eliminates jitter when following physics objects.
- Use ForceMode deliberately. Force and Impulse respect mass; Acceleration and VelocityChange ignore it. Match your mode to your gameplay intent.
- AddTorque for rotational forces. Use AddRelativeTorque when the axis is in local space. Remember to raise maxAngularVelocity if needed.
- AddForceAtPosition for off-center forces. This produces both translation and rotation, simulating realistic physical interactions.
In the next chapter we will look at raycasting casting invisible rays through the physics world to detect collisions, implement shooting, and solve ground-detection problems.
Need help with Unity3D development?
I'm a senior developer with 16+ years experience, including AAA projects at Ubisoft. Let's discuss how I can help with your game or interactive project.
Start a Conversation