Regardless of what coding fundamentals I still know not about, I am confident in my approach of picking them up when I need them.
2D raycasting is a technique used to simulate 3D environments in 2D graphics. It involves casting rays from a player's perspective and calculating the distance to the nearest wall. By rendering the walls based on their distance, you can create the illusion of depth and perspective. In Python, we can implement 2D raycasting using libraries like Pygame to visualize the results.
In the last project, sys is referenced but never used. I find it unlikely that Pygame requires external assistance from it, but will still keep it here for now.
Due to the complexity of building Classic Doom in Pygame, the tutorial builds it in separate, fetchable Python files. While doing so tidies up the folder, creating child methods in the future and tracing them will be more difficult as more separate function files are created. I suggest you write notes on important lines.
The game architecture of this program takes a class-based approach with initialization. Encapsulation can allow us to hide implementation details and expose only the necessary methods and attributes to external modules.
One of the most draining mistakes a programmer can make is building a house out of paper cards (coupling components) to live in that topples under the slightest breeze (rewrite).
I would quote Mister Ludwig Mies van der Rohe's “God is in the details,” but the details we'd be enacting from my first project are quite large and not small at all.
This iteration of mapping uses a 2D list to instruct the creation of game environments. In this case, any list items with (non-zero) value will be textured walls, otherwise they will be empty spaces for player movement. This list is referred to in the tutorial as mini_map.
Once you have your level lists, you next need a way to display them like giant discs on gramophones. One way to do so is with the empty dictionary world_map and method get_map().
get_map() extracts the indices of y- and x-coordinate data in mini_map – provided its list item is non-zero – and combines them into a tuple. Each tuple basically represents the tiles coordinates of walls in the map list, data that will be essential for simulating physical collision in game space.
All efforts above culminate in a ray casting, map compiling system.
Before we begin any raycasting work, a player asset holding the 'lens' needs to be built first. In your settings file, build variables for keeping track of player movement such as current position, current angle, movement speed (in pixels per frame), rotation speed (degrees per frame), etc.
Returning to the player file, the tutorial uses trigonometry (sine and cosine) to facilitate directionally aware movement. By using sine and cosine functions to calculate movement directions and velocities, the player's movement can be made to feel more fluid and natural, especially when combined with angular velocity and acceleration.
The tutorial proposes using the right arrow controls to control player rotation and the WASD quartet for movement. Speaking of player rotation, the statement below contains the modulo operation (%), which calculates the remainder of self.angle divided by math constant tau (τ). τ is equal to around 2π.
% is used to 'wrap around' the value of self.angle to the range of 0 to τ. This means that if self.angle exceeds τ, it will be reduced to a value within the range of 0 to τ by subtracting the largest multiple of τ that is less than or equal to self.angle.
In a 2D space, diagonal movement is the simple application of changing the player's x- and y-coordinates at the same time, thus simulating trigonometric movement.
Speaking of 2D spaces, we need to build the game environment before we dive into anything remotely 3D-related.
The new game initializer architecture fragments the aspects of an on-screen Pygame into separate functions: class builder, game event refresher, and asset drawer. You could just run every function in the previous project's while run loop, but messiness is the antithesis of an adept programmer.
By now, we have laid the foundations to recreating Doom Classic with Python.
You might have noticed a variable called delta_time in the main game file. Symbolically written as Δt, delta time represents the time elapsed between two consecutive frames in a game, typically measured in seconds.
We can use delta time to govern movement speed, separate it from being affected by frame rate by ensuring that movement speed remains constant regardless of the former. Doing so can result in a more constant movement speed, creating a more consistent and predictable gameplay experience.
The ray casting map prototype above was sped up 6 times. If we set the frame rate limit to 0 (null)…
Compare the player speed to a 60 fixed FPS game. Notice how similarly quick each player in the windows move and rotate?