Skip to main content

Bullet

game/bullet.py defines the Bullet class, a pygame.sprite.Sprite representing a plasma bolt fired by the player. Bullets travel in a straight line, leave a short cyan trail, and are removed on wall collision or when they leave the screen.
class Bullet(pygame.sprite.Sprite):
    def __init__(self, x: float, y: float, direction: pygame.Vector2) -> None
    def update(self) -> None
    def draw(self, screen: pygame.Surface) -> None

Attributes

AttributeTypeDescription
imagepygame.Surface20×8 px plasma bolt, rotated to match direction. White ellipse core with a cyan outer glow.
rectpygame.RectBounding rect used for collision detection. Centred on (x, y) after rotation.
pospygame.Vector2Sub-pixel position, updated each frame.
directionpygame.Vector2Normalised unit vector representing the bullet’s travel direction.
speedint18 — pixels per frame.
spawn_timeintpygame.time.get_ticks() at construction. Available for lifetime-based expiry if needed.

__init__(x, y, direction)

x
float
required
Horizontal spawn position. Set to player.pos.x at the moment of firing.
y
float
required
Vertical spawn position. Set to player.pos.y at the moment of firing.
direction
pygame.Vector2
required
Normalised direction vector. In the main game loop this is last_direction, which tracks the most recent non-zero movement direction.
The sprite surface is built in three steps:
# 1. Allocate a 20x8 transparent surface
self.image = pygame.Surface((20, 8), pygame.SRCALPHA)

# 2. Draw the plasma bolt
pygame.draw.ellipse(self.image, (255, 255, 255), (10, 2, 10, 4))  # white core
pygame.draw.ellipse(self.image, (0, 255, 255),   (0,  0, 20, 8), 2)  # cyan glow border

# 3. Rotate to align with travel direction
angle = direction.angle_to(pygame.Vector2(1, 0))
self.image = pygame.transform.rotate(self.image, angle)
The angle is computed relative to the positive x-axis (1, 0), so a bullet fired to the right is unrotated, upward bullets rotate +90°, and so on.
pygame.transform.rotate() enlarges the surface to fit the rotated content. The rect is re-derived from the rotated image and centred on (x, y), so the bullet origin stays accurate regardless of angle.
Firing a bullet:
# In main_game.py — SPACE key handler (levels 2–4)
bullets.add(Bullet(player.pos.x, player.pos.y, last_direction))
shake_amount = 5  # recoil

update()

Advances the bullet along its direction vector each frame:
def update(self) -> None:
    self.pos += self.direction * self.speed
    self.rect.center = self.pos
No delta-time correction is applied; movement is frame-rate-dependent (60 FPS target). At speed = 18, a bullet crosses a 1200 px screen in approximately 67 frames (~1.1 s). update() is called by the bullets sprite group each frame:
bullets.update()  # in main_game.py game loop

draw(screen)

Renders the bullet’s kinetic trail and the plasma image.
screen
pygame.Surface
required
The active display surface.
def draw(self, screen: pygame.Surface) -> None:
    trail_start = self.pos - self.direction * 15
    pygame.draw.line(screen, (0, 150, 255), self.pos, trail_start, 2)
    screen.blit(self.image, self.rect)
A 2-px blue line (0, 150, 255) is drawn 15 px behind the bullet centre, giving a short motion-blur streak effect. The rotated plasma image is then blitted on top.
The main game loop blits bullets a second time with the shake offset applied:
for b in bullets:
    screen.blit(b.image, b.rect.move(offset))
This means the trail line drawn by draw() is not shaken; only the image is offset. At low shake values this difference is imperceptible.

Lifecycle

1

Spawned

Created on SPACE keypress (Levels 2–4) at the player’s current position, travelling in last_direction.
2

In flight

bullets.update() moves the bullet 18 px per frame. The group is iterated to check for wall and screen-boundary hits.
3

Wall impact

If bullet.rect.colliderect(wall) is true for any wall rect, 5 cyan (0, 255, 255) particles are spawned at bullet.pos and the bullet is removed from the group:
for _ in range(5):
    particles.append(Particle(bullet.pos.x, bullet.pos.y, (0, 255, 255)))
bullets.remove(bullet)
4

Off-screen removal

If pos.x or pos.y is outside [0, width] / [0, height], the bullet is removed with no particle effect.
5

Enemy hit

pygame.sprite.groupcollide(enemies, bullets, True, True) removes both the enemy and the bullet simultaneously, spawning 15 red particles and applying shake_amount = 10.

Build docs developers (and LLMs) love