WALLS THAT STOP BULLETS
IN THREE.JS

By Shane Brumback · · Free Three.js tutorial

To make a wall block bullets and players in Three.js, wrap each wall in an invisible box collider, give that box geometry a three-mesh-bvh bounds tree so fast raycasts can hit it, and run a per-frame push-out that keeps the player outside the box. This tutorial pulls the pattern from Super Soldier Battle's real collision code.

Invisible box colliders, BVH-accelerated raycasts, and a closest-point push-out — the three ideas that make the new bunkers solid, simplified so you can drop them into your own game.

Invisible box colliders and BVH raycasts blocking bullets at a wall in Three.js

SEE IT LIVE

This collision code runs in Super Soldier Battle. Take cover in a bunker and watch the walls block shots and players for real. It's free and runs right in your browser — no download.

Play the Game

The blue and red soldiers drifting behind this page are a decorative preview — the real battle is in the game.

Introduction

Super Soldier Battle's new walk-in bunkers only work if their walls are solid: players can't walk through them, and bullets can't pass through them. This tutorial shows how the game does both with three ideas from its actual source code (models/v2/instancing.js): invisible box colliders placed around each bunker piece, a BVH bounds tree so fast raycasts can hit those boxes, and a small push-out function that keeps the player outside walls every frame.

The visible bunker models are detailed and expensive to test against. The colliders are plain boxes: cheap to raycast and easy to reason about. Keeping the two separate is the main trick. Read the dev log for the gameplay side: Walk-In Bunkers and Beaches Land in Super Soldier Battle.

What you should know first: this is an intermediate build. You'll get the most out of it if you've already set up a basic Three.js scene (a renderer, a camera, a mesh, and a Raycaster). Bullet blocking uses the three-mesh-bvh library. The game runs on Three.js r128, which is why some excerpts use BoxBufferGeometry.

What You'll Learn

  • Building invisible box colliders from data (center, size, rotation) and positioning them with a matrix
  • Why visible = false beats an invisible material for performance (fewer draw calls)
  • Why firstHitOnly raycasts with three-mesh-bvh need computeBoundsTree(), or they return no hits
  • Separating "walls" (block sideways) from "floors" (you can stand on them)
  • Push-out collision: moving a player out of a rotated box using its closest point

Prerequisites

Basic Three.js (scene, mesh, Raycaster) and JavaScript. Bullet blocking uses the three-mesh-bvh library. The game runs on Three.js r128, which is why the excerpts use BoxBufferGeometry; the runnable example below uses a current CDN build with BoxGeometry.

Frequently Asked Questions

Why are my three-mesh-bvh raycasts returning no hits?

If you set firstHitOnly = true with the accelerated raycast, the geometry needs a bounds tree. Call geometry.computeBoundsTree(). Super Soldier Battle had exactly this bug on its wall colliders.

Do invisible meshes cost draw calls in Three.js?

Yes, if you hide them with an invisible material, because they still submit a draw call. Set mesh.visible = false instead. It removes the mesh from the render list, and raycaster.intersectObjects() still hits it.

Should I raycast against my detailed 3D models for collision?

Usually not. Simple invisible box colliders are much cheaper and more predictable. Super Soldier Battle collides against boxes placed around each bunker piece, not the visible models.

How do I stop the player from walking through rotated walls?

Transform the player into the wall's local space, clamp to find the closest point on the box, and push the player out along that direction to their radius. Then rotate the push back to world space.

Where can I see this code in action?

In Super Soldier Battle's new bunkers, free at supersoldierbattle.com. The walls block both players and shots.

How It Works

  1. One shared box geometry is reused for every collider and scaled per wall, so hundreds of colliders cost almost nothing to create.
  2. Each collider is hidden with visible = false — not an invisible material — so it leaves the render list entirely while still being raycastable.
  3. The shared geometry gets a BVH bounds tree once via computeBoundsTree(), which is what makes firstHitOnly raycasts actually return hits.
  4. Walls and floors are separated by a kind flag: floors go in the ground-raycast list you stand on; walls go in the bullet / line-of-sight list and the sideways push-out list.
  5. The gun raycaster tests walls and players together, so the nearest hit wins — a wall between you and a target blocks the shot and spawns an impact effect.
  6. A per-frame push-out rotates the player into each box's local space, finds the closest point on the box, and nudges the player back out to their radius, with a small step-up tolerance for ramps.

That's the whole system from the player's point of view. The detailed code breakdown and the full copy-paste example are below — sign in free to unlock them.

What To Use It For

  • Author colliders as data (center, size, rotY, kind) next to each model piece — never collide against the detailed mesh.
  • Build them from one shared box geometry with visible = false to keep draw calls flat.
  • Call computeBoundsTree() on that geometry once if you use three-mesh-bvh with firstHitOnly.
  • Put walls in your bullet / line-of-sight list, and floors in your ground list, so you never stand on a wall.
  • Run pushOutOfWalls(player.position) every frame after movement, in a few passes for tight corners.

SEE THESE WALLS IN A REAL GAME

This collision code ships in Super Soldier Battle's new walk-in bunkers. Read the dev log on the bunkers and beaches update to see the gameplay side of the same feature.

Walk-in bunkers and beaches in Super Soldier Battle Read the Bunkers Dev Log