<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
  </head>
  <body>
    <div id="phaser-game"></div>
  </body>
</html>
html,
body {
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
  background-color: #2a2a2a;
  overflow: hidden;
}
const DEFAULT_WIDTH = 1024
const DEFAULT_HEIGHT = 576
const MAX_WIDTH = 1536
const MAX_HEIGHT = 864
let SCALE_MODE = 'SMOOTH' // FIT OR SMOOTH

class Player extends Phaser.Physics.Arcade.Sprite {
  constructor(scene, x, y) {
    super(scene, x, y, 'dude')

    scene.add.existing(this)
    scene.physics.add.existing(this)

    this.setCollideWorldBounds(true)

    scene.anims.create({
      key: 'left',
      frames: scene.anims.generateFrameNumbers('dude', { start: 0, end: 3 }),
      frameRate: 10,
      repeat: -1
    })

    scene.anims.create({
      key: 'turn',
      frames: [{ key: 'dude', frame: 4 }],
      frameRate: 20
    })

    scene.anims.create({
      key: 'right',
      frames: scene.anims.generateFrameNumbers('dude', { start: 5, end: 8 }),
      frameRate: 10,
      repeat: -1
    })
  }

  update(cursors) {
    if (cursors.left.isDown) {
      this.setVelocityX(-160 * 2)

      this.anims.play('left', true)
    } else if (cursors.right.isDown) {
      this.setVelocityX(160 * 2)

      this.anims.play('right', true)
    } else {
      this.setVelocityX(0)

      this.anims.play('turn')
    }

    if (cursors.up.isDown && this.body.touching.down) {
      this.setVelocityY(-330 * 1.5)
    }
  }
}


class PreloadScene extends Phaser.Scene {
  constructor() {
    super({ key: 'PreloadScene' })
  }

  preload() {
    this.load.crossOrigin = 'anonymous'
    //this.load.baseURL = 'assets/img/'
    this.load.baseURL = 'https://labs.phaser.io/src/games/firstgame/assets/'
    this.load.image('sky', 'sky.png')
    this.load.image('ground', 'platform.png')
    this.load.image('star', 'star.png')
    this.load.image('bomb', 'bomb.png')
    this.load.spritesheet('dude', 'dude.png', { frameWidth: 32, frameHeight: 48 })
  }

  create() {
    this.scene.start('MainScene')
  }
}

class MainScene extends Phaser.Scene {
  fpsText
  cursors
  player

  constructor() {
    super({ key: 'MainScene' })
  }

  create() {
    const world = {
      width: 1600, // the width of 2 ground platforms
      height: 800 // the hight of the game
    }

    // the width and height of the world map
    this.cameras.main.setBounds(0, 0, world.width, world.height)
    this.physics.world.setBounds(0, 0, world.width, world.height)

    // draw safe area
    let safeArea = this.add
      .rectangle(
        this.cameras.main.width / 2 - +this.game.config.width / 2,
        this.cameras.main.height - +this.game.config.height,
        +this.game.config.width,
        +this.game.config.height,
        0xff00ff,
        0.08
      )
      .setStrokeStyle(4, 0xff00ff, 0.25)
      .setOrigin(0)
      .setDepth(2)
      .setScrollFactor(0)

    // draw the sky
    let sky = this.add
      .tileSprite(0, 0, this.cameras.main.width, this.cameras.main.height, 'sky')
      .setOrigin(0)
      // take the full height
      .setScale(Math.max(this.cameras.main.height / 600, 1))
      .setScrollFactor(0)

    // add all platforms
    let platforms = this.physics.add.staticGroup()
    platforms
      .create(400, 800, 'ground')
      .setScale(2)
      .refreshBody()
      .setOrigin(0.5, 1)
    platforms
      .create(1200, 800, 'ground')
      .setScale(2)
      .refreshBody()
      .setOrigin(0.5, 1)
    platforms.create(600, 632, 'ground')
    platforms.create(50, 482, 'ground')
    platforms.create(750, 453, 'ground')
    platforms.create(1150, 312, 'ground')
    platforms.refresh()

    // add the player
    this.player = new Player(this, 450, 450)

    // add the stars
    let stars = this.physics.add.group({
      key: 'star',
      repeat: 22,
      setXY: { x: 12, y: 0, stepX: 70 }
    })
    stars.children.iterate((child) => {
      child.setBounceY(Phaser.Math.FloatBetween(0.4, 0.8))
      child.setInteractive().on('pointerdown', () => {
        console.log('star body', child.body)
        console.log('you hit a star')
      })
    })

    // add the score text
    let score = 0
    // this is fixed to the safeArea
    let scoreTextSafeArea = this.add
      .text(safeArea.x + 16, safeArea.y + 16, 'score: 0', { fontSize: '32px', fill: '#000' })
      .setOrigin(0)
      .setScrollFactor(0)
    // this is fixed to the safeArea
    let scoreText = this.add
      .text(16, 16, 'score: 0', { fontSize: '32px', fill: '#000' })
      .setOrigin(0)
      .setScrollFactor(0)

    // collect a star
    const collectStar = (player, star) => {
      star.disableBody(true, true)
      score += 10
      scoreText.setText('Score: ' + score)
      scoreTextSafeArea.setText('Score: ' + score)
    }

    // add collider and overlap
    this.physics.add.collider(this.player, platforms)
    this.physics.add.collider(stars, platforms)
    this.physics.add.overlap(this.player, stars, collectStar)

    // add cursors
    this.cursors = this.input.keyboard.createCursorKeys()

    // camera should follow the player
    this.cameras.main.startFollow(this.player, true)

    // the resize function
    const resize = () => {
      // update position of safe area
      safeArea.x = this.cameras.main.width / 2 - +this.game.config.width / 2
      safeArea.y = this.cameras.main.height - +this.game.config.height

      // adjust the score text
      scoreTextSafeArea.x = safeArea.x + 16
      scoreTextSafeArea.y = safeArea.y + 16
      scoreText.x = 16
      scoreText.y = 16

      // adjust sky
      sky.width = this.cameras.main.width
      sky.height = this.cameras.main.height
      sky.setScale(Math.max(this.cameras.main.height / 600, 1))
    }

    this.scale.on('resize', (gameSize, baseSize, displaySize, resolution) => {
      console.log('resize "this.scale.on"')
      this.cameras.resize(gameSize.width, gameSize.height)
      resize()
    })
    console.log('resize in scene')
    resize()
  }

