Chapter 6: Physics Programming — Forces, Torque, and Applied Math

Game DevelopmentMath for Unity3DJune 201840 min read

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. A car can feel heavy and realistic or twitchy and arcade-like — the difference is often a single line of code. 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. Its direction determines which way the force pushes, and its magnitude determines how strong the push is. The second argument is the ForceMode, which controls how the force is interpreted by the physics engine (we will cover all four modes in detail shortly).

If you want to apply force in the object's local space instead of world space, 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). They use different components and slightly different APIs:

  • 3D: Rigidbody component + AddForce(Vector3, ForceMode)
  • 2D: Rigidbody2D component + 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. This is a limitation of Box2D and rarely matters in practice — the two available modes cover the vast majority of use cases.

Building a Physics-Based Movement Controller

Let's build the most common use of AddForce(): a movement controller that reads player input and pushes a Rigidbody around. Here is the complete MonoBehaviour:

using UnityEngine;

public class PhysicsMovement : MonoBehaviour
{
    [SerializeField] private float moveForce = 10f;
    [SerializeField] private float maxSpeed  = 5f;

    private Rigidbody rb;

    private void Start()
    {
        // Cache the Rigidbody component once — GetComponent is expensive
        rb = GetComponent<Rigidbody>();
    }

    private void FixedUpdate()
    {
        // Read input axes (returns values between -1 and 1)
        float horizontal = Input.GetAxis("Horizontal");
        float vertical   = Input.GetAxis("Vertical");

        // Build a force vector in world space (flat movement on X-Z plane)
        Vector3 force = new Vector3(horizontal, 0f, vertical) * moveForce;

        // Only apply force if we are under the speed cap
        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.

Third, we check rb.velocity.magnitude before applying force. Without a speed cap the object would accelerate indefinitely as long as a key is held down — you can also achieve the same effect by tuning the Rigidbody's Drag property in the Inspector, which applies a damping force proportional to velocity and naturally creates a terminal velocity.

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. If the game runs at 60 fps, Update is called 60 times per second. If the frame rate drops to 30, it is called 30 times. 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().

Inside FixedUpdate(), use Time.fixedDeltaTime (not Time.deltaTime) if you need to scale values by the elapsed time — although for AddForce() Unity handles the timestep scaling for you automatically.

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.

Use LateUpdate() for: cameras that follow a target, post-processing Transform adjustments, anything that must execute after other scripts have moved objects.

The Rule in Plain English

  • Input readingUpdate()
  • Physics forces and velocity changesFixedUpdate()
  • Camera followingLateUpdate()

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 — especially visible at lower frame rates. What is happening?

The physics engine moves the ball in discrete FixedUpdate steps (every 0.02 seconds). The camera moves every rendered frame (every ~0.016 seconds at 60 fps). These two timelines do not align. Sometimes a frame is rendered between two physics steps, meaning the camera has moved but the ball hasn't yet — and you see a momentary mismatch.

The fix is to move the camera in LateUpdate(), which runs after the physics step for that frame has been applied and after all Update calls have settled:

using UnityEngine;

public class SmoothFollow : MonoBehaviour
{
    [SerializeField] private Transform target;
    [SerializeField] private Vector3   offset = new Vector3(0f, 5f, -10f);
    [SerializeField] private float     smoothSpeed = 8f;

    // LateUpdate guarantees we run AFTER physics and AFTER all Update() calls
    private void LateUpdate()
    {
        Vector3 desiredPosition = target.position + offset;
        transform.position = Vector3.Lerp(
            transform.position,
            desiredPosition,
            smoothSpeed * Time.deltaTime
        );

        transform.LookAt(target);
    }
}

This pattern eliminates flickering completely. The Lerp smooths out any remaining micro-jitter by interpolating toward the target rather than snapping to it.

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 (more mass) accelerates more slowly under the same force — exactly as in real physics (F = ma, so a = F/m).

Units: Newtons (kg·m/s²). Use for: engine thrust, gravity-like effects, wind, magnetic attraction, anything that pushes continuously over multiple physics steps.

// 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. A heavier object receives a smaller velocity change for the same impulse magnitude.

Units: Newton-seconds (kg·m/s). Use for: jumps, explosions, bullet knockback, any sudden velocity change triggered by an event.

// 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. Useful when you want consistent behavior across objects of different masses, or when simulating effects that physically apply to all objects equally (like gravity, which accelerates all objects at 9.81 m/s² regardless of mass).

// 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. This is the most direct control you can have over a Rigidbody's speed without simply setting rb.velocity directly.

// Immediately adds 5 m/s forward regardless of mass
rb.AddForce(Vector3.forward * 5f, ForceMode.VelocityChange);

Choosing the Right ForceMode

  • Sustained thrust, engine, gravity-likeForceMode.Force (respects mass)
  • Jump, explosion, bullet hitForceMode.Impulse (respects mass)
  • Consistent acceleration ignoring massForceMode.Acceleration
  • Direct velocity addition ignoring massForceMode.VelocityChange

Acceleration and VelocityChange in Practice

The mass-ignoring modes are more useful than they might initially appear. Consider a game with enemies of varying sizes — small goblins and large trolls. If you use ForceMode.Impulse for a knockback effect, the troll (high mass) will barely budge while the goblin (low mass) flies across the screen. This might be intentional and realistic, but often you want all enemies to react with the same visual snappiness.

In that case, ForceMode.VelocityChange gives every enemy the same velocity addition regardless of mass:

// Consistent knockback regardless of enemy size
void ApplyKnockback(Rigidbody target, Vector3 direction, float strength)
{
    target.AddForce(direction * strength, ForceMode.VelocityChange);
}

Similarly, ForceMode.Acceleration is ideal for player characters in platformers where you want snappy, responsive movement that feels the same whether the player is carrying a heavy object or not. Many professional Unity platformers use this mode for player movement precisely because it decouples the feel of control from the physics mass, allowing designers to tune mass for collision behavior without affecting movement feel.

2D Physics with Rigidbody2D

Working with 2D physics in Unity follows almost identical patterns, with the main differences being:

  • Use Rigidbody2D instead of Rigidbody
  • Forces use Vector2 instead of Vector3
  • Use ForceMode2D with only .Force and .Impulse modes
  • Movement is on the X-Y plane (Z is depth/layering, not physics)
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()
    {
        // Read jump input in Update (event-driven), apply in physics
        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;
    }
}

Notice that we read input in Update() and use GetButtonDown — which returns true only on the exact frame the button is pressed. If we checked this in FixedUpdate() we might miss the press entirely (if it happened between two physics steps) or detect it twice (if FixedUpdate runs more than once per frame). Input reading belongs in Update(); force application belongs in FixedUpdate().

Exercise: The Flying Saucer

Let's put everything together with a complete mini-project: a hovering saucer that the player controls with WASD, moving by applying forces and rotating toward its direction of movement.

Project Setup

