How do you access a body by its label in MatterJS?

Viewed 102

This question was asked here but no answer was given.

To clarify the question, once a body is created, it is stored in the World/Composite.

The question is, given a body created like so:

Bodies.rectangle(0, 1000, 0, 100, {
                isStatic: true,
                label: "floor",
                friction: 0,
                render: {
                    fillStyle: 'light blue'
            },
})

How do you access the body using the label? (Assuming the body is added to the world)

2 Answers

The simple answer is no, there is no built in function that allows you to retrieve a body by its label. The reason is because labels aren't unique and retrieving a body by label can take really long. Imagine if there were thousands of bodies...

In any case, if you still want to retrieve a body by its label you can do this to search for the body in linear time:

// retrieve all bodies in the world and filter on label
// returns an array containing all bodies that have that label
function getBodiesByLabel(label, world) {
  return Composite.allBodies(world).filter(body => body.label === label)
}

const floorBodies = getBodiesByLabel('floor')

floorBodies.forEach(floorBody => console.log(floorBody))

If you only have a couple of bodies to look through, it's not that bad.

Source: MatterJS GitHub Question Credit: grantjenkins on GitHub

The answer by gfdb works, but it involves a linear search over all bodies for each label lookup, O(n). As I mentioned in a couple of comments, MJS does offer a label property for convenience, but doesn't purport to be a holistic entity management solution; it's just a physics engine library. There doesn't seem to be any backing data structure for labels, and that's probably a good thing. Leaning heavily on this single property seems to be an antipattern, expecting MJS to handle entity management when it's not intended to.

So, the general approach when using MJS standalone is to roll your own application-specific entity management solution that meets your needs, or use an opinionated framework like Phaser that offers an off-the-shelf solution.

A couple of common approaches are:

  1. Use a composition pattern: write your own classes and keep fields for MJS bodies as implementation details (probably OK to be tightly-coupled for most use cases), along with whatever other data you need for your app. Group in data structures as needed and optionally inherit from your base classes as in normal OOP.

    class Enemy {
      constructor(x, y, width, height, opts) {
        this.body = Matter.Bodies.rectangle(x, y, width, height, opts);
        this.kills = 0;
        this.cooldown = 30;
        // ... other important data that isn't necessarily MJS-related
      }
    
      update() {...}
      draw() {...}
      ...
    }
    
    const entities = {
      enemies: [new Enemy(...), ...],
      walls: [...],
      ...
    };
    
  2. Use the bodies directly, but put them into an object of arrays organized by label:

    const bodiesByType = {
      walls: [Matter.Bodies.rectangle(), ...],
      enemies: [Matter.Bodies.rectangle(), ...],
      players: [Matter.Bodies.rectangle(), ...],
      ...
    };
    

    ... or even skip the object and look them up by loose variable names player, walls, etc.

  3. Use gfdb's approach for simple use cases where the above options might be premature optimization (although I don't think option 2 is much work).

Related