PhaserMatterCollisionPlugin: can't apply force to collided object

I’m trying to build hitboxes which are active only during an animation. To achieve this, I created a compound body with sensors for each hitbox. When a key is pressed, these hitboxes ‘turn on’, which is modelled by listening for a collision event between the sensor and objects in the world. I manage the collision detection via the PhaserMatterCollisionPlugin plugin (link), then apply the force to the collided object during callback. However, the force is not being applied at all, and I don’t understand why. Any recommendations?

P.S.: I’ve seen this suggestion for using collision categories to achieve dynamic hitboxes. I will revert to that solution if I need to. However, I would like to understand what I’m doing wrong in my use of PhaserMatterCollisionPlugin.

Here is my code so far:

Define character with hitboxes
/*
Code snippet from the constructor of my `Player` class.
`Player` is instantiate during `create()`
*/
this.sprite = scene.matter.add.sprite(0, 0, spriteKey, 0);

const { Body, Bodies } = Phaser.Physics.Matter.Matter;  // native Matter modules
const { width: w, height: h } = this.sprite;
const mainBody = Bodies.rectangle(w / 2, h / 2, w * 0.3, h * 0.6, { chamfer: { radius: 5 } });
// ^ model as rectangular body with rounded corners (chamfers)
// Regions outside of mainBody will have sensors attached to them, but no collision hitboxes.
this.sensors = {
    bottom: Bodies.rectangle(w / 2, 0.8 * h, w * 0.25, 2, { isSensor: true }),
    left: Bodies.rectangle(0.33 * w, 0.5 * h, w / 16, h / 2, { isSensor: true }),
    right: Bodies.rectangle(0.66 * w, 0.5 * h, w / 16, h / 2, { isSensor: true }),
};
// We also create sword hitboxes which extend outwards from the main body. These hitboxes
// will only be activated during the 'swing' animation.
this.hitboxes = {
    swordLeft: Bodies.circle(0.23 * w, 0.6 * h, 9, { density: 0, isSensor: true }),
    swordRight: Bodies.circle(0.70 * w, 0.6 * h, 9, { density: 0, isSensor: true }),
}
// Put all parts of the body together
const compoundBody = Body.create({
    parts: [
        mainBody,
        this.sensors.bottom,
        this.sensors.left,
        this.sensors.right,
        this.hitboxes.swordLeft,
        this.hitboxes.swordRight
    ],
    frictionStatic: 0.5,
    frictionAir: 0.02,
    friction: 0.1
});
this.sprite.setExistingBody(compoundBody)
Define object I want to collide with
/*
Snippet inside `create()`
/*
this.bomb = this.matter.add.sprite(50, 32, 'bomb', 0);
this.bomb.setBody({
  type: 'polygon',
  sides: 6,
  radius: 8
});
this.bomb.ignoreGravity = true;
this.bomb.setBounce(1);
this.bomb.setVelocity(Phaser.Math.Between(-5, 5), 3);
this.bomb.setFriction(0, 0, 0);
Apply force on collision
export class SwingState extends State {
    enter(scene, player) {
        const sprite = player.sprite; // short-hand
        this.swordHitbox = (player.direction === 'left') ? player.hitboxes.swordLeft : player.hitboxes.swordRight;
        this.swingForce = 1;
        this.swingAngle = 30;

        sprite.setVelocityX(0);
        sprite.anims.play(`swing-${player.direction}`);
        
        // Check for collision with sword hitbox
        scene.matterCollision.addOnCollideActive({
            objectA: this.swordHitbox,
            objectB: scene.bomb,  // matter.sprite which I want to apply force to
            callback: this.hitWithSword,
            context: this
        });
        
        sprite.once('animationcomplete', () => {
            // Stop tracking collision
            scene.matterCollision.removeOnCollideActive({
                objectA: this.swordHitbox,
                objectB: scene.bomb,
                callback: this.hitWithSword,
                context: this
            });
            // ... transition to 'idle' state
        });
    }

    hitWithSword({ bodyA, gameObjectA, bodyB, gameObjectB, pair }) {
        gameObjectB.applyForce({
            x: Math.cos(this.swingAngle) * this.swingForce,
            y: Math.sin(this.swingAngle) * this.swingForce
        });
        // Nothing happens here!
    }
}

Found the the root cause & the solution to my problem:

  • Cause: matterjs clears forces right after events are emitted, hence why the forces aren’t applied.
  • Solution: cache forces/set flags when collisions are detected, then apply those forces in the next beforeUpdate event.

Example:

  1. Check if there is a collision between objects swordHitbox and scene.bomb:
        const unsubscribe = scene.matterCollision.addOnCollideActive({
            objectA: swordHitbox,
            objectB: scene.bomb,
            callback: eventsData => this.hitWithSword(player, scene.bomb), 
            context: scene
        });
  1. If a collision is detected, cache the collided objects (I’m caching them in properties of my player instance, the movable character):
    hitWithSword(player, bomb) {
        // Update flags (enabling player to swing at item) and
        // cache which items are affected
        player.canSwingBomb = true;
        player.bombsToSwing.push(bomb);
    };
  1. Send out a beforeUpdate event which applies the forces to every cached object:
    scene.matter.world.on('beforeupdate', this.swingBomb, this);
    /* ... */
    swingBomb() {
        for (let i=0; i < this.bombsToSwing.length; i++) {
            const bomb = this.bombsToSwing[i];
            const sign = (this.direction === 'left') ? -1 : 1;
            bomb.applyForce({
                x: sign * this.swingForce * Math.cos(this.swingAngle),
                y: -this.swingForce * Math.sin(this.swingAngle)
            });
        };
        // Reset flags and array
        this.canSwingBomb = false;
        this.bombsToSwing = [];
    }