🛸 I Made a Space Shooter in Python (And it works.. Kinda)
Hello internet.
I made a game.
Yeah. Another space shooter.
Because clearly, what the universe needed most right now… was this.
Built in Python. Using pygame.
Fueled by caffeine, chaos, and way too much Stack Overflow.
It started simple — thanks to Tech With Tim's space shooter tutorial.
Then I went full military-grade modder on it.
Because why reinvent the wheel…
when you can just slap armor on it, duct tape it to a rocket, and send it into orbit?
github : https://github.com/Zenoguy/Space_Shooters
Download ->
Windows : https://zenoguy.itch.io/space-shooters-concept-game
linux -> https://drive.google.com/file/d/1glHPw6BS_xvznkOcH-lrjffHMVsdtlQ9/view?usp=sharing
(works for Arch distros{yh I use Arch}, lemme know if its giving error on other distros)
🎞️ The Origin Story (feat. Tim)
I googled "how to make a game in Python".
Tech With Tim popped up, obviously.
I followed his space shooter guide. Learned the ropes. Got the basics working.
Then I looked at my code…
and said: "Cool. Now let's break everything and make it mine."
🧠 The Big Brain Stack
- Python 3 (I'm chaotic, not primitive)
- pygame (aka "Please Save Frequently")
- Sprites? Mostly of Tim and made one myself B)
- Music? Scavenged from the depths of royalty-free Google searches
- Folders:
-
assets/
— ships, lasers, vibes -
bgm/
— background music that fights back
-
🔫 So What Makes My Game Different?
You know how tutorials give you a good starting point?
I didn't stop there.
I duct-taped a bunch of my own mechanics to it until it turned into a legitimate game.
☠️ Kamikaze Enemies That Actually Hunt You
Yeah. These boys don't shoot lasers like regular enemies.
They just dive straight at you like they've got something to prove.
And they're smart about it.
def move(self, vel, player):
if not self.is_diving:
self.y += vel + 1 # Faster than regular enemies
# Start diving when close to player
if self.y > player.y - 200 and self.y < player.y and random.random() < 0.01:
self.set_target(player.x + player.get_width()//2, player.y + player.get_height()//2)
else:
# Calculate direction to target and dive at high speed
dx = self.target_x - (self.x + self.get_width()//2)
dy = self.target_y - (self.y + self.get_height()//2)
distance = (dx**2 + dy**2)**0.5
if distance > 0:
self.x += (dx / distance) * self.dive_speed
self.y += (dy / distance) * self.dive_speed
That's not just enemy logic — that's targeted harassment with visual warnings.
⚠️ Visual Warning System for Kamikaze Attacks
When a kamikaze decides you're the target, it flashes red to give you a split second to panic.
def draw(self, window):
window.blit(self.ship_img, (self.x, self.y))
# Flashing red warning when diving
if self.is_diving and self.warning_timer > 0:
if self.warning_timer % 10 < 5: # Flash every 10 frames
warning_surface = pygame.Surface((self.get_width() + 20, self.get_height() + 20))
warning_surface.set_alpha(100)
warning_surface.fill(RED)
window.blit(warning_surface, (self.x - 10, self.y - 10))
👾 Smart Enemy Spawning — Why It Matters
At first, I just spawned enemies at random x and y positions. Worked fine… until level 3 hit and suddenly six ships spawned inside each other like cursed nesting dolls. Pure chaos. Not the fun kind.
So I added minimum distance checks using math.hypot
to ensure enemies never spawned too close to each other. It gave me controlled chaos — where the challenge scales, but doesn’t feel unfair. Each wave feels intentional, not random junk thrown at the screen.
This was my first time implementing spatial logic, and it was surprisingly fun — kinda like managing party invites to make sure nobody stands too close at awkward small talk range.
🎮 Full-Featured Pause Menu System
Not just "Press P and hope" — this is a proper menu with navigation and settings.
def pause_menu():
options = [
"RESUME",
"NEW GAME",
"QUIT",
f"SFX: {'ON' if SOUND_ENABLED else 'OFF'}",
f"MUSIC: {'ON' if MUSIC_ENABLED else 'OFF'}"
]
selected_option = 0
# Arrow key navigation with Enter to select
# Can toggle sound/music settings mid-game
You can navigate with arrow keys, toggle SFX and music on/off, start a new game, or rage quit. It even pauses the music and switches to pause screen music.
🔊 Dynamic Sound System That Actually Works
Every laser blast, explosion, and menu transition has its own sound.
Plus dynamic music switching between menu, game, and pause states.
def play_sfx(sound):
if SOUND_ENABLED:
sound.play()
def play_music(music_file, loops=-1, volume=0.7):
if MUSIC_ENABLED:
pygame.mixer.music.load(music_file)
pygame.mixer.music.set_volume(volume)
pygame.mixer.music.play(loops)
The music system remembers where you were when you paused and can resume properly. When i was trying to figure it out the music were just overlapping LOL.
🎯 Laser vs Laser Combat System
Player lasers can actually collide with enemy lasers and destroy each other.
This wasn't in the tutorial — pure custom chaos.
# Laser collision detection between player and enemy projectiles
for enemy in enemies:
for enemy_laser in enemy.lasers[:]:
for player_laser in player.lasers[:]:
if enemy_laser.collision(player_laser):
if enemy_laser in enemy.lasers:
enemy.lasers.remove(enemy_laser)
if player_laser in player.lasers:
player.lasers.remove(player_laser)
break
That's ✨ tactical projectile warfare ✨. Not in the tutorial. That's all me.
📊 Proper Scoring System with High Score Persistence
Regular enemies are worth 10 points, kamikaze enemies are worth 20 (because they're more dangerous).
High scores save to a file and persist between sessions.
def save_high_score(score):
with open(HIGH_SCORE_FILE, "w") as file:
file.write(str(score))
# In the collision detection:
self.score += 10 # Regular enemies
self.score += 20 # Kamikaze enemies
🕹️ Smooth Movement with WASD + Arrow Key Support
The movement system supports both WASD and arrow keys, with proper boundary checking.
keys = pygame.key.get_pressed()
if (keys[pygame.K_a] or keys[pygame.K_LEFT]) and player.x - player_vel > 0:
player.x -= player_vel
if (keys[pygame.K_d] or keys[pygame.K_RIGHT]) and player.x + player_vel + player.get_width() < WIDTH:
player.x += player_vel
No stutter. No teleporting through walls. Just smooth, responsive controls.
💥 Game Over Screen That Doesn't Suck
Game ends? You get options.
R
to restart, M
to return to main menu, Q
to rage quit.
Shows your final score and current high score.
if lost:
for event in pygame.event.get():
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_r:
stop_music()
main() # Restart
return
elif event.key == pygame.K_m:
stop_music()
main_menu() # Back to menu
return
No more infinite nested function calls. Clean exits and restarts.
🎧 Music and Audio That Actually Cooperates
You think you understand pain? Try getting pygame.mixer
to behave at 3 AM.
But I figured it out:
- Main menu music
- In-game music
- Pause screen music
- Proper music stopping/starting
- SFX that can be toggled independently
# Music files loaded as paths, not objects
MAIN_MENU_MUSIC = os.path.join("bgm", "MainScreen.mp3")
PAUSE_MUSIC = os.path.join("bgm", "Loading_Screen.mp3")
GAME_MUSIC = os.path.join("bgm", "8bit-spaceshooter.mp3")
# Sounds loaded as objects for instant playback
LASER_SOUND = pygame.mixer.Sound(os.path.join("bgm", "laser.mp3"))
EXPLOSION_SOUND = pygame.mixer.Sound(os.path.join("bgm", "explosion.mp3"))
When it works — it slaps.
💀 Man did I squash bugs
- ✅ Music that wouldn't stop on game over
- ✅ Enemies spawning inside each other like nesting dolls
- ✅ Kamikaze enemies spawning off-screen and becoming invisible
- ✅ Pause menu not actually pausing the game logic
- ✅ Sound effects playing even when SFX was disabled
- ✅ Memory leaks from not properly removing lasers
- ✅ Game over screen not showing final score vs high score
We debug properly around here.
🐍 Turning It Into a Real .exe
Of course I packaged it with pyinstaller
.
pyinstaller --onefile --add-data "assets;assets" --add-data "bgm;bgm" main.py
Out pops main.exe
in /dist/
.
Ship it to your friends. Watch Windows Defender question your life choices.
(It's legitimate. Mostly.)
⚖️ Game Balance That Makes Sense
- Player: 100 HP, fast shooting (20 frame cooldown)
- Regular enemies: 10 HP, slow shooting (40 frame cooldown), 10 points
- Kamikaze enemies: no shooting, aggressive diving, 20 points
- Enemy collision damage: 10 HP
- Kamikaze collision damage: 40 HP (they mean business)
- Player laser damage: 10 HP per hit
The math actually works. Kamikaze enemies are high-risk, high-reward targets.
GitHub and the Great Sound File Situation
70MB of sound files don't play nice with GitHub's size limits.
Solution: I converted my .wav files to .mp3 and they were 8 times lighter now.
Now you can ask why not - .gitignore
the bgm/
folder, upload to Google Drive and link it in the README like a civilized developer.
I didn't do it because this is my game. I'm the BOSS.
🎯 Mask-Based Collision — The Big Brain Move
The biggest “ah-ha” in this whole project?
Pixel-perfect collision using masks.
Most beginner games use bounding boxes (rect.colliderect()
), which is fine for rectangles. But when your spaceship has curved wings and empty space? Rects lie.
Enter pygame.mask
. With this, you create a bitmask from your sprite, and check actual pixel overlap:
offset_x = obj2.x - obj1.x
offset_y = obj2.y - obj1.y
collision = obj1.mask.overlap(obj2.mask, (offset_x, offset_y)) != None
It’s a little mathy. But once you get it, it feels like you’ve unlocked true hit detection. No more “I didn’t touch that!” moments — just accurate, satisfying gameplay.
This was hands-down the most powerful thing I learned in this whole project.
💡 What I Actually Learned
- pygame is simple until you want it to do anything cool
- Tech With Tim teaches the foundation — your chaos makes it unique
- Proper asset management prevents 3 AM debugging sessions
- Visual feedback (like kamikaze warnings) makes gameplay feel responsive
- File I/O for high scores is easier than you think
- Sound mixing is hard but worth it when it works
This Is an OOP Goldmine
This game is basically one hell of a project if you want to learn object-oriented programming.
You’ve got:
-
Player
,Enemy
, andKamikazeEnemy
— each a subclass of a commonShip
base - Encapsulation of behavior (movement, shooting, health, drawing)
- Polymorphism in full effect (kamikaze ships override movement logic)
You’re not just writing loops — you’re designing systems. Every class has a job. You’re learning how to organize code that grows — and that’s the real skill behind game dev, web dev, or honestly any dev.
Building this gave me a genuine “ohhhh that’s what OOP is for” moment.
🛸 So Should You Make One Too?
YES.
Watch the tutorial.
Then make it yours.
Add the weird features that make sense to you.
Break things. Fix them. Break them again.
Then write about it.
Because apparently, that's what we do now.
Top comments (3)
Woah this looks legit epic! 🔥💯
I cant lie all these steps you took look kinda intimidating but that makes it all the more impressive. I definitely want to try making a game like this sometime. Is there any way I cant play this?
Also I'll definitely check put tech with Tim on YT, didn't know there was YouTube channels with tutorials that can teach you to make things like this.
Edit: pls excuse my ignorance, the link toy your Github game is literally in the beginning of your post. I'll wear my glasses when reading next time😅
yeah, just click on the github link , download the zip file and install the requirements .txt file and run the main.py , you can check out the readme file on my github
much appreciated comment
Crazy
Some comments may only be visible to logged-in visitors. Sign in to view all comments.