-
Notifications
You must be signed in to change notification settings - Fork 16
RayCasting
This is the most important page of the tutorial. To start our logic, we need to know some concepts with the player angle
, player FOV
and Screen width
.
The first thing we have to know is that each ray needs to be throwed in relation of the player angle and the Field Of View (FOV). The player FOV is 60º
, but the player focus is in the middle of the FOV. Because this we have to start the RayCasting in -30º
of the player angle. For example, if the player angle is 90º
, we have to cast the rays from the 60º
angle to 120º
angle. Check the image below to see the player FOV representation.

Inside our rayCasting()
function, we can get the actual ray angle with this code
function rayCasting() {
let rayAngle = data.player.angle - data.player.halfFov;
}
After it, we will start the cast of the rays. Remember that we have to iterate all screen slices, so we will use the screen width to do this. For each iteration the rayAngle
variable needs to be incremented to iterate the entire player FOV. We will use the data.rayCasting.incrementAngle
to do it.
function rayCasting() {
let rayAngle = data.player.angle - data.player.halfFov;
for(let rayCount = 0; rayCount < data.screen.width; rayCount++) {
// ...
// Increment
rayAngle += data.rayCasting.incrementAngle;
}
}
Important: The next steps will be created inside the ray loop, and before the ray angle increment.
The first coordinates of the ray is the the same of the player coodinates. We will create an object with these values to be more organized.
// Ray data
let ray = {
x: data.player.x,
y: data.player.y
}
To discover the next coordinates of the actual ray, we have to calculate this based in the ray angle. For this step, we will use the functions Math.sin()
and Math.cos()
. These functions will give to usthe increment values to give for the ray to follow forward. In this step we will use the precision attribute too, to control the interval of each position of the ray. The sin
and cos
give us float values, but we can divide these values with the precision to turn these values
smaller.
Note: The higher value of the precision, more checkings will be executed and more positions the ray will have. We will not use a DDA algorithm that is used to find just the intersections in the grid, to focus making the simpliest way.
Note: We need to use the
degreeToRadians()
function in this step because the trigonometry functions work with radians values type.
// Ray path incrementers
let rayCos = Math.cos(degreeToRadians(rayAngle)) / data.rayCasting.precision;
let raySin = Math.sin(degreeToRadians(rayAngle)) / data.rayCasting.precision;
The next step is the wall checking. We have to increment the rayCos
and raySin
to x
and y
ray coordinates until find a wall in the map. So, in this step we need a loop.
Note: Remember that the matrix positions are represented in integer coodinates. The ray positions incremented with the
cos
andsin
will be float values and we need to convert these values to integer type. The function used for it isMath.floor()
// Wall checking
let wall = 0;
while(wall == 0) {
ray.x += rayCos;
ray.y += raySin;
wall = data.map[Math.floor(ray.y)][Math.floor(ray.x)];
}
When the ray collides with some wall, the loop will be stopped and we will have the ray coordinates updated with the wall position. The RayCasting distance is calculated in this step, to know the strip size that we will need to draw. If the wall is near, the distance will be lower then the drawed line will be bigger. If the wall is far, the distance will be bigger then the drawed line will be smaller.
For the distance calc we will use the Pythagoras Theorem. This formula will be used with the player coodinates and the ray coordinates of the wall.
- Pythagoras Theorem:
a² + b² = c²
- Formula:
distance² = (player x - ray x)² + (player y - ray y)²
- Code:
Math.sqrt( Math.pow( data.player.x - ray.x, 2 ) + Math.pow( data.player.y - ray.y, 2 ) );
The code will be:
// Pythagoras theorem
let distance = Math.sqrt(Math.pow(data.player.x - ray.x, 2) + Math.pow(data.player.y - ray.y, 2));
With the distance we have to define the wall height, the wall height will be used to draw the strip in the canvas. We cannot use the distance directally because we need the reversed value. To bigger distance, smaller strip. To reverse the value we can divide the distance value with the data.screen.halfHeight
. This value will define the same size for the width, height and length.
Note: The
wallHeight
value will be used to draw the strip in the canvas. The canvas coordinates are represented with an integer type for our case, so, we need to convert the value withMath.floor()
. The canvas drawer methods accept float values, but in our case it will not be a good practice.
// Wall height
let wallHeight = Math.floor(data.screen.halfHeight / distance);
The wall height will be used to draw the stripes. This step is divided by three operations.
- Draw the ceiling (From screen top to screen half height minus wall height)
- Draw the wall (From screen half height minus wall height to screen half height plus wall height)
- Draw the floor (From screen half height plus wall height to screen bottom)
I recommend to follow this image to create the logic:

The colors used for this tutorial can be others. For this tutorial preset colors were used. To be easier, we will use the draw function that we created in the Utilities tutorial.
// Draw
drawLine(rayCount, 0, rayCount, data.screen.halfHeight - wallHeight, "cyan");
drawLine(rayCount, data.screen.halfHeight - wallHeight, rayCount, data.screen.halfHeight + wallHeight, "red");
drawLine(rayCount, data.screen.halfHeight + wallHeight, rayCount, data.screen.height, "green");
This is all! Now, you can put your code to run! To do it, just open the HTML file in you browser and see your RayCasting working!
See below for entire code of this tutorial step.
/**
* Raycasting logic
*/
function rayCasting() {
let rayAngle = data.player.angle - data.player.halfFov;
for(let rayCount = 0; rayCount < data.screen.width; rayCount++) {
// Ray data
let ray = {
x: data.player.x,
y: data.player.y
}
// Ray path incrementers
let rayCos = Math.cos(degreeToRadians(rayAngle)) / data.rayCasting.precision;
let raySin = Math.sin(degreeToRadians(rayAngle)) / data.rayCasting.precision;
// Wall finder
let wall = 0;
while(wall == 0) {
ray.x += rayCos;
ray.y += raySin;
wall = data.map[Math.floor(ray.y)][Math.floor(ray.x)];
}
// Pythagoras theorem
let distance = Math.sqrt(Math.pow(data.player.x - ray.x, 2) + Math.pow(data.player.y - ray.y, 2));
// Wall height
let wallHeight = Math.floor(data.screen.halfHeight / distance);
// Draw
drawLine(rayCount, 0, rayCount, data.screen.halfHeight - wallHeight, "cyan");
drawLine(rayCount, data.screen.halfHeight - wallHeight, rayCount, data.screen.halfHeight + wallHeight, "red");
drawLine(rayCount, data.screen.halfHeight + wallHeight, rayCount, data.screen.height, "green");
// Increment
rayAngle += data.rayCasting.incrementAngle;
}
}
Go to the next tutorial to improve your projection.
- Next: Basic Movement
- Previous: Game Cycle
Copyright © 2018 Vinícius Reif Biavatti
- Home
- RayTrancing
- Examples
- Basic Tutorial
- Intermediary Tutorial
- Advanced Tutorial