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

body {
  margin: 0;
  padding: 0;
  background: #111 url(https://assets.codepen.io/918801/phaser.png) no-repeat 0
    20px;
  color: #eee;
  font-family: system-ui;
  line-height: 1.5;
}

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

const { red, orange, yellow, green, blue, white } = colors.hexColors;

class MainScene extends Phaser.Scene {
  create() {
    // Zones have default origin (0.5, 0.5).
    const leftRim = this.add.zone(0, 0, 2, 2);
    const rightRim = this.add.zone(100, 0, 2, 2);
    const backBoard = this.add.zone(130, -50, 2, 100);
    const sensor = this.add.zone(50, 30, 2, 2);
    const ball = this.add.circle(120, 60, 30, orange, 0.4);

    const net = this.add.polygon(
      50,
      40,
      [0, 0, 100, 0, 80, 80, 20, 80, 0, 0],
      white,
      0.2
    );

    const cn = this.add.container(240, 120, [
      net,
      leftRim,
      rightRim,
      backBoard,
      sensor
    ]);

    this.physics.world.enable([leftRim, rightRim, backBoard, sensor, ball]);

    // You could use static bodies instead, but call body.updateFromGameObject() after adding to container.
    for (const { body } of [leftRim, rightRim, backBoard, sensor]) {
      body.immovable = true;
      body.moves = false;
    }

    leftRim.body.setCircle(1);
    rightRim.body.setCircle(1);

    ball.body.collideWorldBounds = true;
    ball.body.setCircle(ball.radius);
    ball.body.bounce.set(0.6);
    ball.body.gravity.y = 200;

    this.physics.add.collider(ball, [leftRim, rightRim, backBoard]);

    this.physics.add.overlap(
      ball,
      sensor,
      function onOverlap(_ball, _sensor) {
        _ball.body.debugBodyColor = yellow;
        // Overlap is continuous.
      },
      function process(_ball, _sensor) {
        return (
          _ball.body.velocity.y > 0 &&
          _ball.body.center.y < _sensor.body.center.y
        );
      }
    );

    this.input.on("pointerdown", ({ x, y, event }) => {
      ball.body.debugBodyColor = red;

      if (event.shiftKey) {
        ball.body.reset(x, y);
      } else {
        this.physics.moveTo(ball, x, y, 200);
      }
    });
  }
}

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

new Phaser.Game({
  width: 480,
  height: 320,
  // pixelArt: true,
  scene: MainScene,
  loader: {
    crossOrigin: "anonymous"
  },
  physics: {
    default: "arcade",
    arcade: { debug: true, debugBodyColor: blue, debugVelocityColor: green }
  }
});
Run Pen

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdn.jsdelivr.net/npm/phaser@3.80.0
  2. https://cdn.jsdelivr.net/npm/@samme/colors@1.2.0
  3. https://cdn.jsdelivr.net/npm/phaser-plugin-debug-body-colors@4.0.0