You are browsing as a guest. Sign up (or log in) to start making projects!

nautrw

@nautrw

Joined June 1st, 2026

  • 33Devlogs
  • 5Projects
  • 3Ships
  • 45Votes
Open comments for this post

3h 28m 5s logged

It took A LOT of work, but I finally got everything working!

  • I added a title and game over page. It took white a while to figure out, but I ended up being able to do it with many changes. First I made a scene manager, which lets me have multiple different scenes as classes, allow scenes to change to other scenes, and generally have much cleaner code. I made a scene manager class that stores a current scene. It has a method go_to which changes that scene variable and runs certain scene methods (more on that later). I then made a Scene class with several methods: handle_events, update, render, on_enter, and on_exit. The on_enter method runs after the scene is switched in go_to, and the on_exit method before. I run the update, handle_events, and render methods in the main game loop from on the current scene of the manager:
self.manager.scene.handle_events(pg.event.get())
self.manager.scene.update(self.dt)
self.manager.scene.render()
  • This ultimately leads to me being able to have the title page (class TitleScene) and the game over screen (GameOverScene).
  • I also started using a GUI library. When I made the TitleScene, I realized that I wanted it to have a button to play the game. At first I wanted to implement my own button but then I gave up because it would’ve been WAY too much work for a project that would use one or two. Thus, I found pygame_gui library. It made UI creation relatively easier. The hardest part was figuring out the theming specifications, as it uses this custom JSON file format. At the end I was able to figure it out, but it was kind of weird and the documentation wasn’t much help. I was able to get a score display working by making a label and then updating the label when it changed.
  • Other changes: I made the background of the main game cornfowerblue to test the score display; pipes are drawn underneath the floor to make it look like they are coming out; I am using a public domain font from Open Game Art but I might change it.

The hard work is paying off!

0
0
5
Open comments for this post

1h 8m 4s logged

I added the pipes! This was kind of hard to figure out, but I finally got it working!

  • For the pipes, I made a new Pipe class as a subclass of pygame.sprite.Sprite. But this is not like an ordinary sprite class, as it’s actually 2 pipes LARPing as 1. I made a basic pipe sprite, and then I duplicate it in the Pipe.__init__() method. This allows me to have a top pipe that is flipped and then the bottom pipe is just the sprite. The actual image attribute is a surface that has both the top and bottom pipes blitted on to it. It took me quite a bit of time to figure out the transparency, though. This is because at first it was just showing up as a fully black column, even though the middle gap was supposed to be transparent. I fixed this by making sure the blitting was actually being done where it was supposed to, and then adding the SRCALPHA flag to the part where I make the image Surface.
  • For collisions, I used a feature called masking, which allows for pixel-perfect collisions based on the actual image of the sprite and not the rectangle. I made both the player and the full pipe image (both top and bottom) have a mask, and then as of right now I am running exit() if they collide. This allows for the player to pass through the middle but then exit the game if it touches the pipes.
  • I am not keeping score just yet, but I managed to get that done by giving the Pipe class a passed boolean attribute, and in the main event loop I am simply looping over the pipes in the pipes Group and checking if their x-coordinate value is behind the player, which means that the player must’ve gotten through the pipe, and later triggering the score to increase.

It’s coming along really well, and I’ve been learning a lot from this project!

0
0
9
Open comments for this post

2h 45m 50s logged

First devlog! It’s quite simple but it took kind of a while due to having to polish it alot.

  • I first made a simple bird sprite that will be getting remade later.
  • I made the Bird class, which is a subclass of pygame.sprite.Sprite. It has a flap_y_delta, which is how much the bird moves upwards when it flaps, and tracks a velocity variable that is either negative or positive and it is added to the y-coordinate of the sprite. In the update method I add the gravity, which is 1200 pixels per second, to the velocity. It also has a flap() method which sets the velocity to the flap_y_delta. The flap_y_delta is negative (-400) because Pygame’s y-axis goes downwards as it increases. Instead of handling user input for the bird in its update() method, I do it in the main event loop and just run bird.flap() because I need to only allow one press of space per keypress. This stops the user from holding the space bar to repeatedly go upwards, but it’s still spammable.
    • The bird also has rotation. This was quite simple. First, in the __init__() method, I have an original image, and then the image attribute is just a copy of the original. Then, in the draw() method, I rotate the original image and set it to the image variable, which is what gets drawn to the screen. The angle is calculated by min(90, self.velocity * 0.1), because the velocity will be very high when it is falling so it will default to 90 degrees when falling, and it is a negative value when the bird is going upwards.
  • I made a floor sprite. Later on I put a red line on it to make sure it is repeating correctly. It will be remade later on.
  • The floor was quite easy to make. Basically I make a Floor class as if it was a normal sprite, and the update method does 2 things: first, it’s going to move it leftwards by a set speed (200 px/s); and then it’s going to check if it has moved fully to the left of the view (self.rect.right <= 0), in which case it’s going to set its x-value to its width times 2 (self.rect.x += self.rect.width * 2), making it wrap to the right outside of the screen. In the main game class I make a sprite group of 2 floors, the second being offset by its own width to make it start to the right. This makes the floor scroll smoothly and infinitely.

