Phaser Raycaster

Raycasting plugin for Phaser 3.

GitHub release npm GitHub Github file size

Phaser Raycaster is a Phaser 3 plugin which provide raycasting for geometric game objects, sprites, Arcade Physics and Matter.js bodies.

Documentation: https://wiserim.github.io/phaser-raycaster/

Code examples are available on CodePen: LINK

Features:

  • compatible with arcade and matter physics,
  • raycasting in a single direction, 360 degrees circle or in a cone,
  • visibility detection (collision detection with game objects),
  • test rays on mapped game objects (containers, lines, rectangles, polygons, circles, sprites, tilemaps and matter bodies),
  • provides closest intersection points between rays and tested objects,
  • tests can be made on all mapped objects, selected ones or only ones within detection range,
  • static and dynamic mapping for individual objects,
  • mapped objects intersections detection,
  • debug mode.

NPM

npm install phaser-raycaster

CDN

https://www.jsdelivr.com/package/npm/phaser-raycaster

Getting started

1. Include plugin in your project:

<!--CDN-->
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/phaser-raycaster@0.10.9/dist/phaser-raycaster.min.js"></script>
# NPM
npm install phaser-raycaster

2. Enable plugin in your Game config:

import PhaserRaycaster from 'phaser-raycaster'

let config = {
    type: Phaser.Auto,
    parent: 'game',
    width: 800,
    height: 600,
    backgroundColor: "black",
    scene: [
        Scene1
    ],
    plugins: {
        scene: [
            {
                key: 'PhaserRaycaster',
                plugin: PhaserRaycaster,
                mapping: 'raycasterPlugin'
            }
        ]
    }
}

new Phaser.Game(config);

3. Create new raycaster in your scene:

create() {
  this.raycaster = this.raycasterPlugin.createRaycaster(options);
  
  // additional code
}

If you're using TypeScript, you need to add to scene plugin class:

import PhaserRaycaster from 'phaser-raycaster'

export default class MyScene extends Phaser.Scene {
  raycasterPlugin: PhaserRaycaster

  // aditional code
}

4. Create new ray

create() {
  // additional code
  
  this.ray = this.raycaster.createRay();
  
  // additional code
}

5. Map game objects which will be tested by rays

//create game object
this.rectangle = this.add.rectangle(100, 100, 50, 50)
  .setStrokeStyle(1, 0xff0000);

//map game object
this.raycaster.mapGameObjects(this.rectangle);

//create group
this.group = this.add.group();

//map game objects actually in group
this.raycaster.mapGameObjects(this.group.getChildren());

//map tilemap layer
this.map = this.make.tilemap();
this.tilemap = this.map.createStaticLayer();

this.raycaster.mapGameObjects(this.tilemap, false, {
  collisionTiles: [1,2,3] //array of tiles types which can collide with ray
});

6. Cast ray

//set ray position
this.ray.setOrigin(400, 300);
//set ray direction (in radians)
this.ray.setAngle(2);
//set ray direction (in degrees)
this.ray.setAngleDeg(90);
//cast single ray and get closets intersection, hit mapped object and hit segment
let intersection = this.ray.cast();
let hitObject = intersection.object;
let hitSegment = intersection.segment;

//cast rays in all directions (toward all mapped objects vertices / points)
let intersections = this.ray.castCircle();

//set ray's cone angle (in radians)
this.ray.setCone(1);
//set ray's cone angle (in degrees)
this.ray.setConeDeg(90);

//cast rays in a cone
let intersections = this.ray.castCone();

7. Raycaster bounding box

By default Raycaster is setting it's bounding box based on Arcade Physics / Matter physics world bounds. If world size will change after creation of Raycaster, bounding box needs to be updated.

//define bounds
var bounds = new Phaser.Geom.Rectangle(x, y, width, height);

//get world bounds (arcade physics)
bounds = this.worldLayer.getBounds();

//get bounds (matter physics)
let walls = this.matter.world;
bounds = new Phaser.Geom.Rectangle(
  walls.top.vertices[3].x, //x
  walls.top.vertices[3].y, //y
  walls.bottom.vertices[1].x - walls.top.vertices[3].x, //width
  walls.bottom.vertices[1].y - walls.top.vertices[3].y //height
)

//set bounding box on raycaster creation
var raycaster = this.raycasterPlugin.createRaycaster({
  boundingBox: bounds
});

//set bounding box after creation
raycaster.setBoundingBox(x, y, width, height);

