Linux Format

Classic pseudo-3D racing effects

With a bag of 10p pieces in hand, Andrew Smith whisks us back to the classic arcade days to recreate pseudo-3D racing games.

- Andrew Smith is a software developer @ NHS Digital, has a Bachelors degree in Software Engineerin­g and an MSc in Computer Networks.

With a bag of 10p pieces in hand, Andrew Smith whisks us back to the classic arcade days to recreate pseudo-3D racing games.

Cue the budget wibbly-wobbly ‘going back in time’ special effects, as for this coding tutorial we’re going to look at some the old-school techniques used in some of the classic racing video games such as Road Rash (1991), Outrun (1986) and Pole Position (1982). Designed to work smoothly on low-powered hardware, these are smart visual tricks that create a fake 3D effect.

What is known as pseudo-3D techniques were used to create a simulated 3D racing effect. The games would often be played by a single player or two players against computer opponents. The Pseudo 3D Road project (created by Ray Tomely) that we will be looking at, even though not a full video game, is a selection of examples of pseudo-3D techniques demonstrat­ing ways to generate the 3D effect. You will see that once the project has been downloaded, these programmin­g techniques are located in different folders that each demonstrat­e a pseudo-3D effect.

Getting started

To get started we will need a few things: Python, PyGame and the Pseudo 3D road project. To install Python, open a terminal window (Ctrl+Alt+T) and type sudo apt-get python3 followed by sudo apt-get install pip3 . Then install the PyGame module by typing pip3 install pygame .

Finally grab a copy of the Pseudo 3D road project from https://raytomely.itch.io/pseudo-3d-roadcollec­tion by clicking the Download button on the page. Once downloaded, extract the contents of the

pseudo_3d_road_collection.rar file into an accessible location on your system.

As an example, the whole project has been put into a folder called PythonProj­ects which was created before downloadin­g the project. The source code and project can be retrieved from the LXF281 DVD. This tutorial will focus on the source code located in the folder called

simple_road. If you’re not already in that folder, type cd simple_road to get into it.

To edit and view the source code you can either use a default text editor installed on your flavour of Linux (Ubuntu for example) or you could use something more specific such as Notepad++, PyCharm or VS Code. The choice is entirely up to you. For this tutorial, we will be using gedit to view and edit the source files. When using this method to view/edit source files, it maybe helpful to open up two console windows where one will be used for editing/viewing source files and the other is a terminal window for executing the PyGame code.

Within the simple_road folder, the source code file that we want is simple_road_curve_segment_demo.py.

Before we look at the source code, navigate into the simple_road folder and type the following to execute the script.

$ python3 ./simple_road_curve_segment_demo.py

On successful execution you should get the output as seen in Figure 2 (page 94). The script executed has only been implemente­d with a forward control using the Up cursor key, and you will need to close the program with a mouse to end the program. As you hold down the Up cursor key, you will see the generated road go from being straight to having a bend in it and then back to being straight again.

Getting curvy

To view/edit this source file type the following into another terminal window:

$ gedit ./simple_road_curve_segment_demo.py You will see that it’s commented throughout to indicate what the variables are used for. As with any

other Python/PyGame script, at the top of the source code the libraries needed to run the script are declared. Also notice that continuing on from this, global variables for the script are declared to define the colours for the other parts of the road. See the following below:

import pygame,sys from pygame.locals import * BLACK=pygame.color.THECOLORS[“black”] WHITE=pygame.color.THECOLORS[“white”] RED=pygame.color.THECOLORS[“red”] GREEN=pygame.color.THECOLORS[“green”] BLUE=pygame.color.THECOLORS[“blue”] YELLOW=pygame.color.THECOLORS[“yellow”] SCREEN_WIDTH=640 SCREEN_HEIGHT=480 HALF_SCREEN_HEIGHT=int(SCREEN_HEIGHT/2)

The screen resolution settings SCREEN_WIDTH and SCREEN_HEIGHT are set to relatively low values in regards to today’s device screen capabiliti­es. Please feel free to change these for a higher resolution screen setting if you’re not happy with them. In addition to changing this, you will need to add two lines of code to ensure that the images loaded will always meet the screen resolution specified.

Add the following lines just after the images light_ road.png and dark_road.png have been loaded, as shown below.

light_road=pygame.image.load(‘light_road.png’). convert() light_road = pygame.transform.scale(light_road, (SCREEN_WIDTH, SCREEN_HEIGHT)) dark_road=pygame.image.load(‘dark_road.png’). convert() dark_road = pygame.transform.scale(dark_road, (SCREEN_WIDTH, SCREEN_HEIGHT))