It’s coming along really well!

0
0
7
Ship

I made Asteroid Shooter, a space invaders-like game where you shoot asteroids. I made it with Pygame (community edition), pixel art made manually in Aseprite by me, public domain sounds, and a lot of trial and error. It tracks the player's score, and it gets harder slowly over time. Overall, I learned more about Pygame (sounds, graphics, user input, OOP, etc.), pixel art, and game design.

  • 7 devlogs
  • 14h
  • 7.96x multiplier
  • 114 Stardust
Try project → See source code →
Open comments for this post

5h 6m 15s logged

I have done a lot and decided to just bundle it into one last big devlog, but the work was well worth it!

  • First, I finally redrew all of the sprites! This took the longest out of anything else, but it was very much worth the effort. I learned a lot more about pixel art using Aseprite, and I think the sprites came out very well. First, using references from Google, I remade the rocket. Then, I made 3 different sprites for asteroids and 3 for explosions. For the explosions, I learned about using several different colors/tones to create depth, and they look very good inside the game. Both explosions and asteroids will have their sprites chosen randomly when initalized. Then, I remade the bullet sprite. At first I thought I wanted it to be a missile, but it looked weird, so I just made it a fireball and it turned out very well. I used the same principles from the explosions for it. Finally, I drew a background. I made some preset stars and then pasted them throughout the whole thing.
  • I made some balancing adjustments. First, I made it so that the player can only shoot twice per second, as before it was 4 and quite overpowered. Then, I made it so that the minimum interval at which asteroids could spawn decreased over time as the score increased. I did this by subtracting the score multiplied by 0.005. This makes it so that asteroids spawn 50% faster by the time the player gets to score 100.
  • I then added sounds! I was able to get free public domain (CC0 licensed) sounds from this website called Open Game Art, and then used Audacity to make a loop and edit them to fit the game. I got one for the background music and made it loop infinitely and more-or-less perfectly. Then, I got one for shooting. Alongside the sounds I also added a system for keeping track of the sound’s volumes. This was kind of hard to figure out, but eventually I was able to make a dictionary and then a function that would toggle the volumes between 0 and 1. I had to do this because Pygame doesn’t have a feature to “group” audios or mute globally, and the background music plays constantly but the shooting sound plays ocasionally.
  • I reworked some of the “UIs”. They are still kind of lackluster, but I added a main menu that the player has to press the space key to enter the game, and shows the controls. Then, I made a game over screen and show the player’s final score. I also made the window have a caption and icon. The icon is just the first explosion sprite.
  • I compiled the games into executable files. PyInstaller requires using a special resources function to get paths. This was quite easy to change to, since I have my functions to load assets in one file. I had some troubles with the paths, still, and I ended up moving everything outside of the ./src directory last-minute. The hard part about this was that I switched to Linux a few days before making this project, so I didn’t have another machine to compile the Windows version of the release. At first I found a guide that I could do it through Wine, and then I had to install a Docker image of Ubuntu that had it all set up, but it ended up not working. I ended up just setting up a Windows 11 virtual machine using Gnome Boxes (much easier than having to set up QEMU). It took me a while but it should work. It runs as expected on the VM (Windows Defender notification will pop up but that’s a problem with PyInstaller).

I’m probably missing some stuff, as this was quite a while, but yeah. The game is finished.

0
0
11
Open comments for this post

1h 35m 32s logged

Quite a few improvements, though the codebase gets even more convoluted.

  • I added explosions. This is pretty simple, just adding a Explosion class, and tracking delta-time against a set lifespan. An explosion will last 1/10th of a second, which I think is good enough.
  • I then added a score display. A score tracked by the main game class. When a player shoots down an asteroid, 1 will be added to the score.
  • I added a main menu and pausing. Usually this would be done in a much nicer way, perhaps with an external library, but I decided to make a few simple screens and display them when variables are True. The player can pause with the Esc. key, and it will toggle a variable paused that is False by default. I also have the main menu, which is True by default and is only toggled at the beginning. Finally, I have one for the game over screen, which is False by default and only toggles when the player has no more lives left. When any of these are True, the sprites will not update, and the game will only listen for input.

