const config = {
  type: Phaser.WEBGL,
  width: 800,
  height: 600,
  physics: {
    default: "arcade",
    arcade: {
      debug: true
    }
  },
  scene: {
    preload: preload,
    create: create,
    update: update
  },
  loader: {
    baseURL: "https://labs.phaser.io",
    crossOrigin: "anonymous"
  },
  plugins: {
    scene: [
      {
        key: "PhaserRaycaster",
        plugin: PhaserRaycaster,
        mapping: "raycasterPlugin"
      }
    ]
  }
};

new Phaser.Game(config);

var raycaster;
var raycaster2;
var ray;
var ray2;
var graphics;
var obstacles;
var intersections = [];
var intersections2 = [];
var targets;
var slices;
var boundary;
var boundary2;
var graphics;
var maskGraphics;
var mask;
var fov;

var RANGE = 150;

function preload() {
  this.load.image("crate", "assets/sprites/crate.png");
}

function create() {
  //create raycaster
  raycaster = this.raycasterPlugin.createRaycaster({ debug: true });
  raycaster2 = this.raycasterPlugin.createRaycaster({ debug: true });

  //create ray
  ray = raycaster.createRay({
    origin: {
      x: 400,
      y: 300
    },
    collisionRange: RANGE,
    detectionRange: RANGE,
    rayRange: 2 * RANGE
  });
  ray2 = raycaster2.createRay({
    origin: {
      x: 400,
      y: 300
    },
    collisionRange: RANGE/2,
    detectionRange: RANGE/2,
    rayRange: 2 * RANGE/2
  });
  ray.enablePhysics();

  console.log("raycaster", raycaster);
  console.log("ray", ray);

  boundary = this.add.polygon(
    ray.origin.x,
    ray.origin.y,
    new Phaser.Geom.Circle(RANGE, RANGE, RANGE).getPoints(36),
    0xffff00,
    0.5
  );
  
  boundary2 = this.add.polygon(
    ray.origin.x,
    ray.origin.y,
    new Phaser.Geom.Circle(RANGE/2, RANGE/2, RANGE/2).getPoints(36),
    0xffff00,
    0.5
  );
  
  raycaster.mapGameObjects(boundary);
  raycaster2.mapGameObjects(boundary2)

  obstacles = this.add.group();
  createObstacles(this);
  raycaster.mapGameObjects(obstacles.getChildren());

  createTargets(this);

  intersections = ray.castCircle();
  intersections2 = ray2.castCircle();

  graphics = this.add.graphics({
    lineStyle: { width: 1, color: 0x00ff00 },
    fillStyle: { color: 0xffffff, alpha: 0.5 }
  });
  
  createFOV(this);
        //@ts-ignore
  fov.setDepth(10001)

  draw();

  this.input.on("pointermove", function (pointer) {
    
    ray.setOrigin(pointer.x, pointer.y);
    ray2.setOrigin(pointer.x+10, pointer.y+10);
    
    boundary.setPosition(pointer.x, pointer.y);
    boundary2.setPosition(pointer.x, pointer.y);
    boundary.data.get('raycasterMap').updateMap();
    boundary2.data.get('raycasterMap').updateMap();
    
    
    intersections = ray.castCircle();
    intersections2 = ray2.castCircle();
  });

  this.physics.add.overlap(ray, targets, function(rayCircle, target){

    //@ts-ignore
    target.setFillStyle(0xff00ff);
    //@ts-ignore
    target.isOverlapingFov = true;
  }, ray.processOverlap.bind(ray));

  this.game.events.on('prestep', function(){
    for(let target of targets.getChildren()) {
      if(!target.isOverlapingFov)
        target.setFillStyle(0x00ff00);
      target.isOverlapingFov = false;
    }
  });
}

function update() {
  draw();
}

function createTargets(scene) {
    targets = scene.physics.add.group();
    let target = scene.add.rectangle(450, 550, 50, 50)
      .setFillStyle(0x00ff00);
    targets.add(target);
  
    target = scene.add.rectangle(750, 500, 50, 50)
      .setFillStyle(0x00ff00);
    targets.add(target);
  
    target = scene.add.circle(450, 75, 25)
      .setFillStyle(0x00ff00);
    targets.add(target);
    target.body.setCircle(25);
  }

function createObstacles(scene) {
  let obstacle = scene.add.rectangle(100, 100, 75, 75, 0xaaaaaa, 0.5);
  obstacles.add(obstacle, true);

  obstacle = scene.add.line(400, 100, 0, 0, 200, 50);
  obstacles.add(obstacle);

  obstacle = scene.add.circle(650, 100, 50, 0xaaaaaa, 0.5);
  obstacles.add(obstacle);

  obstacle = scene.add.polygon(
    650,
    500,
    [0, 0, 50, 50, 100, 0, 100, 75, 50, 100, 0, 50],
    0xaaaaaa,
    0.5
  );
  obstacles.add(obstacle);

  for (let i = 0; i < 5; i++) {
    obstacle = scene.add.rectangle(
      350 + 30 * i,
      550 - 30 * i,
      50,
      50,
      0xaaaaaa,
      0.5
    );
    obstacles.add(obstacle, true);
  }

  obstacle = scene.add.image(100, 500, "crate");
  obstacles.add(obstacle, true);
}
function createFOV(scene){
  maskGraphics = scene.add.graphics({ fillStyle: { color: 0xffffff, alpha: 0 }});
  mask = new Phaser.Display.Masks.GeometryMask(scene, maskGraphics);
  mask.setInvertAlpha();
  fov = scene.add.graphics({ fillStyle: { color: 0x000000, alpha: 0.9 } }).setDepth(29);
  fov.setMask(mask);
  fov.fillRect(0, 0, 800, 600);
}

function draw() {
      graphics.clear();
    //clear field of view mask
    maskGraphics.clear();
    //draw fov mask
    maskGraphics.fillPoints(intersections);
    graphics.lineStyle(1, 0x00ff00);
    graphics.fillStyle(0xffffff, 0.1);
    if(intersections.length > 0) {
      graphics.fillPoints(intersections);
    }
  
    let index = 0;
    if(ray.slicedIntersections.length > 0){
      for(let slice of ray.slicedIntersections) {
        
        graphics.strokeTriangleShape(slice);
        
      }
    }
  
    if(ray2.slicedIntersections.length > 0){
      for(let slice of ray.slicedIntersections) {
        
        graphics.strokeTriangleShape(slice);
        
      }
    }
  
    graphics.fillStyle(0xff00ff);
    graphics.fillPoint(ray.origin.x, ray.origin.y, 3);
    // graphics.fillPoint(ray2.origin.x, ray2.origin.y, 5);
}
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
  2. https://cdn.jsdelivr.net/npm/phaser-raycaster@0.10.1/dist/phaser-raycaster.js