This project has been created as part of the 42 curriculum by yucchen, sileow.
cub3D renders a 3D-like view using raycasting from a 2D map.
cub3D is a graphic design project inspired by the world-famous Wolfenstein 3D game, which is considered the first true First Person Shooter (FPS) ever created.
The goal of this project is to create a "realistic" 3D graphical representation of the inside of a maze from a first-person perspective using the Raycasting principles.
Built with C and the miniLibX graphical library, this project serves as a deep dive into practical applications of mathematics, window management, and event handling.
- Parsing: Parse and validate the
.cubfile (textures, colors, map closure, single player).- .cub format:
- NO SO WE EA textures
- F C colors
- Closed map with one player (N/S/W/E)
Example .cub file: NO ./textures/north.xpm SO ./textures/south.xpm WE ./textures/west.xpm EA ./textures/east.xpm F 180,190,200 C 90,95,105 111111 100101 101001 1100N1 111111
- .cub format:
- Initialization: Set up the player's position, direction vectors, and the camera plane.
- The camera plane is always perpendicular (90 degrees) to the direction vector:
- Two vectors are perpendicular if their dot product is 0.
- Camera plane's length (0.66) controls the field of view (around 66°):
0.66is not mathematically required.- It is just an optimal FOV constant that became the standard in Wolfenstein-style raycasters.
- The camera plane is always perpendicular (90 degrees) to the direction vector:
- MLX Setup: Initialize the MiniLibX window, image buffers, and key/render hooks.
mlx_loop_hook()registers a function that MLX will call repeatedly while the event loop is running- Difference from
mlx_hook():
mlx_hook -> called only when an event happens (For key press/release, mouse, close) mlx_loop_hook -> called every frame (For movement + rendering every frame)
- Difference from
- Raycasting: Utilize the Digital Differential Analysis (DDA) to shoot rays, detect wall hits, and project distances to prevent fish-eye distortion.
- Rendering: Load
.xpmimages and map texture to walls (North/South/West/East)- [Bonus] Display a real-time minimap system to support player navigation.
- [Bonus] Add animation to walls.
- [Bonus] Show doors as open when player is near; closed when player is at least a set distance away.
- Interaction: Handle player movement (
W,A,S,D), camera rotation(Left/Right)- To rotate a vector, multiply with the rotation matrix
[ new_x ] [ cos(a) -sin(a) ] [ x ] [ new_y ] = [ sin(a) cos(a) ] [ y ]- [Bonus] Wall collisions detection to prevent player from 'entering' wall.
- [Bonus] Rotate the point of view with the mouse.
To compile the mandatory part of the project, simply run:
makeTo access the full game features including bonus features, run:
make bonusRun the executable with a valid map file passed as an argument:
./cub3D maps/valid/small_N.cubFor bonus features, replace ./cub3D by ./cub3D_bonus.
| Key | Action |
|---|---|
W |
Move Forward |
S |
Move Backward |
A |
Move Left |
D |
Move Right |
← |
Rotate Camera Left |
→ |
Rotate Camera Right |
ESC |
Exit Game |
The parser is implemented step by step. Each step validates its input before moving to the next step. If any step fails, the program safely frees allocated memory, prints a descriptive error, and exits.
- Check argument count (
argc == 2)
- Verify the
.cubfile extension - Ensure the file can be opened
- Read the entire file using
get_next_line - Strip trailing newline (
\n) - Store into
char **lines
- Skip blank lines
- Parse the 6 required identifiers in any order
- Textures (
NO,SO,WE,EA) and check the file paths - Colors (
F,C) and ensure exactly 3 values in the range[0,255]
- Textures (
- First line containing map tiles (
,0,1,N,S,W,E) marksmap_start - No blank lines allowed inside the map
- No configuration lines after the map starts
- Only valid map characters are allowed
- Each identifier must appear exactly once
- Textures (
NO,SO,WE,EA) - Colors (
F,C)
- Textures (
- Extract map lines from
map_starttoEOF
- Compute max width among all map lines
- Store the map width & height
- Allocate a rectangular map of size
map_height x map_width - Pad shorter lines with spaces (
' ') - Preserve original layout
- Locate exactly one player (
N,S,W,E) - Store player position at tile center (
+0.5) and direction - Replace player tile with
'0'
- Ensure the map is closed by walls
- Border checks
- If the top row contains any
0,N,S,W,E-> fail (Step 3) - If the bottom row contains any
0,N,S,W,E-> fail (Step 5) - If the first column contains any
0,N,S,W,E-> fail (Step 6) - If the last column contains any
0,N,S,W,E-> fail (Step 6)
- If the top row contains any
- Neighbor checks
- For every
'0', check its 4 neighbors != ' '- (x, y)
- North: Up (x, y - 1)
- South: Down (x, y + 1)
- West: Left (x - 1, y)
- East: Right (x + 1, y)
- For every
- Border checks
References
- Lode's Computer Graphics Tutorial: Raycasting (https://lodev.org/cgtutor/raycasting.html)
- Crazy Simple Raycasting E2 - 🎮 How to make awesome 3d games in Scratch (https://www.youtube.com/watch?v=Vihr-PVjWF4)
- Make Your Own Raycaster Part 1 (https://www.youtube.com/watch?v=gYRrGTC7GtA)
AI tools were used as a learning aid, for the following concepts:
- The fundamental concept of DDA.
- How simplifying delta_dist as the absolute reciprocal of ray direction allows us to directly obtain the perpendicular distance from DDA (instead of Euclidean distance).
- Distinguishing between MLX functions
mlx_hookandmlx_loop_hook. - Generate door textures and animated textures that are consistent with our wall textures (but not used eventually).
- Debug memory leak issues.
- This project follows the 42 Norm rules.
- Memory and file descriptor leaks are checked with Valgrind.
valgrind --leak-check=full --show-leak-kinds=all --track-fds=yes ./cub3D maps/valid/small_N.cub