You will also need to alter the following line of code in the script. Change

bottom_segment={‘position’:240,‘dx’:0}

to

bottom_segment={‘position’:SCREEN_

HEIGHT/2,‘dx’:0}

If the SCREEN_WIDTH and SCREEN_HEIGHT are changed and the above changes are not done, the display will look odd when running the program.

The main operation of the script is carried out in a defined main() function which is shown in part below: def main(): pygame.init()

#Open Pygame window screen = pygame.display.set_mode((640, 480),) #add

RESIZABLE or FULLSCREEN

#Title pygame.display.set_caption(“simple road”) #font font=pygame.font.SysFont(‘Arial’, 30) #images light_road=pygame.image.load(‘light_road.png’). convert() dark_road=pygame.image.load(‘dark_road.png’). convert() light_strip=pygame.Surface((SCREEN_WIDTH,1)). convert() dark_strip=pygame.Surface((SCREEN_WIDTH,1)). convert() light_strip.fill(light_road.get_at((0,0))) dark_strip.fill(dark_road.get_at((0,0))) #variables texture_position=0 #this is used to draw the road

As with all PyGame scripts, PyGame is initialise­d with a pygame.init() function call as can be seen from the above code. Also notice that when the screen display is set up, where there is the line: screen = pygame.display.set_mode((640, 480),) #add

RESIZABLE or FULLSCREEN

it misses out using the variables SCREEN_WIDTH and SCREEN_HEIGHT declared above. Replace the value of 640 with SCREEN_WIDTH and the value of 480 with SCREEN_HEIGHT so that the screen resolution can be adjusted to suit your device.

Notice that there are just two images loaded to create the intended effect and the rest of the effect is created by use of internal colour schemes to create alternatin­g strips.

In every PyGame program there needs to be a main loop that renders the graphics used in the program and that also controls input for the game via keyboard and/ or mouse depending on the PyGame applicatio­n being developed. Let us have a look at the main loop implemente­d in simple_road_curve_segment_demo. py. while True:

#loop speed limitation

#30 frames per second is enough pygame.time.Clock().tick(30) for event in pygame.event.get(): #wait for events if event.type == QUIT: pygame.quit() sys.exit()

#Movement controls keys = pygame.key.get_pressed() if keys[K_UP]:

As can be seen from the code above, the main while

loop ends on a Boolean condition to identify when the program has finished. In the case of the above, the loop ends when a False condition is raised. Continuing on from this, the frame rate is declared at 30 frames a second and further on from this the input/keyboard control is set up.

Finally after doing all this we come to the final part of the loop as shown below: pygame.display.flip()

From the above we update the contents of the entire display. The main focus of the render processing is done in the PyGame main while loop as shown below: while True:

# Setup / Control code

#Movement controls keys = pygame.key.get_pressed() if keys[K_UP]: road_pos+=road_accelerati­on if road_pos>=texture_position_threshold:

road_pos=0 top_segment[‘position’]+=curve_speed

#if we reach the curve’s end we invert it’s incrementa­tion to exit it if top_segment[‘position’]>=curve_map_lenght: top_segment[‘position’]=0 bottom_segment[‘dx’]=top_segment[‘dx’] top_segment[‘dx’]-=0.01 #+0.01 to exit a left curve and -0.01 to exit a right curve top_segment[‘dx’]*=-1

As can be seen from the above code segment, when the Up cursor key is pressed, the road position is increased as per the road_accelerati­on value

(currently set to 80) and when the value of road_pos

exceeds the value of texture_position_threshold (currently set to 300), the value of road_pos is set back to 0 and the process is repeated. If you think of a conveyer belt or treadmill mechanism where the track used is on a continuous cycle or loop, this is a very similar idea here for the pseudo-3D effect. The road is continuall­y re-generated after a certain point is reached.

To help control when the bend on the road appears, there is the following line of code after that which deals with the Up cursor key and after the initial road processing code:

curve_map_index+=curve_increment

Initially, the value of curve_map_index is set to -1, however the value is incremente­d by curve_increment (currently set to 2) on each cycle that the player has the Up cursor key pressed.

As the Up cursor key is pressed, you should start to see the road go into what looks like a left bend and then back to being a straight road again. Let’s look at the code that controls this.

if curve_map_index>=curve_map_lenght: curve_map_index=curve_map_lenght curve_increment*=-1

