devlog: made a space shooter, drew every sprite with pygame.draw
had an itch to build a little arcade shooter — dodge and shoot aliens, grab power-ups, level up, don’t die. gave myself one dumb rule first: no image files. no sprite sheets, no downloading pixel art. every ship, alien, laser and icon gets built out of circles/polygons at startup and cached. mostly so I didn’t have to deal with asset folders, but also just to see if it’d look decent. mostly does, once you cheat with alpha blending enough.
the ship is like six polygons wearing a trenchcoat
get_ship_img() is genuinely a pile of triangles and ellipses stacked up — two wing shapes, two SMALLER brighter polygons on top of those, a nose triangle, a cockpit ellipse, a white highlight blob. that second brighter polygon on the wings does basically all the work — first draft with flat single-color wings looked like clipart from 2004. one lighter layer on top and it suddenly reads as “metal panel” instead of “shape.” cheap trick, huge payoff.
aliens (get_alien_img) are the same idea, cycling through 3 base colors so a wave doesn’t look like clones (they are clones but shh). the eyes were an afterthought that became my favorite detail — two yellow circles with dark pupils, pulsed with sin(ticks * 0.006 + phase) at draw time so each alien blinks out of sync with the others. kills the “identical enemies marching in lockstep” feeling.
backgrounds took longer than they should have
didn’t want a static black screen with some stars, so each level has a theme — gradient colors, a couple nebula blobs, a planet in a corner. gradient’s just a line per row lerping color, baked once per level into bg_cache instead of redrawing every frame (felt dumb I didn’t do that from the start).
the nebula thing (add_nebula_blob) is my favorite bit for how little code it is — ~10 shrinking, increasingly transparent circles with BLEND_RGBA_ADD so overlaps brighten instead of turning muddy. expected to need noise textures, got a for loop instead.
explosions, and the cheapest screen shake ever
on death (spawn_explosion) 16 particles fly out with gravity, plus a separate expanding ring. particles alone felt weak, ring alone felt bloodless, both together actually sells it.
screen shake: render the whole frame to an offscreen surface, blit THAT onto the real window with a random offset for a few frames after a hit. no shaking individual sprites, no camera object. one extra surface and a randint call.
power-ups + difficulty as basically one line of math
four boosters — rapid fire, shield, 3-way spread, extra life — drift down on a timer. active ones live in a dict with a countdown, so stacking effects and drawing HUD bars was just one loop, no special-casing per power-up.
difficulty is deliberately dumb: every 150 points = next level, speeds up enemies, spawns more often, capped so it’s not unplayable by level 20. background palette also cycles on level-up, which sells “progress” more than the actual curve does. never underestimate just changing the sky color.
why it’s async even though it’s not a web app yet
main loop is async def main() with await asyncio.sleep(0) per frame instead of blocking while True. weird for desktop pygame, but means I can run it through pygbag later in an actual browser tab without freezing the page. easier to start async than retrofit it.
still on the list
enemies are all “same alien but faster” — want real patterns or a boss
zero sound, biggest thing making it feel unfinished
no persistent high score
shield + multi-shot together trivialize a level, needs tuning
Comments 0
No comments yet. Be the first!
Sign in to join the conversation.