A real-time raycasting engine — walk through a 2‑D grid map with WASD controls, rendered as distance-shaded wall slices via SDL2.
One ray per screen column steps through the map until it hits a wall; the distance determines the projected wall height and its brightness. No GPU, no textures, no Z-buffer — just ray-intersection math and a framebuffer.
- Raycasting with DDA-style linear stepping
- Fisheye correction via view-plane projection
- Per-column wall slice rendering with distance-based shading
- Solid ceiling and floor
- WASD movement and rotation
- 160 rays at 6 px per column — 960 × 640 window
- True 24‑bit color rendering via SDL2 framebuffer
- Keyboard and Ctrl-C quit handling
- Written entirely in C
For each of 160 screen columns, a ray is cast from the player's position at an angle spread evenly across the 1.0-radian field of view. The ray steps forward in 0.04-unit increments until it hits a wall cell on the 16 × 12 tile map. The straight-line distance is corrected for fisheye by multiplying by cos(ray_angle − view_dir), then projected to a wall height of WIN_H / dist. The wall slice is drawn as a coloured column with brightness linearly decreasing from full (close) to dark (at max range). The ceiling is navy and the floor is dark green.
The world is a 2‑D character grid:
static const char *MAP[] = {
"################",
"#..............#",
"#..##....####..#",
/* ... */
"################"
};
#define MW 16
#define MH 12# is a solid wall; . is open space. The wall() helper checks if a world-space coordinate falls on a wall:
static int wall(double x, double y) {
int ix = (int)x, iy = (int)y;
if (ix < 0 || iy < 0 || ix >= MW || iy >= MH) return 1;
return MAP[iy][ix] == '#';
}Out-of-bounds coordinates are treated as walls.
The player has a position and a viewing direction:
double px = 2.5, py = 2.5, dir = 0;W/S move along the current direction; A/D rotate. Movement checks each axis independently so the player slides along walls:
case SDLK_w: {
double nx = px + cos(dir) * 0.2;
double ny = py + sin(dir) * 0.2;
if (!wall(nx, py)) px = nx;
if (!wall(px, ny)) py = ny;
break;
}One ray per screen column, spread across the field of view:
for (int col = 0; col < W; col++) {
double ra = dir - FOV / 2.0 + FOV * col / (double)W;
double sx = cos(ra), sy = sin(ra);
double dist = 0;
while (dist < MAX_DIST) {
dist += STEP;
if (wall(px + sx * dist, py + sy * dist)) break;
}Each ray steps in 0.04-unit increments along its direction until a wall or the 20-unit max range. A finer step gives better accuracy but costs more iterations.
Naive ray distance produces a bulging effect because peripheral rays travel farther. Projecting onto the view plane corrects it:
dist *= cos(ra - dir);Multiplying by the cosine of the angular offset converts slant (radial) distance to perpendicular (planar) distance.
Corrected distance is inversely proportional to wall height:
int wall_h = (int)(WIN_H / dist);
if (wall_h > WIN_H) wall_h = WIN_H;
int ceiling = (WIN_H - wall_h) / 2;
int base = ceiling + wall_h;The brightness of the wall slice decreases linearly with distance:
float shade = 1.0f - fminf((float)dist / MAX_DIST, 1.0f);
Uint8 c = (Uint8)(shade * 230 + 25);
Uint32 col_wall = SDL_MapRGB(fmt, c, (Uint8)(c * 0.7f), (Uint8)(c * 0.5f));| Distance | Shade | RGB (approx) | Visual |
|---|---|---|---|
| 0 | 1.0 | 255, 178, 128 | Bright |
| 10 | 0.5 | 140, 98, 70 | Mid |
| 20 | 0.0 | 25, 17, 12 | Dark |
Each column of COL_W pixels is filled with the appropriate colour — ceiling, wall, or floor:
for (int y = 0; y < WIN_H; y++) {
Uint32 color;
if (y < ceiling) color = col_ceil;
else if (y >= base) color = col_floor;
else color = col_wall;
for (int px = x_start; px < x_end; px++)
pixels[y * pitch + px] = color;
}The ceiling is a solid navy (30, 30, 50); the floor is dark green (15, 25, 15).
Input is polled via SDL events. The frame rate is capped at ~40 fps:
Uint32 elapsed = SDL_GetTicks() - frame_start;
if (elapsed < 25) SDL_Delay(25 - elapsed);git clone <this-repo>
cd Doom-Style-Raycaster
make
Or manually:
gcc caster.c -o caster -lm $(sdl2-config --cflags --libs)
Dependencies: SDL2 and a C compiler (gcc/clang).
Install SDL2 via Homebrew:
brew install sdl2
Or apt:
sudo apt install libsdl2-dev
./caster
Controls:
- W — move forward
- S — move backward
- A — turn left
- D — turn right
- ESC or Q — quit
- Ctrl-C — quit (fallback)
Edit constants at the top of caster.c:
W— number of rays (higher = smoother, slower)COL_W— pixel width per columnFOV— field of view in radiansSTEP— ray marching increment (smaller = more accurate)MAX_DIST— maximum render distance- Map — edit
MAP[]to design your own level - Ceiling, floor, and wall colours
- Ray-casting with linear stepping against a 2‑D grid
- View-plane projection for fisheye correction
- Inverse-distance wall height projection
- Distance-based shading (linear brightness falloff)
- Axis-independent collision detection for wall sliding
- SDL2 surface/pixel-buffer graphics
- Real-time animation with frame-rate capping
- Event-driven keyboard input
SDL.h— window management, pixel buffer, inputstdio.h— stderr diagnosticsstdlib.h—NULLstring.h—memsetmath.h—cos,sin,fminsignal.h—SIGINThandler