How to create object layer from tiled tile objects

I am using Tiled to create a map with multiple layers. One of those layers is an object layer with tile objects. My intent is for those objects to have special properties so that on, say, a collision, I can take an appropriate action (such as loading the appropriate scene when the player’s sprite collides with a door).

For the life of me, I cannot figure out how to do this. The closest I can get is using createFromObjects, but I seem to be required to specify the image I want to use (which isn’t what I want…I have multiple tilesets and am trying to keep this generic, so I am trying to use whatever tile in whatever asset was used in Tiled). I’d rather not encode all of the possible tiles that could have special properties by id; instead, I want to iterate through the objects, and render the tile as it shows in Tiled. Right now, I have something like this within my create:

const gids = {}
const group = this.add.group()
layer = map.getObjectLayer(name)
layer.objects.forEach(object => {
  const {gid, id} = object
  //I do this check because createFromObjects will
  //have already created objects once I use the same gid.
  if (!gids[gid]) { 
    const objects = map.createFromObjects(name, gid)
    objects.reduce((group, sprite) => {
      group.add(sprite)
      this.physics.world.enable(sprite)
      sprite.body.setImmovable()
      return group
    }, group)
  }
})

In another section of my code, I set the collisions between the player and the objects, and that all works as expected. My problem right now is only with rendering the tile. I’m assuming I just need to pass the right secret sauce to createFromObjects, but I have two constraints:

  1. It seems like I need to specify a key to a texture asset. As far as I can tell, I don’t know the asset, but perhaps I can find the asset from the gid?
  2. I need to tell it which actual tile within the asset to render.

Honestly, I would have assumed there was a simpler way to do this…my understanding is that the gid uniquely identifies a tile across all tilesets within a tilemap, so presumably that alone should be enough for Phaser to render the object with the correct texture. Am I missing something?

I guess the key is in object.type? (e.g. “door”)

Sorry, I should have been clearer. createFromObjects can take a few more arguments, one of which is a spriteConfig. That spriteConfig can take a key parameter, which is the key to the asset. In my case, this isn’t sufficient…the door tile is within a tileset. For example, say I’ve loaded ‘doors’ as an asset with a bunch of different door styles in it. I use one of those tiles as a tile object in Tiled. As-is, if I specify the key “doors”, then the entire asset (all tiles) are rendered there, scaled down to fit within a 32x32 px area.

I’ve never used Tiled, so I’m just guessing here…
Could you log some object.type’s to see if they are different per door style?
If so, you’ll have to find the asset in the tileset.
You can probably only use createFromObjects per type ( a gid is shared by all tile objects with the same graphic).

I suppose I could, but it seems a bit heavy handed to add a property to every tile in every tilset I may want to create objects from that lets me know which key to render. I also still don’t know how, even if I find the right asset key, to render only a specific tile from that asset.

I guess my questions could be simplified to this:

Given a gid, can I identify both the asset key and any other parameters in a spriteConfig object that would let me render the specified tile as an object?

I don’t think there is an asset key. createFromObjects is not meant to lookup the tileset image.
You could use the id (or gid, or gid + id), to do what Phaser does on creating a Layer.
Use getTileTextureCoordinates(tileIndex) on the tileset, create your sprite from that.

There is a key property in the spriteConfig argument. In my above example, I am not using it, but the only way I can get it to know what texture to render is to pass a spriteConfig in. For example:

    const spriteConfig = {key: 'nameOfTileset'}
    const objects = map.createFromObjects('objectLayerName', gid, spriteConfig)

