matter-js icon indicating copy to clipboard operation
matter-js copied to clipboard

Properly attaching user data / metadata to a body when using TypeScript

Open b3nk4n opened this issue 2 years ago • 2 comments

Hello!

First of all, thank you for this great library! ❤️ I enjoy playing around with it so far.

Used dependencies

  • "matter-js": "^0.18.0"
  • "@types/matter-js": "^0.17.7"

Background

I'm coming from a Box2D background. And in my previous games, I heavily took advantage of the capability to attach userData to each body, for use-cases such as:

  • assigning a type
  • giving direct access to the model-class / sprite owning this body
  • theoretically one could even store all the relevant fields of a game-object in there

So far I used the options one can pass when creating a Body, such as:

export default class SomeGameObject {
  private readonly _body: Matter.Body
  private readonly _markedForDeletion: boolean
  
  constructor(x: number, y: number) {
    this._body = Matter.Bodies.rectangle(x, y, 16, 16, {
        inertia: Infinity,
        restitution: 0.8,
        isSomeGameObject: true, // <-- custom boolean type check
        instance: this          // <-- custom user data
    })
  }

  update(delta: number): void {
    // ...
  }

  markDelete(): void {
    this._markedForDeletion = true
  }

  get markedForDeletion() {
    return this._markedForDeletion
  }

  get body() {
    return this._body
  }
}

And then this data can for example be used when iterating through all bodies to update them in the game controller:

Matter.Composite.allBodies(this._engine.world).forEach(body => {
  if (body.isSomeGameObject) {
    body.instance.update(delta)

    const collidesWithSomethingElse = true // some pseudo collision check
    if (collidesWithSomethingElse) {
      body.instance.markedForDeletion()
    }
  }
})

This works fine 🙃 However, after enabling typescript type definitions using "@types/matter-js": "^0.17.7", my custom properties are violating the interface contract of IBodyDefinition.

From reading the docs, what I could figure out that instead of using a type specific isXXX boolean flag, I could use the IBodyDefinition#type field. But I'm not sure why to use for other general custom user data, e.g. provided as a generic object, or anything of type any.

I've also seen IBodyDefinition.plugin, but the word reserved in the docs make me skeptical whether they are intended for something completely else which I should not fiddle around:

An object reserved for storing plugin-specific properties.

Questions

  1. What is the recommended way to store user-data (metadata, custom properties, ...) as part of the body?
    • Out of intuition, I would have been looking for something like userData, data or metadata.
    • The TypeScript definition file of Phaser has a BodyDefinition#gameObject field which is internally used for that. I'm however using raw matter-js. And am not sure whether it is a good idea to use Phaser's phaser/matter.d.ts instead.
  2. Or would you discourage to store or reference user-data as part of the body?
  3. Can I safely use plugin?
  4. Or do you have a hint or workaround that one at least would not need to add @ts-ignore in various places.

If this is a missing capability, or is only possible with a workaround, I'm happy to contribute to this project if you agree this would be something valuable.

Thx for your help in advance!

b3nk4n avatar Jun 25 '22 09:06 b3nk4n

From reading the docs, what I could figure out that instead of using a type specific isXXX boolean flag, I could use the IBodyDefinition#type field.

I just tried that, and it looks like when I use anything else than the default value of type: "body" causes that these objects are not updated anymore when updating the simulation using Matter.Engine.update(this.engine, delta). So is the type field also meant to be not modified by the user?

b3nk4n avatar Jun 25 '22 22:06 b3nk4n

I think definitely use your own wrapper object with a property e.g. entity.body and entity.someFlag, especially if your project is strictly typed, but for a back reference you can use something like body.plugin.myGame = { instance } though you probably want to do this after Body.create(options) since this does a deep clone of the options.

The body.type property is definitely reserved for internal use. The typescript bindings are a separate project that is maintained by users, so you might want to check in with them too if the above isn't working!

liabru avatar Jul 13 '22 13:07 liabru

Thank you for your reply. That answered my question.

b3nk4n avatar Sep 21 '22 20:09 b3nk4n