<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 left top;
  color: #eee;
  font: caption;
}

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

const names = `Београд
دِمَشق
Москва
ᠤᠯᠠᠭᠠᠨᠪᠠᠭᠠᠲᠤᠷ
東京都
Αθήνα
Reykjavíkurborg
København
Nassau
نجامينا
جيبوتي
香港
नई दिल्ली
서울
ⵜⵓⵏⵙ
މާލެ
နေပြည်တော်
काठमाण्डौं
🅿︎🅰🆁🅸🆂
Pago Pago
Taumata­whakatangihanga­kōauau­o­tamatea­pōkai­whenua­ki­tāna­tahu
Äteritsiputeritsipuolilautatsijänkä
🍎🍊🍋🍇
🐵🐵🐵
👋
👻`.split("\n");

const { red, orange, yellow, green, fuchsia } = colors.cssColors;

const nameColors = [ red, orange, yellow, green, fuchsia ];
const { GetRandom } = Phaser.Utils.Array;
const { Between } = Phaser.Math;

let players;

function init() {
  const texture = this.textures.createCanvas("name", 256, 1024);
  const ctx = texture.getContext();

  ctx.font = "20px sans-serif";
  ctx.textBaseline = "top";
  ctx.shadowColor = "black";
  ctx.shadowBlur = 2;
  ctx.shadowOffsetX = 1;
  ctx.shadowOffsetY = 1;

  // You could use ctx.measureText() for very precise frame dimensions.
  const dy = 32;
  let x = 0;
  let y = 0;

  for (const name of names) {
    console.debug(name, x, y);
    
    if (y >= texture.height) {
      console.warn("y out of range:", y);
      break;
    }
    
    ctx.fillStyle = GetRandom(nameColors);
    ctx.fillText(name, x, y);
    texture.add(name, 0, x, y, 256, dy - 4);
    y += dy;
  }

  texture.refresh();

  // document.body.appendChild(texture.getSourceImage());
}

function preload() {
  this.load.image("bg", "assets/skies/clouds.png");

  this.load.atlas(
    "knight",
    "assets/animations/knight.png",
    "assets/animations/knight.json"
  );
  // In Phaser v3.55.2 it must be "filecomplete-json-knight"
  this.load.once(
    "filecomplete-image-knight",
    function () {
      this.anims.create({
        key: "run",
        frames: this.anims.generateFrameNames("knight", {
          prefix: "run/frame",
          start: 0,
          end: 7,
          zeroPad: 4
        }),
        frameRate: 12,
        repeat: -1
      });
    },
    this
  );
}

function create() {
  this.add.image(0, 0, "bg").setOrigin(0, 0);

  players = this.physics.add.group({
    bounceX: 1,
    bounceY: 1,
    collideWorldBounds: true,
    defaultKey: "knight"
  });
  const blit = this.add.blitter(0, 0, "name");

  for (const name of names) {
    const x = Between(0, 800);
    const y = Between(0, 800);
    const bob = blit.create(x, y + 16, name);
    const player = players
      .create(x, y)
      .setName(name)
      .setData("nametag", bob)
      .setVelocity(60 * Between(-1, 1), 60 * Between(-1, 1))
      .playAfterDelay("run", Between(0, 200));
  }

  this.physics.add.collider(players);

  this.sys.displayList.bringToTop(blit);
}

function update() {
  for (const player of players.getChildren()) {
    player.getData("nametag").setPosition(player.x, player.y + 16);
    player.setFlipX(player.body.velocity.x < 0);
  }
}

new Phaser.Game({
  width: 800,
  height: 800,
  pixelArt: true,
  physics: { default: "arcade" },
  scene: { init, preload, create, update },
  loader: {
    baseURL: "https://labs.phaser.io",
    crossOrigin: "anonymous"
  }
});

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

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdn.jsdelivr.net/npm/[email protected]/dist/phaser.js
  2. https://cdn.jsdelivr.net/npm/@samme/[email protected]/dist/colors.umd.js