cub3d - creating Wolfenstein 3D's render engine

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:

0:00
/0:19

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 wanted to build a real 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:

0:00
/1:55

You can play our game and check our code on the GitHub repository below:

GitHub - marcioflaviob/cub3d_42: cub3d Group Project by Marcio Flavio and Teo Rimize
cub3d Group Project by Marcio Flavio and Teo Rimize - marcioflaviob/cub3d_42