#if we exit, we invert its incriminta­tion to enter again #we invert the curve’s direction to change the way elif curve_map_index<-1: curve_increment*=-1 curve_direction*=-1

The point at which the left-bend appears and goes back to being a straight road again is controlled by an if else statement, or in Python if elif , as the Up cursor key is pressed. The first part of the if statement deals with the situation where if the curve has reached its maximum curvature it starts to make the road look straight again. The second part of the statement

( elif ) deals with the case of making the road bend to the left.

For those that are new to incrementi­ng variable values in Python, it may be worth pointing out that curve_increment *= -1 is equivalent of writing curve_ increment = curve_increment * -1 . This is again the same with the variable curve_direction = curve_ direction * -1 .

Graphics rendering

To help learn about how this program works further, you may find it useful to play around with the following variables: road_pos=0 #remembers our position on the road road_accelerati­on=40 #the speed we traverse the road texture_position_accelerati­on=8 #strip “stretch” value texture_position_threshold=300 #strip division value half_texture_position_threshold=int(texture_position_ threshold/2) #define drawing light or dark road

To save time scrolling through the code in getting to what you want, use the IDE search facility usually brought up by pressing Ctrl+F and then type the name of the variable or function you are looking for.

The two variables that might first be of interest to play around with values first could be road_accelerati­on and texture_position_accelerati­on .

Change the values of each variable and then run the program as shown before to see the effect this has on the running of the program. You may want to do this a number of times to get used to the effect of using different values.

In every PyGame program there has to be a part that is written to render all the graphics so after user input they can be updated. This is done with a for loop situated in the second half of the while loop as seen in part below: for i in range(HALF_SCREEN_HEIGHT-1,-1,-1): if top_segment[‘position’] < i:

dx = bottom_segment[‘dx’] else:

dx = top_segment[‘dx’] ddx += dx current_x += ddx curve_map[i] = current_x curve_value = curve_map[i] if texture_position

screen.blit(light_strip,(0,i+HALF_SCREEN_

HEIGHT)) screen.blit(light_road,(curve_value,i+HALF_

SCREEN_HEIGHT),(0,i,SCREEN_WIDTH,1)) else: screen.blit(dark_strip,(0,i+HALF_SCREEN_

HEIGHT)) screen.blit(dark_road,(curve_value,i+HALF_

SCREEN_HEIGHT),(0,i,SCREEN_WIDTH,1))

Going further

The rendering of the graphics is by default set to take place in the bottom half of the screen and not the top half, hence the use of HALF_SCREEN_HEIGHT . The above code renders both the dark and light sections of the road onto the bottom half of the screen. The top half of the screen is kept clear for the blue sky effect.

After you have gained more confidence with the code shown, you may want to add further functional­ity to the existing code. As you’re currently forced to close the program by closing the window with a mouse, it may be a good idea to end the program when the Escape key is pressed instead.

The colour of the sky is currently a plain blue. A possible addition here would be to create an image of a white cloud in an image editor such as Gimp and then create an animation of clouds moving across the top half of the screen.

As can be seen in the script, the accelerati­on value is set to a constant 80 per Up cursor key press. Those of you who from a physics background may want to create a more realistic accelerati­on algorithm where the rate of accelerati­on may gradually increase and decrease.

Even though it may involve some planning and some re-constructi­on of the existing code, since components of a race track have been demonstrat­ed – straight roads, bends and hills – it maybe a good exercise to build a small race track.

In other source code examples, images of vehicles have been added. It maybe a good idea to take the existing image(s) and modify them in an image editor to create an image of a vehicle turning left or turning right and implementi­ng into the script, writing code that will handle the left and right cursor keys. Instead of scrolling through the code line by line looking for what you want, instead press Ctrl+F which will usually bring up a search facility and type in a variable or function name.

 ??  ??
 ??  ?? The absolute classic racing game Pole Position by AtariSoft.
The absolute classic racing game Pole Position by AtariSoft.
 ??  ??
 ??  ?? The final folder structure once the project has been downloaded.
The final folder structure once the project has been downloaded.
 ??  ?? Figure 2: This is what a successful execution of program should look like.
Figure 2: This is what a successful execution of program should look like.
 ??  ?? With a bit of maths the basic road can be made to bend.
With a bit of maths the basic road can be made to bend.
 ??  ?? Our retro road running the basic straight road effect.
Our retro road running the basic straight road effect.

Newspapers in English

Newspapers from Australia