Overall, the game is basically done and I just need to polish it. I will likely be redrawing the sprites next.

0
0
5
Open comments for this post

2h 35m 17s logged

Lots of improvements!

  • All movement is framerate-independent. I did this by tracking delta-time.I could multiply all the movement deltas by the amount of seconds between frames. This will make the game smoother for everyone despite computer specs.
  • I got rid of the global settings file. All speeds were moved into their respective classes, and the main game now has its own Game class. This makes it generally easier, as now I don’t have to wire so many variables between functions and all.
  • Collisions between the player and asteroid, as well as asteroid and ground are checked. When either of these happens, one of the player’s lives will be removed and the sprite will start blinking. This allows me to have a sort of I-frame where if it is blinking it can not take damage, making it somewhat easier for the player. The game will end when the player has no lives left.
  • Pausing has been implemented, mostly to allow me to record for now, lol.
  • I drew up a heart sprite, which will also be getting redesigned along with everything else.

It’s coming along great!

0
0
16
Open comments for this post

2h 13m 9s logged

I made some pretty simple changes to the overall codebase but now it’s much cleaner.

  • Instead of storing all globals like movement speed inside the single settings.py file, I moved them into attributes of their own classes.
  • Sprites are now loaded in the __init__ method of their classes. Previously, I was under the impression that this was not possible because you needed to run pygame.init() before you used the .convert() (and similar) methods after loading the image. However, I was putting it outside of the class, so it would always run before running pygame.init(). This overall makes the code much cleaner, as now I don’t have to load in the sprites in the main file. Also, it was getting quite challenging to deal with figuring out ways to pass sprites into functions like player.shoot() so that it would spawn in a Bullet correctly.
  • I am now using .convert_alpha() instead of .convert(), which allows the sprites to have transparency (as seen in picture 2). I had been saving all of the sprites as .png files, but they had a black background when overlapping with other sprites (as seen in picture 3). With this, it actually allows for transparency and looks much nicer.
  • I added meteors. This was actually quite easy, just another class like the Bullet class but falling downwards. Pygame has this really nice feature where I can check for all collisions between all sprites from any 2 groups like so. pygame.sprite.groupcollide() takes in 2 sprite groups (pygame.sprite.Group), 2 booleans representing whether to remove the sprite right after, and a callback function. This will be useful later for adding explosions and score. They also spawn at a constant interval regardless of FPS. I did that by using delta-time and adding a counter to get the amount of milliseconds since the last frame. A meteor will spawn every 250 milliseconds. I find that this is a playable amount for now.
  • Shooting now has a cooldown. I do this in the same way as the asteroid spawning. The player can shoot 4 times per second.

Next thing to do is to add lives and subtract them when the player gets hit with a meteorite.

0
0
10
Open comments for this post

1h 17m 15s logged

I made some pretty drastic changes, but for the better.

  • First, I added shooting. I drew up a quick bullet sprite (also getting changed in the future). The main thing with this was that I wanted to have the Player class have its own .shoot() method, but that required me to pass the sprite Group object, as well as the actual sprite image into the function. I searched for a “better” solution for a while but ultimately couldn’t find one. The shoot() method handles the placement, the creation of the Bullet object, and adding it into the Group.
  • I have an actual file hierarchy now! I took inspiration from DaFluffyPotato, though I am using an assets folder instead of data, because I won’t be storing anything other than sprites and maybe sounds. Also, I will store the .ase files for the sprites just in case, so that I can fully edit them in Aseprite if there are any layers. The one roadblock I came across here was that the paths to the assets were kind of confusing, but I figured it out quickly.

Overall, I think it’s coming along great!

0
0
4
Open comments for this post

53m 56s logged

Again it takes longer than I would’ve thought, but I implemented movement. To be honest, the movement part was actually the easiest thing and I spent most of the time fighting with ty (the LSP/type checker).

  • Instead of editing the coordinate attributes of the class in the main event loop, I took inspiration from some established Pygame projects and made the Player class have a custom update() method. Inside, I check for the currently pressed keys and then use an if-statement to eddit the coordinates there. This makes the whole thing much smoother, as with the former option it would be choppy and wouldn’t let you keep holding down the key. Also, when I add the other entities (asteroids), I can make it have its own update() method, and that can be called as well. I am using this cool feature called pygame.sprite.Group, which lets me have a set of sprites and update and draw them all simultaneously. The player can be moved with the left and right arrow keys or A and D. I made it so that if they are both pressed the player will not move at all.
    • The main issue with the type checker/LSP was that Pygame sets the type hint of the rect attribute of pygame.sprite.Sprite to pygame.Rect | pygame.FRect | None, which causes ty to throw an error because None has no attributes. First, I tried ignoring all errors of that type by setting them to "ignore" in the ty configuration, but then I figured out I could just set the rect attribute to pygame.Rect | pygame.FRect inside the entity class, and that fixed the issue.

