top of page

I’ve uploaded my entire project onto GitHub! Please feel to download and use this code how you’d like. The zip file contains a Standalone Release Build. I’ve also included the SDL2 API, which The project requires to compile. Placing the folders in your root C: Drive and opening in Visual Studios 2015 should be all that’s needed!

PROJECT SHARING

SIMPLE TRICKS FOR FAKE 3D

I’m very fascinated with anachronistic engines and methods for game development. Reading through the Wolfenstein Black Book was a real treat. I really wanted to see if I had the chops to recreate what many legendary developers already created; a 2.5D Raycast Engine! Of course, I did not want to go so far and develop it for DOS on a 286.

 

There’s nothing ground-breaking about my version There already exists hundreds of examples and tutorials of this time-tested ( some would say obsolete ) method. To give this exercise some extra weight, I decided to use Standard C. This was my first real hefty project using a non- object oriented programming language. This entire project was so much fun to do!! I hope I’ll get around to creating a Sector-Based 2.5D Egnine!

HOW RAYCASTING WORKS

The tech behind Wolfenstein 3D, Heretic, Zero Tolerance, etc has been very well documented and reproduced. Unlike real 3D rendering, raycast engines don’t worry about draw order, culling, world-to-local transforms, etc. It’s simplicity makes it amazing! Here is a cropped snippet of code that gives a gist of it

 

// Raycast and draw the World

for (i = 0; i < COLUMN_COUNT; i++)

{

Hit hit = Raycast(game, i);

}

if (hit.isHit == TRUE)

{

RenderColumn(game, &hit, i);

}

// Calculate Column Height based on distance and resolution

RenderColumn(GameState *game, Hit *hit, int i )

{

int columnHeight  = (int)( RESOLUTION.y / game->gameMap.wallScale / hit->dist );

int horizonLine   = (RESOLUTION.y / 2) - (columnHeight / 2);

SDL_Rect wallRect = { (int)(i / game->gameMap.columnRatio), horizonLine,

 (int)(1 / game->gameMap.columnRatio), columnHeight };

SDL_RenderCopy(game->renderer, game->img_Wall.img, &uvRect, &wallRect);

}

To cut it short, 2.5D raycasting is the projection of a 2D grid into a “third” dimension. From the Player’s position, hundreds of rays are cast with incrementing change of angle ( based on the FOV ). The ray travels along the 2D grid ( a 2D array ) and returns the distance to the first “wall” it hits. For each ray, a thin rectangle is drawn, with height being determined by the returned raycast distance.

When I showed my peers my tech demo, I was surprised that a couple assumed perspective correction was being applied to the wall textures. This is not the case. The illusion of rotation is coming from the subtlety changing distance of each ray to the wall. It’s as if the engine is drawing deck of cards, all piled on top of each other. Normally, a raycast engine’s number of columns is equal to the horizontal resolution. However, the more I reduce the number of raycasts to fire every N number of pixels, the more the “stack of cards” look becomes apparent! In fact, it looks very similar to the legendary Sega Super Scalar games!

CHEAP, EFFECTIVE LIGHTING

One of my favorite moments of this project was discovering ( on a whim ) how to create simple in-game lighting. The Column Draw function was already using distance ( measured by raycast ) to determine each Column’s height. I wondered, how the world would look if I shaded each Column using the same distance scalar value. It turns out…it’s pretty darn convincing!

// Simple Shadowing

if (game->debug.enabledLighting)

{

// Darken the Columns

double darkness = (hit->isSide == FALSE) ? hit->dist *

(game->gameMap.darknessIntensity/2.0f) : hit->dist *  game->gameMap.darknessIntensity;

darkness += (hit->isSide == FALSE) ? 128 : 0; // side walls are automatically darker Uint8 shadowAlpha = clampI( (int)darkness, 0, 255 );

 

// Draw Transparent Rect over Column for "shading" SDL_SetRenderDrawColor(game->renderer, 0, 0, 0, shadowAlpha); SDL_RenderFillRect(game->renderer, &wallRect);

}

Two Rects are drawn for each in-game Column ( horizontal resolution * const ratio scalar ). After the preliminary textured Rect is drawn on-screen, another fill Rect is drawn on top of it. The opacity of the black Rect is determined by the distance * const intensity scalar. In addition, perpendicular walls are always shaded 50% for some cheap, fake directional lighting! The world’s depth really benefits from this addition.

bottom of page