Skip to main content

Ray

Ray's are a useful tool for testing if geometry intersects a line of sight with Collider.rayCast. You can think of them as a point in space with a direction.

Examples where ray casting can be useful

  • Custom physics implementations (platformers, slopes, etc)
  • Line of sight AI
  • Proximity detection
warning

If you start a raycast from inside an Actor it will hit that Actor first unless you explicitly exclude it. See Raycast Options below.

Scene Testing

It is possible to test against all actors in a Scene at once.

typescript
// given a scene reference
const game = new ex.Engine({...});
game.start();
const ray = new ex.Ray(ex.vec(100, 100), ex.Vector.Right);
game.currentScene.physics.rayCast(ray, {...});
// or in a custom scene
class MyScene extends ex.Scene {
someRayTestMethod() {
const ray = new ex.Ray(ex.vec(100, 100), ex.Vector.Right);
this.physics.rayCast(ray, {...});
}
}
typescript
// given a scene reference
const game = new ex.Engine({...});
game.start();
const ray = new ex.Ray(ex.vec(100, 100), ex.Vector.Right);
game.currentScene.physics.rayCast(ray, {...});
// or in a custom scene
class MyScene extends ex.Scene {
someRayTestMethod() {
const ray = new ex.Ray(ex.vec(100, 100), ex.Vector.Right);
this.physics.rayCast(ray, {...});
}
}

Scene Raycast Options

Excalibur's raycast can be optionally configured in a variety of ways. By default if no options are provided to the rayCast(ray, ...) the options will be

typescript
const hits = scene.physics.rayCast(ray, {
searchAllColliders: true,
collisionGroup: CollisionGroup.All,
collisionMask: CollisionGroup.All.mask,
ignoreCollisionGroupAll: false,
maxDistance: Infinity,
filter: null,
})
typescript
const hits = scene.physics.rayCast(ray, {
searchAllColliders: true,
collisionGroup: CollisionGroup.All,
collisionMask: CollisionGroup.All.mask,
ignoreCollisionGroupAll: false,
maxDistance: Infinity,
filter: null,
})

Search All Colliders

By default the raycast will not stop after the first hit is found, if you'd like to only get the first hit set searchAllColliders: false

Max Distance

Distance is in terms of pixels, this is the maximum length along the ray to search for colliders.

Hit Filtering

Using the filter option you can do complex filtering logic to reject or accept potential hits.

typescript
scene.physics.rayCast(ray, {
filter: (potentialHit: RayCastHit) => {
// return true to accept the hit
// return false to reject the hit
return true;
}
})
typescript
scene.physics.rayCast(ray, {
filter: (potentialHit: RayCastHit) => {
// return true to accept the hit
// return false to reject the hit
return true;
}
})

Collision Mask

The collision mask it a bit mask with a 1 in the place for each collision group category you'd like to consider in the raycast. For example this is useful if all your enemies share a collision group category. See collision group documentation for more information.

You may want to pair this with ignoreCollisionGroupAll: true to avoid default actors which collide with everything including specific rayCast masks.

typescript
const playerGroup = new ex.CollisionGroup('player', 0b0001, ~0b0001);
const enemyGroup = new ex.CollisionGroup('enemy', 0b0010, ~0b0010);
...
// this raycast will only actors with the 1 in the second place, so enemyGroup
const enemyHits = scene.physics.rayCast(ray, {
collisionMask: 0b0010;
});
// this raycast will only actors with the 1 in the first and second place, so playerGroup and enemyGroup
const playerAndEnemyHits = scene.physics.rayCast(ray, {
collisionMask: 0b0011;
});
typescript
const playerGroup = new ex.CollisionGroup('player', 0b0001, ~0b0001);
const enemyGroup = new ex.CollisionGroup('enemy', 0b0010, ~0b0010);
...
// this raycast will only actors with the 1 in the second place, so enemyGroup
const enemyHits = scene.physics.rayCast(ray, {
collisionMask: 0b0010;
});
// this raycast will only actors with the 1 in the first and second place, so playerGroup and enemyGroup
const playerAndEnemyHits = scene.physics.rayCast(ray, {
collisionMask: 0b0011;
});

Collision Group

The collisionGroup searches for actors that match a specific collision group.

You may want to pair this with ignoreCollisionGroupAll: true to avoid default actors which collide with everything including specific rayCast groups.

Specific Actor Geometry Testing

If you have a specific actor or geometry that you know you will test against, all Collider types implement rayCast(ray, [maxDistance]).

typescript
const actor = new ex.Actor({
pos: ex.vec(200, 100),
width: 100,
height: 100,
color: ex.Color.Red,
})
const ray = new ex.Ray(ex.vec(100, 100), ex.Vector.Right)
// Ray cast against the collider geometry, returns a point if intersects or null if not
const point = actor.collider.get().rayCast(ray)
// Vector(150, 100)
typescript
const actor = new ex.Actor({
pos: ex.vec(200, 100),
width: 100,
height: 100,
color: ex.Color.Red,
})
const ray = new ex.Ray(ex.vec(100, 100), ex.Vector.Right)
// Ray cast against the collider geometry, returns a point if intersects or null if not
const point = actor.collider.get().rayCast(ray)
// Vector(150, 100)