  • Create a new 3D scene.
  • Add a Plane as the ground (scale 10, 1, 10), add a PhysicsMaterial with zero friction.
  • Add a Capsule (or import a saucer mesh) at position (0, 2, 0). Add a Rigidbody.
  • Set Rigidbody Constraints: Freeze Position Y, Freeze Rotation X and Z (saucer stays flat).
  • Attach the script below.
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()
    {
        // Read input in Update
        float h = Input.GetAxis("Horizontal");
        float v = Input.GetAxis("Vertical");
        inputDirection = new Vector3(h, 0f, v);
    }

    private void FixedUpdate()
    {
        // Apply thrust force
        if (inputDirection.magnitude > 0.1f && rb.velocity.magnitude < maxSpeed)
        {
            rb.AddForce(inputDirection.normalized * thrustForce, ForceMode.Force);
        }

        // Apply manual drag on X-Z to stop sliding (we froze Y so vertical drag is irrelevant)
        Vector3 flatVelocity = new Vector3(rb.velocity.x, 0f, rb.velocity.z);
        rb.AddForce(-flatVelocity * dragCoefficient, ForceMode.Force);
    }

    private void LateUpdate()
    {
        // Rotate toward movement direction smoothly
        if (inputDirection.magnitude > 0.1f)
        {
            Quaternion targetRotation = Quaternion.LookRotation(inputDirection.normalized);
            transform.rotation = Quaternion.RotateTowards(
                transform.rotation,
                targetRotation,
                rotationSpeed * Time.deltaTime * 100f
            );
        }
    }
}

This script demonstrates several important patterns: caching the Rigidbody, separating input reading (Update) from physics (FixedUpdate), applying manual drag to create responsive-feeling deceleration, and handling rotation in LateUpdate to avoid conflicts with the physics step.

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. This causes the Rigidbody to angularly accelerate without any translation (unless combined with forces).

rb.AddTorque(Vector3 torque, ForceMode mode = ForceMode.Force);

The torque vector follows the right-hand rule: if you point your right thumb along the vector, your fingers curl in the direction of rotation. So a torque of Vector3.up spins the object counterclockwise when viewed from above; -Vector3.up spins it clockwise.

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>();
        // Increase Unity's default angular velocity cap (default is 7 rad/s)
        rb.maxAngularVelocity = maxAngularVelocity;
    }

    private void FixedUpdate()
    {
        float input = Input.GetAxis("Vertical");

        // Apply torque around the wheel's local X axis
        rb.AddRelativeTorque(Vector3.right * input * torqueStrength, ForceMode.Force);
    }
}

Note rb.maxAngularVelocity — Unity caps angular velocity at 7 rad/s by default to prevent simulation instability. If your wheel or spinning object needs to go faster, raise this limit explicitly.

AddForceAtPosition()

There is a third force method worth knowing: rb.AddForceAtPosition(force, worldPosition). This applies a force at a specific world-space point on the Rigidbody rather than at its center of mass. If the point is off-center, the force will produce both translation and rotation — exactly like pushing a real object off its center. This is perfect for simulating things like wind pushing on one side of a sail, or a rocket thruster mounted on the edge of a ship.

// 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 Again

Let's rebuild the classic Unity Roll-A-Ball tutorial from scratch using everything in this chapter. The ball rolls on a plane, collects pickup objects, and displays a score.

Project Structure

  • A plane at the origin — scale (2, 1, 2) for a comfortable play area
  • A sphere at (0, 0.5, 0) with a Rigidbody — this is the player ball
  • 8–12 small cube "collectibles" placed around the plane, each with a trigger collider and tag "Pickup"
  • A UI Text element for the score

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;

// Attach to each collectible cube — makes them spin visually
public class PickupRotate : MonoBehaviour
{
    [SerializeField] private float rotateSpeed = 90f;

    private void Update()
    {
        // Non-physics rotation: purely visual, no Rigidbody needed
        transform.Rotate(Vector3.up, rotateSpeed * Time.deltaTime);
    }
}

The collectible rotation uses transform.Rotate() rather than physics — it is purely visual and does not need to interact with the physics engine. This is a good pattern: use physics only where it is genuinely needed, and Transform-based manipulation for purely cosmetic effects.

Putting It All Together: Physics Design Principles

Before moving to the next chapter, let's summarize the key principles you have learned:

  • Cache your Rigidbody. Always store the reference in Start(). Never call GetComponent<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 that collision callbacks alone cannot handle cleanly.

Unity3DPhysicsAddForceRigidbodyForceModeFixedUpdateC#

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