In my case, tiles are 32x32px. createFromObjects will properly find all objects with the specified gid in objectLayerName. It properly determines the x, y, width, and height of the object to place on the map. However, if I don’t specify the key in spriteConfig, I get the Missing Texture image instead. If I specify some asset (such as the "doors" tileset key, I get a scaled down version of the tileset there (not the actual tile I care about).

I’ve been playing, and I can calculate everything I need to get the image (from the gid I can go through the tilesets and find the right tileset, and I can get the x,y of the tile). However, I don’t know what keys to pass in the spriteConfig to actually render that subsection of the tileset image.

I’m not sure how to create the sprite as you describe. I’m assuming that means not using createFromObjects, but I haven’t yet come across an example of creating a sprite from a subsection of an image (i.e., given an image, x, y, width, height, create the sprite).

So your key is the tileset name?
Then just loop all tilesets for containsTileIndex(tileIndex).

@Milton - First, thanks for the help, I definitely appreciate it. I think I’m not being clear in my problem. At this point, I am able to:

  1. Find the right tileset
  2. Determine the x,y coordinates of the tile in the tileset.
  3. Create an object that is the correct position/width/height via createFromObjects

What I haven’t figured out is how to put all of that together to display the right texture (crop the tilset image properly to render the tile I want). This seems like something that should be super simple and I’m probably just making it unnecessarily complicated.

As an update, here is what I have so far:

const gidToSpriteConfig = (gid, map) => {
  const tilesets = map.tilesets
  const nextIndex = tilesets.findIndex(t => t.firstgid > gid)
  const tileset = tilesets[nextIndex-1]
  const {name, tileWidth, tileHeight, tileMargin, tileSpacing, rows, columns} = tileset
  const w = tileWidth+tileSpacing
  const h = tileHeight+tileSpacing
  const {x, y} = tileset.getTileTextureCoordinates(gid)
  const tx = (x-tileMargin)/w
  const ty = (y-tileMargin)/h
  //I calculated the frame in hopes i could pass that, but the tileset images aren't loaded
  //with frame info, so it doesn't appear to work as expected/hoped Although maybe
  //I can load the image differently such that the tiles are frames?
  const frame = ty*rows + tx
  console.log(frame, tx, ty)
  console.log(tileset)
  return {key: name, crop: {x, y, tileWidth, tileHeight}}
}

  create() {
    const map = this.make.tilemap({key: "map"})
    const tilesets = TILESETS.map(params => {
      return map.addTilesetImage(...params)
    })
 
    //LAYERS is a configuration array telling me what layers I expect from the
    //tilemap (by convention) with configs on how to properly configure that layer
    const layers = LAYERS.map(([name, collide, type='tile'],i) => {
      console.log(name, collide)
      let layer
      if (type === 'tile') {
        layer = map.createDynamicLayer(name, tilesets, 0, 0)
        if (collide) layer.setCollisionByProperty({collides: true})
        layer.setDepth(i)
      }
      if (type === 'object') {
        const gids = {}
        const group = this.add.group()
        layer = map.getObjectLayer(name)
        layer.objects.forEach(object => {
          console.log(object)
          console.log(map.tilesets)
          const {gid, id} = object
          if (!gids[gid]) {
            const {key, crop} = gidToSpriteConfig(gid, map)
            const objects = map.createFromObjects(name, gid, key)
            objects.reduce((group, sprite) => {
              const {x, y, tileWidth, tileHeight} = crop
              console.log(x, y, tileWidth, tileHeight)
              console.log(sprite)
              group.add(sprite)
              this.physics.world.enable(sprite)
              sprite.body.setImmovable()
              return group
            }, group)
          }
        })
        layer = group
      }
      return layer
    })

Well, I guess you have to create the texture yourself then.
Create a canvastexture, draw the part of getSourceImage that you want to it, and use that as texture.

Hi,
Why do you want to mix tiles and sprites?
I use a lot the object layers with tiled, for enemies, doors, elevators, save points…
But for example, i use the enemy object layer with custom properties in each object, with the key of the enemy, his life, damage, speed, everything i need, so i can have identical enemies with different speed life or damage.
And all this sprites are on an atlas. This give the possibility to animate doors etc…

So, I’ll start off by saying there may well be (and honestly I hope there are) easier ways to do what I’m trying. In this particular case, I don’t actually care about sprites (that’s just an artifact of createFromObjects), I care about actions to take on collision with doors. Each door would load a different scene on collision, and I want to encode that in the properties of the door. Since you cannot specify properties per tile in a tile layer, I am using an object layer. Tiled supports tile objects, which is what I am using. What I would have expected is for Phaser to understand tile objects (tile objects have a gid property, giving Phaser everything it needs to know to render it just like it can render tilemap layers)

In the cases you mention (enemies and such), what you describe certainly makes sense. In this case, I don’t really have a need to animate, at least not right now.

@Milton - Yea, that may be what I end up doing. I also may just leave the object as an invisible rectangle instead of a tile. It just seems odd that this case isn’t natively supported…you would think it would be no different than whatever Phaser does under the hood to render a tilemap, just using parameters from the object layer.

Each Texture has a key. Each Sprite has a texture (or Frame). Layers are not sprites but texture mapping. Tiles don’t have a key in the Texture Manager.
You are trying to mimic a Texture Atlas, which creates Frames in a texture.

this.textures.get('key').add('frame', 0, x, y, width, height);

Now the ‘crop’ is available like:

this.add.image(0, 0, 'key', 'frame')

im just a rank amateur hobbyist so im not sure if what i wrote is readable … maybe this helps towards what you’re looking for :crazy_face:

this.map = this.make.tilemap({key: currentmap});

this.tiles = this.map.addTilesetImage('TM_ofcmain','tiles'); //m hmmm lombi been a while 
this.bottomlayer = this.map.createDynamicLayer(0,this.tiles, 0, 0);//mh 
this.wallslayer = this.map.createDynamicLayer(1,this.tiles, 0,0);// yes, needs layers BUT RT -> doors/objects layers?
this.pickupSlayer = this.map.getObjectLayer('pickupSlayer')['objects'];
this.trapSlayer = this.map.getObjectLayer('trapSlayer')['objects'];

this.wallslayer.setCollisionBetween(1,10);


this.pickupSgroup = this.physics.add.staticGroup();

    this.pickupSlayer.forEach(object => {
    let obj = this.pickupSgroup.create(object.x,object.y, "coin");        
});

this.trapSgroup = this.physics.add.staticGroup();

    this.trapSlayer.forEach(object => {
    let obj = this.trapSgroup.create(object.x,object.y, "grabhand");        
});

this.pickupSgroup.children.iterate(function(child){
    child.setScale(2);
    child.setOrigin(-0.3,1);
    child.body.setOffset(32,-32);
    child.anims.play('rotcoin',true);
});

this.trapSgroup.children.iterate(function(child){
    child.setScale(1);
    child.setOrigin(-0.25,1);
    child.body.setOffset(64,-32);
    child.body.setSize(4,4);
    child.refreshBody();
    child.anims.play('grabtrap',true);
});



    this.physics.add.overlap(this.player, this.pickupSgroup, function(player, coin){

        this.coinget.play();
        this.tweens.add({
            targets: coin,
            y: coin.y - 100,
            alpha: 0,
            duration: 800,
            ease: "Cubic.easeOut",
            callbackScope: this,
            onComplete: function(){
                this.pickupSgroup.killAndHide(coin);
                this.pickupSgroup.remove(coin);
            }
        });

    }, null, this);


    this.physics.add.overlap(this.player, this.trapSgroup, function(player, trap){

        this.dying = true;
        this.player.anims.stop();
        this.physics.world.removeCollider(this.platformCollider);
        this.spike(this.player); //death function anim

    }, null, this);