This project is inspired by the classic Wolfenstein 3D, which is considered the first-ever first-person shooter (FPS) game.
It uses raycasting to project a 3D perspective onto a 2D screen, creating an immersive view inside a maze-like environment.
Our game was inspired by our daily life at school, with textures based on real walls and views from 42 São Paulo.
In the bonus version, the player must find their lost lanyard before being caught by Flaviano, a beloved and iconic staff member from 42SP.
It was a challenging and creative project, but the result was both fun and thrilling. We hope everyone have fun playing it :)
The program takes a map file as input, which defines the layout of walls, player's starting position, textures, and colors. It then renders a 3D scene where the player can explore using keyboard and mouse controls.
Features
- Map parsing: loads the map layout and configuration from a
.cubfile - Raycasting engine: projects a 3D world from a 2D map using raycasting
- Collision detection: prevents player from walking through walls or closed doors
- Minimap: 2D top-down view of the map with player orientation
- User interaction:
- Move around the map:
W,A,S,Dkeys - Rotate player's view:
←/→keys or drag with the left mouse button - Open and close doors:
SPACEkey
- Move around the map:
- Animated sprites:
- 🧟 Enemy: avoid colliding with sprites wandering around, otherwise you will lose
- 🏅 Medal: find the medal to win the game!
At the core of this project lies the raycasting algorithm, which — combined with the DDA algorithm — simulates a 3D environment based on a 2D map layout.
It works by casting rays from the player’s point of view — one ray for each vertical stripe of the screen — and detecting where each ray hits a wall in the map. Based on the distance to that wall, a vertical slice of the wall is rendered, creating the illusion of depth.
-
For each vertical stripe on the screen:
- Calculate the direction of the ray using the player’s direction and the camera plane
- Determine the step direction and calculate delta and initial side distances
delta_distrepresents how far the ray has to travel in the world to move from one x-side or y-side of a square grid to the next. It is constant for each ray and is based on the direction of the rayside_distrepresents the distance from the player's current position to the first x-side or y-side the ray will hit. It depends on the ray's direction and player's current position within the grid
delta_dist[x] = fabs(1 / ray_dir[x]); if (ray_dir[x] < 0) { step[x] = -1; side_dist[x] = (pos[x] - map[x]) * delta_dist[x]; } else { step[x] = 1; side_dist[x] = (map[x] + 1.0 - pos[x]) * delta_dist[x]; }
The same logic applies to the y-side.
-
The DDA algorithm is used to traverse the 2D grid map and determine the first point at which each ray intersects a wall. It incrementally steps through the grid cells by comparing distances to the next x-side and y-side, and accumulates distance until a wall is hit:
while (hit == 0) { if (side_dist[x] < side_dist[y]) { side_dist[x] += delta_dist[x]; map[x] += step[x]; side = 0; // vertical wall } else { side_dist[y] += delta_dist[y]; map[y] += step[y]; side = 1; // horizontal wall } if (grid[map[y]][map[x]] == WALL) hit = 1; }
- Calculate the perpendicular distance from the player to the wall, which will be used to scale the height of the wall slice on screen
- Choose the appropriate texture based on the wall's direction and compute the exact texture coordinate to display
- Draw the vertical line, sampling the texture pixel by pixel
For a more detailed explanation, check out this tutorial.
-
Compile the project:
make
Or, for a more playful experience, compile the bonus version:
make bonus
-
Run the program providing the path of the selected map as argument:
./cub3D maps/valid/42.cub
./cub3D_bonus maps/valid/42_bonus.cub
You can explore other map files in the
maps/directory or create your own following the required format.
