cub3d - creating Wolfenstein 3D's render engine

First, 42 challenged us to develop a 2D game without a game engine. Now it's time to do a 3D game.
We had to build a raycaster engine just like the one in Wolfenstein 3D, the very first 3D shooter.
Even though this game is visually a 3D game, it is just a 2D game with some tricks to look tri-dimensional. Some people might even call it 2.5D.
How does it work? 🤔
So the first step to build a raycaster engine is to create a 2D game. The player must be able to walk on the map, rotate, and not collide with walls.
Then we have to cast rays where the player is looking until the ray hits a wall. We have to cast as many rays as the window width resolution, and we can set an angle (ideally 60°) to be the field of view of the player.
Here's the initial stage of our game:

After that, comes the 3D trick, having the distance between each ray and the player, we can just draw a vertical line on the screen. The closer the distance to the player, the larger we'll draw the line on the screen, giving an idea of depth.
Look at the function to cast a single horizontal ray:
void check_horizontal(t_cube *cub, t_raycast *ray)
{
double px = cub->player.x * TILE_SIZE;
double py = cub->player.y * TILE_SIZE;
double atan = -1 / tan(ray->ra);
if (ray->ra < M_PI) { // Player is looking north
ray->ry = ((int)(py / TILE_SIZE)) * TILE_SIZE - 0.00001;
ray->rx = px + (ray->ry - py) * atan;
ray->yo = -TILE_SIZE;
ray->xo = (ray->yo * atan);
} else if (ray->ra > M_PI) { // Player is looking south
ray->ry = ((int)(py / TILE_SIZE)) * TILE_SIZE + TILE_SIZE;
ray->rx = px + (ray->ry - py) * atan;
ray->yo = TILE_SIZE;
ray->xo = (ray->yo * atan);
} else if (ray->ra == 0 || ray->ra == M_PI) {
ray->rx = px;
ray->ry = py;
ray->dof = WIDTH;
}
while (ray->dof < WIDTH) { // Loop until a wall is hit
ray->mx = (int)(ray->rx / TILE_SIZE);
ray->my = (int)(ray->ry / TILE_SIZE);
if (ray->my >= 0 && ray->mx >= 0 && ray->my < cub->map.rows && ray->mx < cub->map.cols && (cub->map.map[ray->my][ray->mx] == '1')) {
// Wall was hit
ray->hx = ray->rx;
ray->hy = ray->ry;
ray->h_dist = dist(px, py, ray->hx, ray->hy);
ray->dof = WIDTH;
} else {
ray->ry += ray->yo;
ray->rx += ray->xo;
ray->dof++;
}
}
}
Here's our game after drawing the vertical lines as the walls, and painting the top and bottom pixels:
Only that was enough to submit the project to 42. The project required us to just build a "maze-like" game, where you had walls and could walk around. But my partner Teo Rimize and I wanted to build an actual game.
We decided to add enemies and bosses, weapons and items you could pick up and get stronger. We also added three levels, each one with different textures and enemies.
To honor the city we live in, our last level is set in the Catacombs of Paris ☠️
Take a look at the full game:
You can play our game and check our code on the GitHub repository below: