Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Access to custom properties on inserted tiles #283

Closed
luttje opened this issue Jan 6, 2022 · 3 comments · Fixed by #287
Closed

Access to custom properties on inserted tiles #283

luttje opened this issue Jan 6, 2022 · 3 comments · Fixed by #287

Comments

@luttje
Copy link

luttje commented Jan 6, 2022

First of all: Thanks for the great engine! Loving it so far 👍

Context

I'm creating 'inserted tiles' in Tiled with which the player can interact with their pointer. I'm creating custom properties in Tiled which are succesfully loaded by this plugin.

Sadly when excalibur-tiled builds the tilemap most custom properties on the TiledObjects are not copied to the actor. This is because a generic Actor object is created. From the properties only collisionType and zIndex are used while constructing the actor.

Proposal

To maintain easy access to the custom properties from Tiled I suggest to derive a new class from Actor. E.g: a TiledActor which has one additional property rawTiled: TiledObject which refers to the original Tiled data.

We could then later access the properties easily:

class MyScene extends Scene {
  onInitialize() {
    Resources.Map.addTiledMapToScene(game.currentScene)
    
    this.actors.forEach(actor => {
      if (!(actor instanceof TiledActor))
        return
      
      actor.enableCapturePointer = true

      actor.on('pointerup', (event: PointerEvent) => {
        let object = <TiledObject>actor.rawTiled
        console.log(`Clicked inserted tile with custom property ${object.getProperty<string>('MyProperty')?.value}`)
      })
    })
  }
}

Current solution

The current solution is a bit tedious and has me comparing the position of an actor with the tiled map data:

const TILE_SIZE = 16

class MyScene extends Scene {
  onInitialize() {
    Resources.Map.addTiledMapToScene(game.currentScene)

    const layers: TiledObjectGroup[] = Resources.Map.data.getExcaliburObjects()
    const inserted = layers.flatMap(o => o.getInsertedTiles())

    this.actors.forEach(actor => {
      actor.enableCapturePointer = true

      actor.on('pointerup', (event: PointerEvent) => {
        const targetPos = event.worldPos

        inserted.forEach((object: TiledObject) => {
          const objectY = object.y - TILE_SIZE // origin of tile is at bottom
          const objectStart = vec(object.x, objectY)
          const objectEnd = vec(object.x + (object.width ?? 0), objectY + (object.height ?? 0))

          if (!(targetPos.x >= objectStart.x
            && targetPos.y >= objectStart.y
            && targetPos.x <= objectEnd.x
            && targetPos.y <= objectEnd.y))
            return

          console.log(`Clicked inserted tile with custom property ${object.getProperty<string>('Item')?.value}`)
        })
      })
    })
  }
}

Perhaps I'm overthinking it or missing something. Looking forward to your thoughts.

@eonarheim
Copy link
Member

@luttje Thanks for the kind words!

I agree getting the custom properties from Tiled should be easier, the current state is not ideal for sure. I definitely think this is something that should be exposed to the consuming game code.

Instead of a derived Actor type, how does a new component to hold this data sound? This would make it easy to reuse Tiled properties on any excalibur Entities from Tiled including Actors.

// New component to hold the raw Tiled data
export class TiledObjectComponent extends Component<'ex.tiled'> {
   public readonly type = 'ex.tiled';
   constructor(public tiled: TiledObject) {}
}

// Updating the tiled insert logic https://github.com/excaliburjs/excalibur-tiled/blob/02c122a11f3737928d9d19db3b3b77e6895c6196/src/tiled-map-resource.ts#L167 
const actor = new Actor(...)
actor.addComponent(new TiledObjectComponent(tile));

// Consuming game code
this.actors.forEach(actor => {
  // retrieve the component if it exists on the actor/entity
  const maybeTiled = actor.get(TiledObjectComponent); // only exists on our Tiled created actor/entity
  if (maybeTiled) {
     console.log(maybeTiled.getProperty<string>('Item')?.value);
  }
});

cc @jedeen @kamranayub Thoughts?

@luttje
Copy link
Author

luttje commented Jan 8, 2022

Thanks for the fast response and that bit of code, it really helped me while refactoring my (in hindsight) hugely inefficient code.

It would be nice to see a component like that shipped with this plugin. 👍

@eonarheim
Copy link
Member

No promises, but I might be able to squeeze this into the plugin in the next release (theoretically sometime in Jan) 🤞

eonarheim added a commit that referenced this issue Jan 12, 2022
This PR closes #283

- Adds a new Tiled specific component `TiledDataComponent` which is a holder for the original `TiledObject` data
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants