The judging period is over, and I'm glad that people had fun with the game. With every PyWeek I participate in, there's always lessons to be learned and traps to avoid for next time.
As I said before, unlike my previous individual entries, this entry was a team entry. I asked my friend, whose username is LegoBricker, if he wanted to join, and he figured that he'll have enough time throughout the week to help out with the game. We both started the week by programming, but I ended up doing most of the gameplay logic and the level design, while LegoBricker primarily worked on the game's appearance by finding the majority of the assets, modifying them to fit the game's needs, and making sure that they showed up in the game. He also did the story sequences and created the images associated with them. I laughed the first time I saw the introduction and ending, and I'm glad that some of you found the plot amusing :)
If my next entry ends up being an individual entry, I should take a page from my friend's book and use public-domain images from Wikimedia Commons as the basis for some images if it's appropriate for the game. That was rather clever, and I never would have thought of doing that.
This entry was also different because it used pyglet, which neither of us were familiar with. Speaking of pyglet...
Pyglet, AVBin, and the future
...well, I did have a feeling that AVBin was going to cause problems for some people, sadly :( I don't know how much I can do about that, though. I'm not really fond of how someone has to go out of their way and download a separate installer just to get music and sound to work, but oh well.
The main reason we went with pyglet instead of what I knew, which was PyGame, is mainly because LegoBricker uses Mac OS X. There are a few problems related to the latest OS X version (El Capitan, at the time of this writing), such as this one that I'd rather not have him, nor anyone else, deal with.
Either way, I thought that pyglet was a good framework to work with. We had to learn a lot about it during the week, but the learning process wasn't too bad. Working with sounds and music with it is probably my least favorite part about it, I suppose, but everything else worked fine for the most part. Having a sprite-heavy game in OpenGL also made using pyglet worth it. I just wish that there was an easier way of drawing primitives since I'm so used to being able to easily draw rectangles in PyGame.
In the future, since SDL 1.2, which PyGame is based of, is old and is no longer really maintained anymore, I might look towards using a library or framework that uses SDL2. There are quite a few SDL2 bindings for Python out there already. It would be nice if there was something like an SDL2 version of PyGame Zero. Of course, I'll have to figure out how to distribute a Python game that uses SDL2 and the 'how to play' instructions associated with it, but that can wait.
Coming up with the idea
So 'the aftermath' was the theme that we didn't initially have any good ideas for. Our ideas for that theme were:
- A zombie-themed bullet hell game
- A farming sim
- Tower defense game where the player has to survive for a period of time
That third idea was actually based on the main idea we had for the theme 'the incantation'. The gameplay would involve the player defending some sort of magician's tower until a giant spell was ready to be unleashed. It would have been fun to make, but maybe we can save it for next time. Or maybe someone here can borrow that idea :p
We were unsatisifed with how uninspiring (and in the case of the third idea, forcefully adapted) those ideas were. Thus, when the theme was announced, we weren't happy at first :p I ended up programming some classes I would need later on, such as a Rect class and some sort of screen manager, but we didn't get around to coming up with the final idea until the next morning. At first, I proposed a combination of the first two ideas. That idea would be a game divided into two phases: daytime and nighttime. During the daytime, it would be a farming simulator where the player can send out people to salvage resources, find more people, and work on the farm. During the nighttime, the player would defend the farm from zombies or aliens or whatever. That would be when the bullet hell gameplay enters the picture.
However, that idea was basically two games, and we didn't have the time to make two games, so we were at a loss for a bit. Then, I remembered watching a YouTube video of a bizarre Touhou fangame in which the main character has to clean up the floor while avoiding bullet patterns. Since one definition of 'aftermath' is 'new grass growing after mowing or harvest,' I basically took inspiration from that gameplay video and made it about mowing lawns while avoiding aliens that have taken over Earth. Therefore, our game fits the theme in more than one way :)
What went right, and what could be better
Well, the game turned out to be fun, and that's the most important part to us :) Of course, I should elaborate.
As one of the judges noted, we liked the constant feedback that the game gave to the player. One of the most important aspects of that is the lawn mowing sound that the drone makes as it moves. It was LegoBricker's idea to implement the sound in that way, and when I pulled his commit from our Git repository and moved around the drone, I was really happy with how it turned out. It really felt that the player was in control of the drone. Seeing the grass get mowed was also satisfying, and before there were enemies and bullets in the game, I spent a few minutes just drawing random shapes in the grass :)
I was also satisfied with the difficulty curve, and it seems that not many people got stuck this time around. Since traditional bullet hell games tend to be very difficult for many people, even on easier settings, I decided early on that having difficulty levels would be close to mandatory. I also wanted to introduce new concepts over time. That's why level 3 is when enemies first change their patterns as the player makes progress, and I delayed the appearance of the big bullets until level 6. I'm curious to know what difficulty setting people played on and how far they got. I think the hard difficulty could have perhaps been left out entirely since, to be honest, I can't get past the third level on hard, but I wanted to have fun with the patterns :)
The only issue with the level design might be that it's probably not obvious to players how the boss battle works. I could have done a better job of communicating to the player that they should just be focused on surviving instead of trying to mow the lawn. Hopefully people figured it out eventually if they got that far.
As for what can be better, well, the code can always be better :) There are a few implementation details that sounded like good ideas in my head but were actually awful ideas in practice. One example is the giant dictionary that is in level_data.py. It sounded like a good idea at the time, and it probably is a good idea if the variables for each level were simple. However, as I added more levels, I kept running into syntax errors, particularly with tuples that contain only one item since the syntax is awkward for them. Even when I fixed the syntax errors, if I passed in the wrong number of arguments for a given pattern, the game would crash once that pattern became active.
Also, I really should have implemented object pooling for the Bullet class. I never implementing object pooling before, and it was the middle of the week before I realized I should have done that, so I didn't really want to break anything. It's something that might require practicing beforehand. The constant allocation of memory for new Bullet objects and the garbage collector having to clean up after the trash that resulted was probably detrimental to the game's performance.
In fact, performance was always an issue since bullet hell games unsurprisingly involve tons of moving bullets and thus a lot of collision detection and position updating. I learned how to use cProfile during the week, and I was able to optimize a few methods and make the game perform better. For example, my initial and horribly lazy implementation of the grass mowing logic checked all ~1000 grass rects at once, but I was able to reduce that number to around 25, if I recall correctly. Also, I noticed that assigning to Sprite.position is better than indvidually assigning to their x and y properties, since Sprite._update_position gets called in all three cases, which is where a ton of time is spent once there are hundreds of bullets on the screen. A lot of time is still spent just moving the bullets around, but it's better than it used to be.
Problems along the way
It's inevitable that participants in game jams will run into some problems at some point, and we hit a few snags along the way. I document them here so that we can try to avoid them next time.
Probably the silliest problem, at least from our perspective, was that the font was missing some commonly used characters. Next time, we should examine the font in something like FontForge first before using it. The font we used was missing some characters such as a single quote or a percent sign. This first came to my attention when I wanted to use a percent sign for the numbers next to the progress bar, but it showed up as a rectangle. I was confused at first, but soon I found out that the font didn't have a percent sign! It didn't have a single quote either, which made writing the story annoying since apostrophes would show up as rectangles instead. It was midway through the week, so it was a bit late to change the font :( The lesson here is to make sure that any font found online is reasonably complete before it's used.
Another problem came up during the last 24 hours when music was added to the game. Shortly after, I noticed that when a lot of bullets are being fired, the music was cutting in and out, which was very distracting. I had LegoBricker test the game on a bullet-heavy level to see if that problem occurred on his computer, and it did more than just occur. The game froze! Of course such a thing would happen on the last day. We tried a few things to solve the problem, but eventually I noticed that the bullet sound has quite a bit of silence at the end, so I asked LegoBricker to trim the file. This seemed to alleviate the problem, at least on our computers. Sadly, PulseAudio didn't like how we play too many bullet sounds at once, which caused me to write that previous diary entry. I thought that we didn't have to worry about the differences between the audio drivers that pyglet supports, but at least there's a workaround for that particular problem.
One final note: it was amusing to hear the music for the boss level in Tee's game as well :p
Well, I think that's all I have to say. This is rather long, but oh well :) LegoBricker might write his own post-mortem later.
Until next time...