Maximum Safe Projectile Speed in Matter.js
This article explains the maximum safe speed for projectiles in a standard Matter.js physics engine setup, detailing why high-speed objects phase through barriers and how to calculate safe velocity limits. Because Matter.js uses discrete collision detection by default, the safe speed limit is not a fixed universal constant; instead, it is directly determined by your simulation frame rate and the physical dimensions of the projectile and target colliders.
The Discrete Collision Limit (Tunneling)
Matter.js relies on discrete collision detection. In each update cycle (step), the engine calculates an object's new position based on its velocity and immediately checks for overlaps with other bodies. It does not trace the path between the old position and the new position.
If an object travels fast enough to move completely through an obstacle within a single frame, the engine never detects an overlap. This artifact is known as "tunneling." Consequently, the maximum safe speed is the threshold where an object cannot jump over another collider between two consecutive updates.
The Safe Speed Formula
To prevent tunneling without third-party continuous collision detection (CCD) plugins, an object's displacement per update must be smaller than the minimum thickness of the barrier it is colliding with plus the size of the projectile itself.
The mathematical condition for absolute safety is:
\[\text{Displacement Per Step} < \text{Target Thickness} + \text{Projectile Thickness}\]
Expressed in terms of velocity:
\[V_{\text{safe}} < \frac{T_{\text{target}} + T_{\text{projectile}}}{\Delta t}\]
Where:
- \(V_{\text{safe}}\) is the speed in pixels per second.
- \(T_{\text{target}}\) is the thickness of the obstacle in pixels along the trajectory.
- \(T_{\text{projectile}}\) is the depth or radius of the moving projectile in pixels.
- \(\Delta t\) is the time elapsed per frame in seconds (typically \(1/60 \approx 0.0167\text{ s}\) for a standard 60 Hz display).
Practical Baseline in a Default Setup
In a default browser configuration running at 60 frames per second (\(\Delta t \approx 16.67\text{ ms}\)):
- Small Projectile (10px wide) against a standard wall (20px wide): The maximum combined thickness is 30 pixels. The maximum speed must stay strictly below \(30 \text{ px} / 0.0167 \text{ s} \approx 1800 \text{ px/second}\) (or 30 pixels per frame).
- Conservative Rule of Thumb: To guarantee stable collision resolution and bounce physics without penetration glitches, an object should not move more than its own radius or half the wall's thickness in a single frame. In practice, keeping projectile displacement below 10 to 15 pixels per frame (600 to 900 pixels per second) provides reliable collisions in standard environments.
How to Increase Safe Projectile Speeds
If your game mechanics require speeds higher than standard discrete limits allow, you can increase the threshold using several techniques:
- Substepping: Instead of running
Engine.update(engine, 1000 / 60)once per frame, execute the update multiple times per frame with a smaller delta (e.g., three updates of \(\approx 5.55\text{ ms}\)). This reduces the per-step displacement by two-thirds without altering visual scale. - Thicker Colliders: Increase the thickness of stationary walls while keeping their visible render sprites thin. Alternatively, place invisible buffer sensors around boundaries.
- Swept Volumes and Raycasting: For extreme velocities (such as bullets or laser beams), bypass rigid body translation entirely. Cast a ray or generate a temporary polygon covering the swept area from the previous frame to the current frame to detect impacts before updating positions.