DEVELOPING A RAYCASTING ‘3D’ ENGINE GAME IN PYTHON AND PYGAME – PART 6

The following changes will be covered in this post:

  • Improved skybox.
  • Bugfix for a bug related to the rendering of walls introduced with functionality to look up and down.
  • Red transparent screen effect added to act as a damage indicator for the player when enemy contact occurs.

Skybox Improvement


Due to the pattern and size of the image used for the skybox, an issue would occur where the image would suddenly switch to a different position. Although not game-breaking, it was somewhat jarring. To improve this, two changes needed to be made to the image used for the skybox:

  • the Image X (horizontal) resolution needed to be changed to be the same as the display window resolution (In this case, 1920 pixels).
  • The image needed to be replaced with a seamless image, i.e., the two sides of the image aligned to create an infinitely repeating pattern of clouds.

Wall Rendering Bugfix
A bug was introduced with the functionality for the player to look up and down that caused the rendering of walls to get miss-aligned if the player’s point of view was not vertically centered and the player was close to the wall in question. The image below shows an example of how the bug manifests:

This results from the game engine’s limitations and the lack of a z-axis for proper spatial positioning of items. To get around this, I added auto vertical centering of the player’s field of view every time the player moves. This will not completely fix the issue but will make it occur far less frequently.

To implement this change I added the following method in the Player class (in the playert.py file):

def level_out_view(self):
        if (self.HALF_HEIGHT - BASE_HALF_HEIGHT) > 50:
            self.HALF_HEIGHT -= 50
        elif self.HALF_HEIGHT - BASE_HALF_HEIGHT < -50:
            self.HALF_HEIGHT += 50
        else:
            self.HALF_HEIGHT = BASE_HALF_HEIGHT

And updated the keys_control method in the player class as follows:

def keys_control(self, object_map,enemy_map):
        sin_a = math.sin(self.angle)
        cos_a = math.cos(self.angle)
        keys = pygame.key.get_pressed()

        if keys[pygame.K_ESCAPE]:
            exit()
        if keys[pygame.K_w]:
            nx = self.x + player_speed * cos_a
            ny = self.y + player_speed * sin_a
            self.x, self.y = check_collision(self.x, self.y, nx, ny, object_map, HALF_PLAYER_MARGIN)
            if nx == self.x or ny == self.y:
                self.play_sound(self.step_sound)
            self.level_out_view()
        if keys[pygame.K_s]:
            nx = self.x + -player_speed * cos_a
            ny = self.y + -player_speed * sin_a
            self.x, self.y = check_collision(self.x, self.y, nx, ny, object_map, HALF_PLAYER_MARGIN)
            if nx == self.x or ny == self.y:
                self.play_sound(self.step_sound)
            self.level_out_view()
        if keys[pygame.K_a]:
            nx = self.x + player_speed * sin_a
            ny = self.y + -player_speed * cos_a
            self.x, self.y = check_collision(self.x, self.y, nx, ny, object_map, HALF_PLAYER_MARGIN)
            if nx == self.x or ny == self.y:
                self.play_sound(self.step_sound)
            self.level_out_view()
        if keys[pygame.K_d]:
            nx = self.x + -player_speed * sin_a
            ny = self.y + player_speed * cos_a
            self.x, self.y = check_collision(self.x, self.y, nx, ny, object_map, HALF_PLAYER_MARGIN)
            if nx == self.x or ny == self.y:
                self.play_sound(self.step_sound)
            self.level_out_view()
        if keys[pygame.K_e]:
            self.interact = True
            self.level_out_view()
        if keys[pygame.K_LEFT]:
            self.angle -= 0.02
            self.level_out_view()
        if keys[pygame.K_RIGHT]:
            self.angle += 0.02
            self.level_out_view()

Player Visual Damage Indicator
A Visual Damage Indicator is a way to let the player know he is taking damage. This will become more relevant at a later stage when the concept of health points is implemented, but for now, it provides a way of showing when the enemy is in touching range of the player.
The number of enemies has also been increased to three to increase the chances of a damage event.


The Visual Damage Indicator is implemented by drawing a semi-transparent red rectangle over the screen whenever a collision between the player and the enemy is detected.

To check for these collisions a new function was added in the common.py file as below:

def check_collision_enemy(x, y, map_to_check, margin):
    location = align_grid(x, y)
    if location in map_to_check:
        #  collision
        return True

    location = align_grid(x - margin, y - margin)
    if location in map_to_check:
        #  collision
        return True

    location = align_grid(x + margin, y - margin)
    if location in map_to_check:
        #  collision
        return True

    location = align_grid(x - margin, y + margin)
    if location in map_to_check:
        #  collision
        return True

    location = align_grid(x + margin, y + margin)
    if location in map_to_check:
        #  collision
        return True

    return False

This function is called from the keys_control function in player class:

self.hurt = check_collision_enemy(self.x, self.y, enemy_map, HALF_PLAYER_MARGIN)

In the drawing.py file the background method in the Drawing class was updated as follows:

