Game Engineering • Optimization

Writing Custom Physics vs using Box2D: The Developer's Guide

Written by Sudhishkumar K • Updated Mar 2026 • 18 Min Read

There is a moment in every game developer's life when they realize the engine is fighting them. You are building a tight, grid-based puzzle game or a pixel-perfect platformer (like Celeste or Super Meat Boy), but the player character keeps getting stuck on flat walls. Sometimes, a box lands on the floor and jitters for a few frames before settling.

This is the "Black Box" problem. Unity (Box2D/PhysX), Godot, and Unreal provide incredible, general-purpose physics simulations. They are designed to handle thousands of interactions, ragdolls, and complex friction models. But for a specific subset of games—particularly 2D platformers and puzzle games—they are simply too much.

At Boomie Studio, we often strip out the default Rigidbody2D components in favor of custom-written collision logic. In this guide, we will explore why you should consider writing your own physics, the math behind AABB (Axis-Aligned Bounding Box) collision, and how to handle the dreaded "Tunneling Effect."

1. The Case Against General Purpose Engines

Why would you reinvent the wheel? Box2D has been in development for years by geniuses. Surely you can't write something better?

You aren't writing something better; you are writing something simpler. General engines work on Floating Point Approximation. They prioritize simulation stability over precision.

Box2D (Unity Default) Custom Character Controller
Non-Deterministic: The same input might yield slightly different results on different processors due to floating point math variations. Deterministic: You control the math. If you move x + 1, it moves exactly 1 unit, every time.
"Floaty" Feel: Inertia and momentum are applied automatically, often making controls feel sluggish. "Snappy" Feel: You define the acceleration curves. Input is instant.
Overhead: Calculates rotation, angular drag, and friction even if you don't need them. Lean: Calculates only what you code (usually just X/Y position).

2. The Foundation: AABB Collision

If your game does not require rotating hitboxes (e.g., a crate is always a square aligned with the grid), you should use AABB.

The math is elegant in its simplicity. We are checking if the gap between two rectangles on the X-axis or Y-axis has closed.


public struct AABB {
    public Vector2 min; // Bottom-Left corner
    public Vector2 max; // Top-Right corner

    public bool Overlaps(AABB other) {
        // If one is to the left of the other
        if (this.max.x < other.min.x || this.min.x > other.max.x) return false;
        
        // If one is above the other
        if (this.max.y < other.min.y || this.min.y > other.max.y) return false;

        return true; // Overlap detected
    }
}
            

3. The "Tunneling" Problem and Raycasting

The code above works for static overlap checks. But games move. If your player is moving at 100 pixels per frame, and a wall is 10 pixels thick, the player will teleport through the wall between frame A and frame B. This is called Tunneling.

To solve this, we don't just check "Are we inside?"; we check "Will we be inside?" This is where Raycasting comes in.

The Raycast Controller Strategy

Instead of moving the object and then checking for collisions (which causes objects to get stuck inside walls), we cast rays outwards from the object's edges before moving.

If the ray hits a wall 5 units away, but our velocity says we want to move 10 units, we clamp our movement to 4.99 units. We stop exactly before the wall.


// Simplified Raycast Logic
void Move(Vector2 velocity) {
    float directionX = Mathf.Sign(velocity.x);
    float rayLength = Mathf.Abs(velocity.x) + skinWidth;
    
    // Cast rays from the leading edge
    for (int i = 0; i < horizontalRayCount; i++) {
        Vector2 rayOrigin = (directionX == -1) ? raycastOrigins.bottomLeft : raycastOrigins.bottomRight;
        rayOrigin += Vector2.up * (verticalRaySpacing * i);
        
        RaycastHit2D hit = Physics2D.Raycast(rayOrigin, Vector2.right * directionX, rayLength, collisionMask);

        if (hit) {
            // We hit a wall! Set velocity to the distance to the wall
            velocity.x = (hit.distance - skinWidth) * directionX;
            rayLength = hit.distance; // Shorten subsequent rays
        }
    }
    transform.Translate(velocity);
}
            

What is "Skin Width"?

You'll notice the variable skinWidth in the code. In floating-point physics, you never want an object to be perfectly flush with a wall (distance 0.0000). This causes flickering and division-by-zero errors.

We intentionally keep objects floating a microscopic distance (e.g., 0.015 units) away from surfaces. This invisible buffer is the "Skin."

4. Handling Slopes (The Hard Part)

Box2D handles slopes automatically, but often poorly (the player might bounce down a ramp). In custom physics, you have to do the trigonometry yourself.

When a horizontal ray detects a slope:

  1. Get the angle of the surface normal.
  2. If the angle is walkable (e.g., < 45 degrees), we are not hitting a wall, we are climbing.
  3. Convert the X velocity into a vector that matches the slope angle.

// New Velocity = oldVelocity.x * Cos(angle) on X, and oldVelocity.x * Sin(angle) on Y.

This ensures that moving constantly to the right maintains a constant speed, whether on flat ground or a 30-degree ramp. No slowing down on accents, no speeding up on descents (unless you program it to!).

5. Determinism & Multiplayer

If you are building a competitive puzzle game or a fighting game with "Rollback Netcode" (GGPO), Custom Physics is almost mandatory.

In a deterministic simulation, if I send the input "Right Arrow" on Frame 100, the game state on Frame 101 must be bit-identical on every machine in the world. Box2D/PhysX does not guarantee this across different CPU architectures.

By writing your own integer-based or fixed-point math collision system, you ensure that the game state can be rewound and replayed perfectly, which is the backbone of modern lag compensation.

6. When Should You Stick to Box2D?

I don't want to demonize the built-in engines. They are tools, and excellent ones. You should use Unity's built-in physics if:

Conclusion

Writing a custom physics engine is a rite of passage for game engineers. It gives you absolute control. When the player complains that a jump "didn't feel right," you don't have to tweak a generic "Friction" slider and hope for the best—you can look at line 45 of your code and change exactly how gravity is applied per frame.

For the puzzle mobile games we build at Boomie Studio, that precision is what separates a 3-star game from a 5-star game.


Frequently Asked Questions (FAQ)

Q: Is custom physics better for performance?
A: Generally, yes. A custom AABB system checks far fewer variables than a full rigid-body simulation. On older mobile devices, avoiding the overhead of the PhysX integration can save significant battery and CPU time.

Q: How do I handle "bounciness" without Box2D?
A: You implement a restitution variable. When a collision is detected on the Y-axis (hitting the floor), instead of setting velocity.y = 0, you set velocity.y = -velocity.y * bounciness. If bounciness is 0.5, they rebound with half the speed.

Q: Where can I find a good starting point code-wise?
A: I highly recommend looking at Sebastian Lague's 2D platformer series on GitHub. It is the gold standard for Unity raycast controllers and served as the inspiration for our internal tools at Boomie Studio.