How to Create Rotating Obstacles in Matter.js

This guide explains how to construct dangerous, rotating obstacles such as spinning spiked clubs or maces using the Matter.js 2D physics engine. You will learn how to design a multi-part compound body for the weapon, anchor it in place using constraints, apply constant angular velocity to keep it spinning, and detect player collisions for hazard interactions.

1. Constructing the Compound Body

A spiked club consists of multiple shapes: a shaft, a main club head, and several sharp spikes. In Matter.js, you can combine multiple overlapping shapes into a single rigid body using Body.create with the parts property.

const { Bodies, Body, Composite } = Matter;

// Create individual parts
const handle = Bodies.rectangle(400, 300, 20, 160, {
  render: { fillStyle: '#8B5A2B' }
});

const head = Bodies.circle(400, 200, 35, {
  render: { fillStyle: '#4A4A4A' }
});

// Create four spikes around the head
const spikeTop = Bodies.polygon(400, 155, 3, 15, { angle: 0 });
const spikeBottom = Bodies.polygon(400, 245, 3, 15, { angle: Math.PI });
const spikeLeft = Bodies.polygon(355, 200, 3, 15, { angle: -Math.PI / 2 });
const spikeRight = Bodies.polygon(445, 200, 3, 15, { angle: Math.PI / 2 });

// Combine parts into a single compound body
const spikedClub = Body.create({
  parts: [handle, head, spikeTop, spikeBottom, spikeLeft, spikeRight],
  label: 'hazard'
});

// Set the pivot point near the bottom of the handle
Body.setCentre(spikedClub, { x: 400, y: 360 }, true);

Composite.add(world, spikedClub);

2. Pinning the Obstacle to the World

To make the club rotate around a fixed pivot without falling or drifting due to gravity, attach it to an anchor point using Constraint.create.

const { Constraint } = Matter;

const pivot = Constraint.create({
  pointA: { x: 400, y: 360 }, // Fixed world coordinates
  bodyB: spikedClub,
  pointB: { x: 0, y: 80 },     // Relative offset to the club's pivot
  stiffness: 1,
  length: 0
});

Composite.add(world, pivot);

3. Driving the Rotation

Matter.js physics can naturally slow an object down over time due to friction and air resistance. To maintain a constant spin, you can continuously enforce an angular velocity or update the body's rotation before every engine update tick.

Using Events.on with the beforeUpdate event ensures predictable, continuous rotation:

const { Events } = Matter;

const rotationSpeed = 0.05; // Adjust for faster or slower rotation

Events.on(engine, 'beforeUpdate', () => {
  // Method A: Maintain dynamic angular velocity
  Body.setAngularVelocity(spikedClub, rotationSpeed);

  // Method B: Alternatively, directly advance the angle for a kinematic hazard
  // Body.setAngle(spikedClub, spikedClub.angle + rotationSpeed);
});

To eliminate resistance and ensure smooth rotation if using setAngularVelocity, set the body's friction properties to zero:

spikedClub.frictionAir = 0;
spikedClub.friction = 0;

4. Detecting Collisions with the Hazard

Use the collisionStart event to detect when a player or other dynamic object hits the spinning obstacle. Checking the label property allows you to apply damage, knockback, or game-over triggers.

Events.on(engine, 'collisionStart', (event) => {
  event.pairs.forEach((pair) => {
    const { bodyA, bodyB } = pair;

    if (bodyA.label === 'hazard' || bodyB.label === 'hazard') {
      const target = bodyA.label === 'hazard' ? bodyB : bodyA;

      if (target.label === 'player') {
        // Trigger damage or apply knockback
        Body.applyForce(target, target.position, {
          x: (target.position.x - 400) * 0.005,
          y: -0.05
        });
      }
    }
  });
});