Currently out of my home country right now and am running on Wi-Fi fumes. Actively rationing data on a more portable older device right now.
Setting the visual stage for our Pygame involves creating a captivating background and projecting sprites that interact with the environment. A well-designed background image can transport players to new worlds, while sprite projections and distance-based scaling can add depth and believability to the gameplay experience.
Before we start with background texturing and distance-based scaling, let us take a quick detour to our screen controls. Rather than cumbersomely use our keyboard arrows to turn around, the tutorial suggests using the mouse to do so instead.
The mouse border variables are for preventing the mouse from moving too far to the left or right, which could cause the player's view to rotate excessively. By resetting the mouse to the screen center when it reaches these borders, limiting the mouse movement to a certain range, the game can maintain a more controlled and comfortable viewing experience for the player.
The relative movement of the mouse would also need to be limited to enhance player viewing experience. Without this set of restrictions, the player's view would rotate extremely quickly or radically over a difference in a few pixels, potentially leading to a disorienting and nauseating game experience.
In the part of player angle updating, mouse sensitivity fractionizes the rotation speed relative to mouse movement to a very small value. Otherwise, like the absence of limiting relative movement, the player's view would be more difficult to control.
If nothing on the screen makes your eyes tired, you are on the adept track.
If you simply want to hide your mouse from the screen, notably not wanting to display its position-resetting method in-game, Pygame has a built-in function for setting mouse visibility.
The mouse may have killed some immersion back there.
Back to the object renderer class, we get our sky image texture and store it into a variable. Create an offset variable to go with it as well.
The sky offset is used to create a scrolling effect for the game's sky texture. Its value slowly increases by a value multiplied by delta time, divided by width, to make the program draw the texture slightly over the side.
Oh, and you can color the floor by drawing a rectangle as wide as your screen and half as tall as your screen.
This method creates an illusionary presence of a four-surface skybox, since the player is unable to look up or down.
I would say this enhances movement immersion… if this world's sky looks similar to our own.
Personally, I can make the sky look more eldritch by governing its offset with the game's delta time instead of the player's relative movement (of their mouse).
Anything in the files is yours to further custom or refine.
Text
In an immersive 3D game, the player's field-of-view is typically limited to a certain angle, about 90 degrees. When an object is directly in front of the player, it appears larger and closer than when it is at an angle to the player's direction. To accomplish this, the tutorial's iteration of Pygame 3D simulation employs a technique called angle-based distance scaling.
Text
First, this technique calculates the angle (theta) between the player's position and the object's position. Theta is the angle in radians between the positive x-axis and the vector (dx, dy). In our current context, it represents the direction from the viewer to the sprite.
Next, the difference (delta) between the angle theta and the player's current angle is calculated. Delta represents how much the object's direction deviates from the player's current direction.
Regarding delta, if the angles in the game are not represented in a circular manner, wrapping around from π to -π, the delta angle would be calculated as a negative value which would lead to incorrect calculations downstream.
The tutorial proposes adding a condition that checks if the object is on the right side of the player (dx > 0) and the player is facing downwards (self.player.angle > π), or if the object is on the left side of the player (dx < 0) and below the player (dy < 0). If either conditions are met, math.tau (equal to 2π) is added to delta, effectively 'wrapping it around' to the correct value.
Afterwards, by dividing delta by delta_angle (field of view divided by number of rays) in the settings file, we get delta_rays. This variable represents how much of the field of view the sprite's angle occupies.
Imagine measuring the distance between two points on a map with an unfamiliar scale. You can break down the map into smaller units (delta_angle) and analyze the total distance (delta). By dividing the total distance into smaller units, you can get the number of units the distance covers (delta_rays), giving you a precise measurement.
To get the current position of our sprite on-screen, this iteration calculates what number of delta_rays it takes for said sprite to reach the middle of the screen, which helps the program know if it is within screen borders and should be spawned. The scale factor is then applied to delta_rays to convert the ray coordinates to screen x-coordinates.
Continuing from the last allegory, imagine trying to pinpoint a location on a map with an unfamiliar scale. You can pinpoint a location on a map by identifying the center (half_number_of_rays) and adding the offset (delta_rays) to get the total units from the edge to the location. Finally, you can multiply by the map's scale to get the exact x-coordinate (self.screen_x), precisely locating the point on the map.
Onto simulating depth perception, you need the program to adjust the size of projecting sprites the closer/farther they are from the player. You obviously need it to calculate the distance between player and object (dx, dy), but this brings back a past problem once present in our ray casting lens: fishbowl effect.
The tutorial proposes normalizing (dx, dy) to remove this problem. The normalization formula below removes the fishbowl effect in sprite projection by compensating for the distortion caused by the viewer's perspective. The cosine in the normalization formula reduces the distance as the sprite approaches the edge of the screen, which counteracts the stretching effect of the perspective.
If the real world were a computer program, object permanence would decrease overall performance and lag much more. Luckily for us, we can partially nullify that concept in our game to improve its frame rate.
The condition dictates that if the sprite has moved far enough to the right that half its width is visible on-screen (self.image_half_width < self.screen_x), and it is not too far away from the viewer (self.normalized_distance > 0.5), the program will consider that the sprite is 'spawned' and becomes visible on the screen.
Before we begin creating our sprite projection function, we need our program to calculate each sprite image's ratio. This is important because it prevents the sprite from being stretched or distorted when projected onto the screen.
The formulae below correlates sprites' parameters (width, height, ratio) with player distance, creating new variables that simulate depth perception – afar equals small, close equals big.
Using the center as an indicator of position to the program of its on-screen presence invites a visual oddity (not bug- or glitch-level): its image will despawn halfway besides the screen borders. Simply let the program acknowledge the sprite image's size as an indicator instead to prevent premature despawning.
Make a habit of initializing any personal variables for your classes in programming.
The place looks a little livelier, but we have to make the walls hide things behind them.
Getting rid of this feature involves tampering with the order of items to be drawn from the rendering list. Said order will be sorted by depth, distance of an object from the player's viewpoint, in descending order. If a wall is closer to you than that lamp sprite, the lamp sprite will be behind the wall until your rays touch it directly.
No more wall hacks.
I think our sprite object looks a little too big and away from the ground right now. If you want to scale or shift the height of them, initialize them as variables for the class.
Now to apply the sprite scale with the projection formula. Unless one is capsuled in a pair of brackets, the order of formulas defaults at left to right (after =).
Do the same with height shift and position y-axis, although with addition instead.
Adjust your sprites' size and height to your liking.