It’s a little complex as of right now because of the sort of unique way Pygame projects are structured, but I think it’s coming along very well.

0
0
32
Open comments for this post

36m 25s logged

This took somewhat longer than I would’ve expected, but first devlog!

  • I first got a very basic window going, nothing much to say.
  • Then, for the player, I decided to make it its own class. This is because, from my research, games made in Pygame are programmed in OOP (Object Oriented Programming), where each thing is its own class. I also want to draw my own sprites and stuff. Luckily, I purchased Aseprite a while ago on Steam, so I quickly drew up a basic rocket (second image). This will change in the future, but for now it’s just a very basic placeholder.
0
0
9
Ship

I implemented Conway's Game of Life in pygame(-ce). This took me quite a while because it is my first time working in pygame. I added features like controlling the flow of the simulation (pausing, going forward one generation, unpausing, speed), and themes. There is a statusline showing the current generation, speed, etc. There is a window showing the keybinds. Both can be toggled. I learned about the basics of Pygame, Pyinstaller, UI/UX design, and general programming.

  • 9 devlogs
  • 9h
  • 11.70x multiplier
  • 105 Stardust
Try project → See source code →
Open comments for this post

48m 19s logged

Just added a new thing to allow shift+left clicking, because dragging right click wouldn’t work on my laptop. This kind of took a while to implement because Pygame is weird, but I got it and now it works on laptops too! I don’t know if this is specific to only mine, but it’s also just easier to play the game on a laptop now. Also, I figured out how to package it into an executable. Pyinstaller is a pain to work with, and it took me a while too, but I got it. At first it was 50 megabytes but I got it to ignore some of the libraries from Pygame so now it’s 15-25, which seems to be normal.

0
0
4
Open comments for this post

20m 6s logged

I think this is the final devlog! I added a (relatively) small message to show the keybinds. It’s on by default, for new users. I also made the text bigger and changed the statusline from “updates/sec” to “gens./sec.” (abbreviated).

0
0
4
Open comments for this post

1h 32m 8s logged

