A Raycast is a method of projecting an imaginary line (a "ray) from a specific point in a given direction to detect intersections with objects. You can think of it as a quick way to figure out what object would be "hit" if you fired an infinitely thin laser beam from a certain point in the direction of another point.
If the raycast hit an object, it gives information on the object hit. In HYTOPIA a hit object will be a block or entity.
If the raycast does not hit an object, it will return null.
You can control the length of the cast ray, it's origin and direction, and a number of options to ignore certain intersections, and more.
Basic Raycast Example
Here's some basic code showing how we can perform a raycast. Raycasts are performed from the simulation instance of a world as follows.
const origin = { x: 10, y: 2, z: 0 };
const direction = { x: 0, y: 1, z: 0 }; // raycast straight up
const length = 5; // Max length in blocks the ray travels
const raycastResult = world.simulation.raycast(origin, direction, length);
Block Breaking & Placing Example
Let's make something more useful. How about we setup our players so they can use the left click of their mouse to break blocks in front of the direction their facing, and the right click to place a block.
We can do that as follows.
// ... other code
// Enable debug raycasting so we can visualize our raycasts for testing
world.simulation.enableDebugRaycasting(true);
world.onPlayerJoin = player => {
const playerEntity = new PlayerEntity({
player,
name: 'Player',
modelUri: 'models/players/player.gltf',
modelLoopedAnimations: [ 'idle' ],
modelScale: 0.5,
});
// PlayerEntity by default has a PlayerEntityController assigned to .controller,
// but we explicitly assert that with ! to prevent typescript from complaining.
playerEntity.controller!.onTickWithPlayerInput = (entity, input, cameraOrientation, deltaTimeMs) => {
if (input.ml || input.mr) { // ml = mouse left clicked, mr = mouse right clicked
const origin = entity.position; // start at the player's current position.
const direction = entity.player.camera.facingDirection; // cast in the camera direction
const length = 5;
const raycastResult = world.simulation.raycast(origin, direction, length, {
// Prevent the raycast from registering our player entity as the hit object.
// Raycast options typically work using raw physics engine handles, so we
// have to use the raw rigid body of our player entity.
filterExcludeRigidBody: playerEntity.rawRigidBody,
});
if (raycastResult?.hitBlock) { // see if the result hit a block
if (input.ml) { // left click, break block
const breakPosition = raycastResult.hitBlock.globalCoordinate;
world.chunkLattice.setBlock(breakPosition, 0); // 0 = no block/air
} else { // right click, place block as neighbor of the hit block
const placePosition = raycastResult.hitBlock.getNeighborGlobalCoordinateFromHitPoint(raycastResult.hitPoint);
world.chunkLattice.setBlock(placePosition, 1); // 1 = bricks in the default block types
}
}
// Explicitly cancel inputs to prevent raycast spam each tick
// A player will need to let go of their click and click again
input.ml = false;
input.mr = false;
}
};
playerEntity.spawn(world, { x: 0, y: 10, z: 0 });
};
Here's a gif showcasing how our block breaking and placing code using raycasts works!
Diving Deeper
Raycasts make use of a few systems. To learn more and understand all of the features of raycasts, we recommend the following resources.
If there are features that we don't currently support for raycasts that you'd like to see added to the HYTOPIA SDK, you can submit a feature request here.