8. Collisions (arcade physics)

//enable auto slicing field of view
this.ray.autoSlice = true;
//enable arcade physics body
this.ray.enablePhysics();
//set collision (field of view) range
this.ray.setCollisionRange(200);
//cast ray
this.ray.castCircle();

//get all game objects in field of view (which bodies overlap ray's field of view)
let visibleObjects = this.ray.overlap();

//get objects in field of view
visibleObjects = this.ray.overlap(group.getChildren());

//check if object is in field of view
visibleObjects = this.ray.overlap(gameObject);

//add overlap collider (require passing ray.processOverlap as process callback)
this.physics.add.overlap(this.ray, targets, function(rayFoVCircle, target){
  /*
  * What to do with game objects in line of sight.
  */
}, this.ray.processOverlap.bind(this.ray));

9. Collisions (matter physics)

//enable auto slicing field of view
this.ray.autoSlice = true;
//enable matter physics body
this.ray.enablePhysics('matter');
//cast ray
this.ray.castCircle();

//get all game objects and bodies in field of view (which bodies overlap ray's field of view)
let visibleObjects = this.ray.overlap();

//get objects and bodies in field of view
visibleObjects = this.ray.overlap([gameObject1, gameObject2, body1, body2]);

//check if object or body is in field of view
visibleObjects = this.ray.overlap(gameObject);

//add onCollide event
this.ray.setOnCollide(function(collisionInfo){
  //get body
  let body = collisionInfo.bodyA.label === 'phaser-raycaster-ray-body' ? collisionInfo.bodyB : collisionInfo.bodyA;
    /*
    * What to do with game object which enters line of sight .
    */
  }
});

//add onCollideWith event
this.ray.setOnCollideWith(body, function(body, collisionInfo){
    /*
    * What to do with game object which enters line of sight.
    */
  }
});

//add onCollideEnd event
this.ray.setOnCollideEnd(function(collisionInfo){
  //get body
  let body = collisionInfo.bodyA.label === 'phaser-raycaster-ray-body' ? collisionInfo.bodyB : collisionInfo.bodyA;
    /*
    * What to do with game object which leaves line of sight.
    */
  }
});

//add onCollideActive event
this.ray.setOnCollide(function(collisionInfo){
  //get body
  let body = collisionInfo.bodyA.label === 'phaser-raycaster-ray-body' ? collisionInfo.bodyB : collisionInfo.bodyA;
    /*
    * What to do with game object while it's in line of sight.
    */
  }
});

10. Destroy objects

//remove mapped objects
this.raycaster.removeMappedObjects(object);
this.raycaster.removeMappedObjects(arrayOfObjects);

//destroy ray
this.ray.destroy();

//destroy raycaster
this.raycaster.destroy();

11. Statistics

//get raycaster statistics
let statistics = this.raycaster.getStats();
/*
  statistics = {
    mappedObjects: {
      total - mapped objects total
      static - static maps
      dynamic - dynamic maps
      rectangleMaps - rectangle maps
      polygonMaps - polygon maps
      circleMaps - circle maps
      lineMaps - line maps
      containerMaps - container maps
      tilemapMaps - tilemap maps
      matterMaps - matter body maps
    }
  }
*/

//get ray statistics
let rayStatistics = this.ray.getStats();
/*
  rayStatistics = {
    method - used casting method (cast, castCircle, castCone)
    rays - casted rays
    testedMappedObjects - tested mapped objects
    hitMappedObjects - hit mapped objects
    segments - tested segments
    time - casting time
  }
*/

12. Debug mode

  //enable debug mode
  this.raycaster = this.raycasterPlugin.createRaycaster({
    debug: true
  });

  //advanced debug mode options
  this.raycaster = this.raycasterPlugin.createRaycaster({
    debug: {
      enabled: false, //enable debug mode
      maps: true, //enable maps debug
      rays: true, //enable rays debug
      graphics: {
          ray: 0x00ff00, //debug ray color; set false to disable
          rayPoint: 0xff00ff, //debug ray point color; set false to disable
          mapPoint: 0x00ffff, //debug map point color; set false to disable
          mapSegment: 0x0000ff, //debug map segment color; set false to disable
          mapBoundingBox: 0xff0000 //debug map bounding box color; set false to disable
      }
    }
  });

  //change debug options after initialization
  this.raycaster.debugOptions.enabled = true;

  this.raycaster.setOptions({
    debug: true
  });