<footer><div id=version></div></footer>
html, body {
  height: 100%;
}

body {
  margin: 0;
  padding: 0;
  background: #111 url(https://labs.phaser.io/assets/sprites/phaser3-logo-small.png) no-repeat 0 20px;
  color: #eee;
  font: caption;
}

#version {
  position: absolute;
  left: 0;
  top: 0;
  padding: 0;
  background: rgba(0, 0, 0, 0.5)
}
/* global colors, Phaser */

const { Rectangle } = Phaser.Geom;

class Example extends Phaser.Scene {
  constructor() {
    super("example");
  }

  preload() {
    this.load.image("ground_1x1", "assets/tilemaps/tiles/ground_1x1.png");
    this.load.spritesheet("coin", "assets/sprites/coin.png", {
      frameWidth: 32,
      frameHeight: 32
    });
    this.load.tilemapTiledJSON(
      "map",
      "assets/tilemaps/maps/tile-collision-test.json"
    );
    this.load.image("player", "assets/sprites/phaser-dude.png");
    this.load.image("mask", "assets/sprites/mask1.png");
  }

  create() {
    const map = this.make.tilemap({ key: "map" });

    const groundTiles = map.addTilesetImage("ground_1x1");

    const backgroundLayer = map.createLayer(
      "Background Layer",
      groundTiles,
      0,
      0
    );
    const groundLayer = map.createLayer("Ground Layer", groundTiles, 0, 0);

    this.rooms = [
      new Rectangle(0, 0, 224, 160),
      new Rectangle(384, 128, 384, 160),
      new Rectangle(256, 352, 352, 192)
    ];

    //  Our fake RenderTexture mask goes here, above the layers, but below the player.
    //  For performance, we want it to be the size of the canvas, _not_ the whole map!
    this.rt = this.add
      .renderTexture(0, 0, this.scale.width, this.scale.height)
      .setOrigin(0, 0)
      //  Make sure it doesn't scroll with the camera
      .setScrollFactor(0, 0);

    groundLayer.setCollisionBetween(1, 25);

    this.player = this.physics.add.sprite(320, 320, "player");

    this.physics.add.collider(this.player, groundLayer);

    this.cameras.main.setBounds(0, 0, map.widthInPixels, map.heightInPixels);
    this.cameras.main.startFollow(this.player);

    this.cursors = this.input.keyboard.createCursorKeys();
  }

  update() {
    const cam = this.cameras.main;

    //  Clear the RenderTexture
    this.rt.clear();

    //  Fill regions in black. Adjust for the camera scroll since the RenderTexture is pinned to the screen.
    for (const { x, y, width, height } of this.rooms) {
      this.rt.fill(0, 1, x - cam.scrollX, y - cam.scrollY, width, height);
    }

    //  Erase the 'mask' texture from the RenderTexture based on the player position.
    //  We subtract half the mask's dimensions so it's centered on the player.
    //  Adjust for the camera scroll since the RenderTexture is pinned to the screen.
    const { halfWidth, halfHeight } = this.textures.getFrame("mask");

    this.rt.erase(
      "mask",
      this.player.x - halfWidth - cam.scrollX,
      this.player.y - halfHeight - cam.scrollY
    );

    //  Move the player.

    const { left, right, up, down } = this.cursors;

    this.player.body.setVelocity(0, 0);

    if (this.cursors.left.isDown) {
      this.player.body.setVelocityX(-256);
    } else if (this.cursors.right.isDown) {
      this.player.body.setVelocityX(256);
    }

    if (up.isDown) {
      this.player.body.setVelocityY(-256);
    } else if (down.isDown) {
      this.player.body.setVelocityY(256);
    }
  }
}

document.getElementById("version").textContent = `Phaser v${Phaser.VERSION}`;

new Phaser.Game({
  width: 800,
  height: 576,
  //  <http://clrs.cc>
  backgroundColor: colors.hexColors.olive,
  scene: Example,
  loader: {
    baseURL: "https://labs.phaser.io",
    crossOrigin: "anonymous"
  },
  physics: {
    default: "arcade",
    arcade: { gravity: { y: 0 } }
  }
});
Run Pen

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdn.jsdelivr.net/npm/phaser@3.80.1/dist/phaser.js
  2. https://cdn.jsdelivr.net/npm/@samme/colors@1.2.0/dist/colors.umd.js