def background(self, angle, half_height, hurt):
        sky_offset = -1 * math.degrees(angle) % resX
        print (sky_offset)
        self.screen.blit(self.textures['S'], (sky_offset, 0))
        self.screen.blit(self.textures['S'], (sky_offset - self.textures['S'].get_width(), 0))
        self.screen.blit(self.textures['S'], (sky_offset + self.textures['S'].get_width(), 0))
        pygame.draw.rect(self.screen, GREY, (0, half_height, resX, resY))
        if(hurt):
            RED_HIGHLIGHT = (240, 50, 50, 100)
            damage_screen = pygame.Surface((resX, resY)).convert_alpha()
            damage_screen.fill(RED_HIGHLIGHT)
            self.screen.blit(damage_screen, (0, 0, resX, resY))

RED_HIGHLIGHT is a Tuple with four values stored in it. The first three values represent the RGB color code, and the last value indicates transparency level, with 0 being completely transparent and 255 completely opaque.
The convert_alpha method tells Pygame to draw the rectangle to the screen applying the transparency effect.

Here is a video of the effect in action:

The source code for everything discussed in the post can be downloaded here and the executable here.

DEVELOPING A RAYCASTING ‘3D’ ENGINE GAME IN PYTHON AND PYGAME – PART 6

Make a Rubber Ducky with a Raspberry Pico

I am taking a slight detour from the Raycasting series of posts (don’t worry, the next post in the series is coming soon) to cover another small project I have been working on, creating a Rubber Duckly using a Raspberry Pico and CircuitPython.


A Rubber Ducky is a keystroke injection tool that is often disguised as a USB flash drive to trick an unsuspecting victim into plugging it into their computer. The computer recognizes the Rubber Ducky as a USB keyboard (and mouse if required), and when it is plugged in, it executes a sequence of pre-programmed keystrokes, which will be executed against the target computer, as if the user did it. This attack thus exploits the security roles and permissions assigned to the user logged in at the time.
This is a good time to note that using a Rubber Ducky for dubious intents is illegal and a terrible idea, and I take no responsibility for the consequences if anyone chooses to use what they learn here to commit such acts.

To create the Rubber Ducky described in this post, you will need four things:
1. A Rasberry Pico
2. A Micro USB Cable
3. CircuitPython
4. The Adafruit HID Library of CircuitPython

First, you will need to install CircuitPython on your Raspberry Pico. This link will provide all the instructions and downloads you will require to do this.
Next, you will need to install the Adafruit HID Library. Instructions on how to do this can be found here.

Now that all the pre-requisites are installed and configured, the source code below can be deployed using the process described in the first link. The Source code below executes a sequence of keystrokes that opens Notepad on the target computer and type out a message. Just note that the keystrokes are slowed down significantly to make what is happening visible to the user Typically, this will not be done with a Rubber Ducky.

import board
import digitalio
import time
import usb_hid
from adafruit_hid.keyboard import Keyboard
from adafruit_hid.keycode import Keycode

kbd = Keyboard(usb_hid.devices)

led = digitalio.DigitalInOut(board.LED)
led.direction = digitalio.Direction.OUTPUT
led.value = True
time.sleep(10)
while True:
    kbd.press(Keycode.GUI, Keycode.R)
    time.sleep(.09)
    kbd.release_all()
    kbd.press(Keycode.N)
    time.sleep(.09)
    kbd.release(Keycode.N)
    time.sleep(.09)
    kbd.press(Keycode.O)
    time.sleep(.09)
    kbd.release(Keycode.O)
    time.sleep(.09)
    kbd.press(Keycode.T)
    time.sleep(.09)
    kbd.release(Keycode.T)
    time.sleep(.09)
    kbd.press(Keycode.E)
    time.sleep(.09)
    kbd.release(Keycode.E)
    time.sleep(.09)
    kbd.press(Keycode.P)
    time.sleep(.09)
    kbd.release(Keycode.P)
    time.sleep(.09)
    kbd.press(Keycode.A)
    time.sleep(.09)
    kbd.release(Keycode.A)
    time.sleep(.09)
    kbd.press(Keycode.D)
    time.sleep(.09)
    kbd.release(Keycode.D)
    time.sleep(.09)
    kbd.press(Keycode.ENTER)
    time.sleep(.09)
    kbd.release(Keycode.ENTER)
    time.sleep(.09)

    kbd.press(Keycode.H)
    time.sleep(.09)
    kbd.release(Keycode.H)
    time.sleep(.09)
    kbd.press(Keycode.E)
    time.sleep(.09)
    kbd.release(Keycode.E)
    time.sleep(.09)
    kbd.press(Keycode.L)
    time.sleep(.09)
    kbd.release(Keycode.L)
    time.sleep(.09)
    kbd.press(Keycode.L)
    time.sleep(.09)
    kbd.release(Keycode.L)
    time.sleep(.09)
    kbd.press(Keycode.O)
    time.sleep(.09)
    kbd.release(Keycode.O)
    time.sleep(.09)
    kbd.press(Keycode.ENTER)
    time.sleep(.09)
    kbd.release(Keycode.ENTER)
    time.sleep(100)

led.value = False

Here is a video of the Rubbert Ducky in Action:

Make a Rubber Ducky with a Raspberry Pico