<div id=instructions>Click to drop a ball. Spikes and lava are sensor tiles.</div>
<footer><div id=version></div></footer>
html, body {
  height: 100%;
}

body {
  margin: 0;
  padding: 0;
  background-color: #111111;
  color: #EEEEEE;
  font-family: Sans-Serif;
}

#instructions {
  position: absolute;
  top: 10px;
  left: 290px;
  padding: 5px;
  background-color: rgba(50, 50, 50, 0.5);
}

#version {
  position: absolute;
  top: 10px;
  left: 10px;
  padding: 5px;
  background-color: rgba(50, 50, 50, 0.5);
}
document.getElementById('version').textContent = 'Phaser v' + Phaser.VERSION;

// This example was adapted from:
// https://phaser.io/examples/v3/view/tilemap/collision/matter-detect-collision-with-tile
// For reference, the tile map can be found here:
// https://github.com/photonstorm/phaser3-examples/blob/master/public/assets/tilemaps/maps/tileset-collision-shapes.json

const config = {
  type: Phaser.AUTO,
  mode: Phaser.Scale.FIT,
  width: 960,
  height: 960,
  backgroundColor: '#000000',
  loader: {
    baseURL: 'https://labs.phaser.io',
    crossOrigin: 'anonymous',
  },
  physics: {
    default: 'matter',
    matter: {
      gravity: { y: 1 },
      enableSleep: true,
    },
  },
  scene: {
    preload: preload,
    create: create,
    update: update,
  },
};

const game = new Phaser.Game(config);

function preload() {
  this.load.spritesheet('balls', 'assets/sprites/balls.png', { frameWidth: 17, frameHeight: 17 });
  this.load.tilemapTiledJSON('map', 'assets/tilemaps/maps/tileset-collision-shapes.json');
  this.load.image('kenny_platformer_64x64', 'assets/tilemaps/tiles/kenny_platformer_64x64.png');
}

function create() {
  var map = this.make.tilemap({ key: 'map' });
  var tileset = map.addTilesetImage('kenny_platformer_64x64');
  var layer = map.createLayer(0, tileset, 0, 0);
  
  // This allows us to set which tiles in our layer will receive bodies based on each tile's properties
  // In this example, ALL tiles have the property { collides: true }
  // In your game, you may only be giving bodies to certain tiles, filtered by your own set of properties
  layer.setCollisionByProperty({ collides: true });
  
  // Convert the layer. Any colliding tiles will be given a Matter body. If a tile has collision
  // shapes from Tiled, these will be loaded. If not, a default rectangle body will be used. The
  // body will be accessible via tile.physics.matterBody. Note that complex collision shapes may be
  // given MULTIPLE bodies. More on that further down.
  this.matter.world.convertTilemapLayer(layer);
  
  this.matter.world.setBounds(map.widthInPixels, map.heightInPixels);
  this.cameras.main.setBounds(0, 0, map.widthInPixels, map.heightInPixels);
  
  // Now that the tiles we've set to have bodies with 'setCollisionByProperty' have actual bodies,
  // We'll loop through all the tiles to find the ones we want to make into SENSORS
  layer.forEachTile(function (tile) {
    // If your desired sensor tiles have a complex body made up of different parts, you'll need to iterate through
    // each part. If it's a simple rectangle, it will only have 1 part which is a reference to itself
    // In this example, some tiles have the additional property 'type'. We're going to set those as sensors.
    if (tile.properties.type === 'lava' || tile.properties.type === 'spike') {
      tile.physics.matterBody.body.parts.forEach((part) => { part.isSensor = true });
    }
  });
  
  // Drop bouncy, Matter balls on pointer down. Apply a custom label to the Matter body, so that
  // it is easy to find the collision we care about.
  this.input.on('pointerdown', function () {
    const { x, y } = this.input.activePointer;
    var frame = Phaser.Math.RND.integerInRange(0, 5);
    this.matter.add.image(x, y, 'balls', frame, { restitution: 1, label: 'ball' });
  }, this);
  
  // Loop over collision pairs. We can do something with sensor overlaps here.
  // In this example, we'll tint the tile to show that the overlap occured.
  this.matter.world.on('collisionstart', function (event) {
    for (let i = 0; i < event.pairs.length; i++) {
      const { bodyA, bodyB } = event.pairs[i];
      const ball = bodyA.label === 'ball' ? bodyA : bodyB;
      const tile = bodyA.label === 'ball' ? bodyB : bodyA;
      
      if (tile.isSensor) {
        const mainBody = getRootBody(tile);
        const { gameObject } = mainBody;
        console.log(gameObject.tile);
        gameObject.tile.tint = 0x00FFFF;
      }
    }
  });
}

function update() {
  // nothing to do here in this example
}

// When tiled creates a composite body, there is a "main" body and "parts" bodies
// The parts do NOT have a reference to the parent game object, the main one does
// ALL bodies have a "parent" property
// For "parts" bodies, the parent is a a "main" or "parts" body
// For "main", the parent is itself
// This function takes ANY body and returns the "main" body
function getRootBody (body) {
  if (body.parent === body) { return body; }
  while (body.parent !== body) {
    body = body.parent;
  }
  return body;
}
Run Pen

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdn.jsdelivr.net/npm/phaser@3.55.2/dist/phaser.js