The Last Gardener
Ratings (show detail)
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...
Note for PulseAudio users
It's been brought to my attention that the game may crash when enough sounds are played at once, particularly the bullet sound, with "pyglet.media.MediaException: Too large". I actually don't have PulseAudio installed, so pyglet uses OpenAL instead on my computer, which worked fine during testing. Sorry for overlooking this :\
If this affects you, and you also have OpenAL installed, run the game with 'PYGLET_AUDIO=openal python run_game.py' instead. This will cause pyglet to use OpenAL, which seems to work fine. If you don't have OpenAL and you can't install it, you can turn off sound, although the game might be harder to play with sound off.
It is done!
I've just uploaded the first version of the game. Unlike my previous individual entries, this entry uses Pyglet 1.2.4, which is included in the download. If you haven't done so, remember to install or update AVBin before playing since Pyglet depends on it for playing sound and music. Let me know either on #pyweek or on this diary post if you're having problems getting the game to run.
Overall, we're pretty happy with how the game turned out. There are some parts of the game's code that could use some optimization, sadly, so there might be some slowdown, especially on later levels or on hard difficulty. Despite that, we hope you enjoy the game :)
Day 6 progress
I'll make this quick for obvious reasons :)
The graphics related to the (nonsensical yet amusing) story is finished, the levels are almost done, and finding music that fits the game is a bit tricky.
And now it's time to wrap things up...
Day 5 progress
I'm not sure how to write this diary entry, and I want to get back to programming, so I'll try doing a list this time. Here's a list of things we've done since last time:
- Main menu and title screen
- Another level or two
- A "next level" option upon beating the level
- Saving progress and settings
- The usual bug fixes
...and an hour or two after the last blog post, we decided on a name for the game, which you can see above this diary entry :)
We still have to add music, more levels, the rest of the story, and minor things, so I'll end this diary entry here.
Day 4 progress
So 96 hours have passed since the start of the week, and there's 72 more to go.
The main tasks that we got done are creating the level select screen, the first story scene (which is really just a slideshow :p), and the menu popup that appears when the player pauses the game, beats the level, or fails the level. There's also a lot more sounds added to the game. One major feature that still needs to be implemented is saving the user's progress. There's still more story scenes to put in, we need to add more levels, put in some music, the main menu doesn't really work, and a lot of other optional but nice-to-have things, but the finished product is slowly starting to take shape.
Also, it's a bit of a rude awakening to find out that the font being used doesn't have a single quote, or an equals sign, or a percent sign, or a lot of other characters, but we're just going to work around it, I guess.
Anyway, I figured that I should just put the screenshot directly in the diary entry, so here:
Day 3 progress
The problem that LegoBricker mentioned in his earlier diary entry (which is really part of this day :p) is that Pyglet didn't like the .wav files we were using for some reason. We just converted them to .ogg and it worked; we don't want to waste time figuring out exactly why :\
Anyway, a lot of the unfinished things I mentioned in my last diary entry are now implemented, and the gameplay is pretty much set in stone now. Everything else that surrounds the gameplay, such as the menus, putting the story in the game, defining the levels (the framework is there), and adding more sounds and music, has yet to be done, but we're making some steady progress.
I also added a screenshot :) Time to go back to work...
Legobricker's Day 2 Update
Day 2 progress
And before we know it, another 24 hours has passed.
Anyway, we managed to get the mowing mechanic working, and we've replaced the temporary player sprite with a better one. As usual, however, there are many features that aren't even close to being finished. There are enemies, but they don't move, and they don't have hitboxes. There are bullets, but there aren't really any patterns; instead, there's test code that fires hundreds of them randomly. And there's no way to really win or lose.
It's progress, though :) We also started to search for resources and edit them to fit our needs, so hopefully the game will look decent by the end of the week.
Right now, I need to get back to figuring out how levels will be defined and stored...
Day 1 progress
Alright, time to write our first diary entry :)
I'm doing a few major things differently this time around. This time, I'm working with a friend for this entry. His username is LegoBricker so say hi to him :) Also, we're using Pyglet, which neither of us used before. Inevitably, we've hit a few roadblocks due to not knowing much about the library, but we've managed to get the basics down. There isn't really any actual gameplay, however. So far, we managed to make a ball move around the screen with the arrow keys.
As for our idea, it's a bit silly :p Basically, it's a bullet hell game, but the player doesn't attack the enemies, which appeared due to some sort of catastrophe that we're probably going to figure out as we go along. Instead, the player must mow a percentage of the lawn to beat the level while avoiding bullets.
And that's it for now. We don't have any enemies yet, so we should get that done as well as a million other things...
Edit: forgot to add that our Git repository is here.