I have made some final touches to the project. First, I changed the font to something more legible, and also used sys.exit() instead of exit() to close the game, as the latter threw an error when the app was compiled. I wasn’t able to make a Windows build because I don’t have a Windows machine available. I’m kind of giving up on this project but I guess it’s ready to ship.
It took A LOT of work, but I finally got everything working!
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()
TitleScene) and the game over screen (GameOverScene).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.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!
I added the pipes! This was kind of hard to figure out, but I finally got it working!
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.exit() if they collide. This allows for the player to pass through the middle but then exit the game if it touches the pipes.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!
First devlog! It’s quite simple but it took kind of a while due to having to polish it alot.
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.
__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.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!
I have done a lot and decided to just bundle it into one last big devlog, but the work was well worth it!
0.005. This makes it so that asteroids spawn 50% faster by the time the player gets to score 100../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.
Quite a few improvements, though the codebase gets even more convoluted.
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.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.
Lots of improvements!
Game class. This makes it generally easier, as now I don’t have to wire so many variables between functions and all.It’s coming along great!
I made some pretty simple changes to the overall codebase but now it’s much cleaner.
settings.py file, I moved them into attributes of their own classes.__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..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.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.Next thing to do is to add lives and subtract them when the player gets hit with a meteorite.
I made some pretty drastic changes, but for the better.
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.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!
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).
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.
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.
This took somewhat longer than I would’ve expected, but first devlog!
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.
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).
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.
Board class (board.py), one for the themes (themes.py), and then the main.py file stores the main while loop.s key toggles the statusline.n key runs the simulation for one generation.c key clears the board (did I mention that before?).+ and - keys have been unbound. I felt they weren’t that necessary.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.
Quite a bit done here, but a lot of progress, and I think I’m almost ready to ship.
g key can be pressed to toggle the grid (forgot to show it in the video, unfortunately).t key can be pressed to change the theme.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.
This was fairly straightforward. I added a bunch of quality of life features for the simulation.
False as the default board.clock.tick() at the end of the main while loop.
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…
I got the rules working! This was probably the hardest part yet, honestly.
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.