Pseudo 3D First Person Shooter Example



We're still dealing with Pseudo 3D retro games - The creation of 3d worlds without using a 3d engine, models etc - but rather simple pixel manipulation on a 2d canvas.Today I'd like to show an example of the beautiful classic - Wolfenstein 3d - which was a pioneer in the pseudo 3d category.In previous posts we saw how to simulate depth on a static image of a road, by progressing towards an imaginary distance. We did so by drawing the pixels in each row of the road according to a set percentage that got smaller as we progressed towards the distance.In Wolf 3d the creators used a similar approach.

They split the screen into columns and filled each column with a "wall segment". The height of each wall (the number of rows the pixels of each segment took up) was determined by the distance of the player to that wall.How do you calculate distance and depth in a 2d canvas? By using a technique called "Ray casting".You start out with a 2d map (can be seen in the bottom left of the video). This map is a simple 2d array consisting of ones and zeros to mark walls and open spaces.

Something along the lines of:
[1,1,1,1 ,1 ,1],
[1,0,0,0,0,1],
[1,0,0,0,0,1],
[1,1,1,1 ,1 ,1]

Each number corresponds to a tile of equal width and height which is drawn on screen. Next you draw the player somewhere inside the grid and set his "range of sight". The range of sight is the current rotation of the player with offsets to the sides. For instance, a player with a 60 degree view angle will see his current rotation minus 30 degrees, and his current rotation plus 30 degrees.For each degree we "fire a ray" - meaning we check the pixel at the row and column of the specific degree in a loop that updates in an increasing radius until it falls within a wall tile of the 2d map.Upon hitting a wall we know that the length of the ray is the distance of the player to the wall. Another important variable is determining how far can the player actually see before objects should disappear into the distance. In order to understand how tall each wall strip on screen needs to be we simply divide the max distance by the length of our ray - and the result is the wall height that should be drawn on screen!
The farther away the wall is from the player, the longer the ray, and the division with the distance will provide a smaller wall - giving the illusion of depth.This alone is enough in order to simulate a 3d world. We can draw a screen split into the same amount of columns as the rays we fired (screen width / number of rays) - and fill each strip with the height of the corresponding wall.Some issues arise, one being the "Fish eye" effect. Standing in front of a wall parallel to the player will render a bent wall that is seemingly closer in the middle of the screen and farther away towards the edges of the screen. This happens because the rays fired by the player get longer as you get farther away from the center rotation outwards. Thankfully there's an easy fix - changing the length of each ray to cos(player angle - current angle). Another challenge is drawing the correct texture on each wall strip. You need to calculate not just whether your ray hit a wall, but where on that wall it hit. You need to grab the percentage of the tile your ray struck and paint the corresponding percentage of your wall texture to the screen- taking into account the number of columns and distance from the wall. In our case we have 4 different wall textures (Just replace the ones (1) in the grid with other numbers, marking different tiles. )
In my view the end result is no less than astonishing. A convincing 3d world, rendered without using any 3d rendering engine.I managed to write the majority of the code by myself, relying purely on theory. In order to solve several stubborn bugs I turned to this useful tutorial:

https://dev.opera.com/articles/3d-games-with-canvas-and-raycasting-part-1/

My code can be accessed here: https://drive.google.com/drive/folders/1GjRHcvAfY_QwiO7lIe2MQHn1sk_6xhZs?usp=sharing

תגובות

פוסטים פופולריים מהבלוג הזה

Thoughts on HTML5, a Flash Developers hopeless rant

Pseudo 3d Isometric view

Veroni Noise