Raycasting plugin for Phaser 3.
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
<!--CDN-->
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/phaser-raycaster@0.10.10/dist/phaser-raycaster.min.js"></script>
# NPM
npm install phaser-raycaster
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);
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
}
create() {
// additional code
this.ray = this.raycaster.createRay();
// additional code
}
//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
});
//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();
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);
//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));
//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.
*/
}
});
//remove mapped objects
this.raycaster.removeMappedObjects(object);
this.raycaster.removeMappedObjects(arrayOfObjects);
//destroy ray
this.ray.destroy();
//destroy raycaster
this.raycaster.destroy();
//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
}
*/
//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
});