Ok… The scope gets even bigger. I did A LOT of work this time, in very small but meaningful changes. Tl;dr: I improved the code, made the project look nicer, changed some of the keybinds, and made it much better to use.

  • Various code improvements: The code is generally cleaner now. I took the time to go over the if-statements and such, and then remove unnecessary things. I also split it up into several different files. As of right now, I have one to store the main Board class (board.py), one for the themes (themes.py), and then the main.py file stores the main while loop.
  • Visual changes: I made some visual changes that I think make the project nicer.
    • Instead of the gray bar at the bottom, I put the status line at the top left corner and made it toggleable by a keybind. I think I might put a rectangle behind it to make it look cooler and distinguish it from the white cells in the default theme (at the moment if there are squares there you won’t be able to see the text.
    • I changed the title bar. This was fairly straightforward, only 2 lines of code. I made screenshots of the glider in all the different themes, so now it will pick a random one for the icon at startup.
  • Performance improvements: I wasn’t really sure what to name this category, though the project probably won’t be the most performant thing ever… On a slightly older laptop it took ~10% of the CPU (Ryzen 5 5500U) and 100MB ram. Anyways, the main thing here is that the FPS is now separate from the speed of the simulation. I had to do this because the dragging was very choppy and would skip a lot of cells when the FPS was low. To fix this, I started tracking the delta-time (the amount of time since the clock tick at the end of the main while loop last updated). I was then able to add it to a counter variable every time the while loop ran. By comparing that counter value to my set interval, I am able to control how many times per second the simulation is ran. Thus, I set the FPS to 120, which is pretty smooth and not as resource-intensive as 256 (small difference, but eh). By default, the simulation will run 8 times per second.
  • Keybind changes:
    • The s key toggles the statusline.
    • The n key runs the simulation for one generation.
    • The c key clears the board (did I mention that before?).
    • The + and - keys have been unbound. I felt they weren’t that necessary.
    • Keys 1-7 change the presets from 1 generation/second, and doubling until 64/s. I felt any more than that was unnecessary and it wouldn’t refresh anyways because the FPS is 120. Could let the user change it in the future.

I’ve been telling myself that I am close to finishing the project, but I keep adding new things. I’m sure I’ll find a new feature to add.

0
0
4
Open comments for this post

1h 8m 41s logged

Quite a bit done here, but a lot of progress, and I think I’m almost ready to ship.

  • I added a counter for the generation. It isn’t very useful, but still nice to have.
  • I found the idea of a menu to be unnecessary, so I made a little 10-pixel tall statusline that shows the running status, current generation, and FPS. Quite easy to make, I just added 10 to the window height and then wrote a function to put text on that strip.
  • I added some new keybinds:
    • The g key can be pressed to toggle the grid (forgot to show it in the video, unfortunately).
    • The t key can be pressed to change the theme.
  • I added themes! Because the Board.draw() method was already flexible, this was very straightforward. First, I got ChatGPT to generate a list of themes (nothing more). Then, I made a new variable to keep track of the current theme index. That variable gets increased when the t key is pressed, and then the keys of the theme are bassed to the Board.draw() method.

To conclude, I think I am very close to finally shipping this small project! I will continue to make some more small changes and improvements.

0
0
3
Open comments for this post

29m 8s logged

This was fairly straightforward. I added a bunch of quality of life features for the simulation.

  • Clearing the screen (c) fills the board with the same 2-dimensional array filled with False as the default board.
  • Speed up (+) and down (-) set the FPS up or down 1 and then it is passed into clock.tick() at the end of the main while loop.
    • Speed presets (1-9) set the FPS to the corresponding index of an array of settings. As of right now, it’s just a sequence of powers of 2 from 1 to 256, but I might change it to something more ergonomic. I like this one because I use one if statement to handle all 9 keys.
  • Pausing (space) as promised earlier. This just toggles a variable that is False by default, and then I am checking if it’s True later.

Next I am going to add a small menu that will display important information like the number of the generation, as well as the presets. I want it to be hideable so as to not block it, but I also want the board to be the whole screen, so it’s going to be a small window. Then, maybe, tapping into the flexibility of my custom draw() function and adding preset colorschemes?

P.S.: Here is a better recording that showcases the simulation… The last post didn’t do it very much justice…

0
0
1
Open comments for this post

2h 35m 52s logged

I got the rules working! This was probably the hardest part yet, honestly.

  • First, the function to count the live neighbors of a cell took an embarrassingly long amount of time. After a bunch of iterations, I settled on looping through a list of tuples representing offsets for the x and y “coordinates” of the cell. I had to check for the bounds, too, since there are cells on the edges and it will fail if I count those as neighbors too.
  • The function to actually run the simulation, or making a cell live or die based on the rules, was less hard. I implemented the rules fairly quickly from the Wikipedia page on the game, though the wording was kind of tricky. There was this bug early on where the cells would glitch out and not update correctly, and I was able to fix it by creating a copy of the grid and changing that instead, then “pushing” it to the screen all at once.

Overall, I think the project is turning out nicely. Next, I will implement a pause feature so that I can draw stuff and see how it plays out instead of hardcoding it for testing.

0
0
2
Open comments for this post

57m 47s logged

I implemented clicking and dragging! This was somewhat more complicated than I thought it would be, but not hard in retrospect.

The clicking itself was quite easy. It was just a matter of listening for the click event, and then I implemented a function to find the position of the cell under the mouse position and toggle it.

The dragging was a bit harder, though. When I first got it working (shown in the second video), it was choppy. The main issues that I saw were:

  • I can drag but it’s generally interrupted;
  • dragging just a little bit repeatedly toggles the cell;
  • and I can move my cursor off the window and, it will still toggle the cells but sort of wrapped around.

Interestingly, the last one only seemed to happen when I was dragging my cursor and then moved the mouse out of the window but only on the right side.

I ended up solving the issue by adding some checks and making dragging only make the cells alive. This resulted in the first video, which is much cleaner.

I like how this is turning out!

1
0
4
Open comments for this post

48m 53s logged

I changed it to use a main class for the whole board.

The class contains a 2-dimensional array of booleans. This lets me access a specific cell from a coordinate like pygame’s coordinates, where x goes right and y goes down, as shown in the picture attached.

I also changed the draw function to be much more versatile. First, I am now using the rectangle function, which will let me color specific cells much easier. Also, I have some future customizability options with the colors of the borders, as well as either state of the cell.

0
0
4
Loading more…

Followers

Loading…