  update() {
    this.player.update(this.cursors)
  }
}

const config = {
  backgroundColor: '#ffffff',
  // please check if the parent matched the id in your index.html file
  parent: 'phaser-game',
  scale: {
    // we do scale the game manually in resize()
    mode: Phaser.Scale.NONE,
    width: DEFAULT_WIDTH,
    height: DEFAULT_HEIGHT
  },
  height: DEFAULT_HEIGHT,
  scene: [PreloadScene, MainScene],
  physics: {
    default: 'arcade',
    arcade: {
      debug: true,
      gravity: { y: 600 }
    }
  }
}

window.addEventListener('load', () => {
  const game = new Phaser.Game(config)

  const resize = () => {
    const w = window.innerWidth
    const h = window.innerHeight

    let width = DEFAULT_WIDTH
    let height = DEFAULT_HEIGHT
    let maxWidth = MAX_WIDTH
    let maxHeight = MAX_HEIGHT
    let scaleMode = SCALE_MODE

    let scale = Math.min(w / width, h / height)
    let newWidth = Math.min(w / scale, maxWidth)
    let newHeight = Math.min(h / scale, maxHeight)

    let defaultRatio = DEFAULT_WIDTH / DEFAULT_HEIGHT
    let maxRatioWidth = MAX_WIDTH / DEFAULT_HEIGHT
    let maxRatioHeight = DEFAULT_WIDTH / MAX_HEIGHT

    // smooth scaling
    let smooth = 1
    if (scaleMode === 'SMOOTH') {
      const maxSmoothScale = 1.15
      const normalize = (value, min, max) => {
        return (value - min) / (max - min)
      }
      if (width / height < w / h) {
        smooth =
          -normalize(newWidth / newHeight, defaultRatio, maxRatioWidth) / (1 / (maxSmoothScale - 1)) + maxSmoothScale
      } else {
        smooth =
          -normalize(newWidth / newHeight, defaultRatio, maxRatioHeight) / (1 / (maxSmoothScale - 1)) + maxSmoothScale
      }
    }

    // resize the game
    game.scale.resize(newWidth * smooth, newHeight * smooth)

    // scale the width and height of the css
    game.canvas.style.width = newWidth * scale + 'px'
    game.canvas.style.height = newHeight * scale + 'px'

    // center the game with css margin
    game.canvas.style.marginTop = `${(h - newHeight * scale) / 2}px`
    game.canvas.style.marginLeft = `${(w - newWidth * scale) / 2}px`
  }
  window.addEventListener('resize', event => {
    console.log('resize event')
    resize()
  })
  console.log('resize at start')
  resize()
})
View Compiled
Run Pen

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/phaser/3.16.2/phaser.min.js