Butterfly Destroyers Development OverviewWritten by flipcoder (github.com/flipcoder)
Team PythonCoders was an open team started by MysteryCoder, a 13y/o programmer. The team
was international: Our team members were from USA, France, Finland, and UAE.
I myself have been in several gamejams in the past, including GitHub game-off,
where my game was one of the featured winners.
Pyweek would be the shortest jam I'd done so far.
Going into this jam we started with our project management using Trello.
We used a Kanban-like layout for tasks. Our columns were:
Ideas, Queue, Doing, Fix, Test, and Done.
Trello Link: https://trello.com/b/WuR7izYB/butterfly-destroyers
One of the early strategies I suggested was to just start going and not worry too much about
waiting on our decision-making, since we were slower at this at the start,
it was very important to me to just get a flow going.
From my experience with jams, especially with more than a couple members,
being bottlenecked by "analysis paralysis" is a common problem. It's better
to do something and have to scrap or fix it than to discuss every detail before
you add it. In this strategy, the most active people on the team can make the initial decisions
as they arise, This sometimes causes code redundancy, since we may
find different ways to do the same thing, but its often worth the cost of this,
since allowing coders to use the methods they are comfortable with increases
their development speed.
Our team was light on the graphics end, so we initially thought of doing
something in curses, so I threw together a curses-like terminal in pygame (see game/base/terminal.py).
This would be come in handy later, considering our game's scripting would
make heavy use of character-by-character typing during the game.
We had some ideas for games. I pitched the idea of doing something like Top Gun for NES, where it would
seem 3D, but we would try not to overdue it, knowing pygame's limitations were
going to become a problem. I imagined most teams would approach the butterfly theme as something gentle or light-hearted.
We went completely the other direction. I thought doing something comedic where you play
as the military hunting down butterflies for no apparent reason, while playing
it off as serious was quite funny. We decided this would make a cool arcade-like game and went with it.
By the time we decided to move forward on the idea, we already had a basic game base.
I put together an Entity system as ddorn created a nice 2.5D/parallax Camera.
MysteryCoder implemented fog/distance-fading.
One of the core parts of the game's codebase is the signal/slot system, which was extended by both the timer system
(game/base/when.py) and the scene entity list itself. We implemented Scene
as a Signal containing entities as slots. This is the first time I've tried this,
but it had some benefits. The signal system was basically a safely-modifiable list,
where all changes to it during the list's iteration are queued until after
all iterations of it have stopped. This iteration+modification is also reentrant.
This meant all objects could modify the scene without iterator invalidation,
and we don't need to copy the entire scene list every time we iterate.
The modes of the game were implemented as a basic state machine.
The intro, game, intermission, and credits are separate modes that
contain their own update(dt), and render methods.
And finally, one of my favorite parts of the project: The scripting system.
We didn't want levels to be simply X,Y,Z object placements, we wanted
them to be scripts that were stepped through, and could pause/resume/restart
as needed. Multiple scripts could run at once and start/stop each other.
We used python's generators to script
both levels and entity behavior, allowing a single line of code to turn
on and off behaviors of things in the game at any time.
We allowed scripts to be run not just for levels, but for entities and game states as well.
A 3rd use of signals were here: Scripts could yield a condition for when to resume,
very similar to async promises. These were either timer events or fades.
I wrote a timer system called 'When', based on the signal system, that would
register timed events and fades and return a connection/slot that we could
yield from our scripts.
Scripts that needed user input would accumulate events that happened since last yield.
So we could script things like this:
yield script.sleep(5) # resume the coroutine after 5 game-seconds
yield lambda: not boss.alive # continue the script until the boss dies
# call faderfunc() with color values from black to white and resume script after
yield script.when.fade(2, (ncolor('black'), ncolor('white'), faderfunc, script.resume))
Look and Feel
Ddorn added ground tilting and parallax clouds. I added a noise-gradient to
give the ground and sky some depth.
We wanted each level to look different, so we varied sky and ground colors, and later
added weather effects and rocks on the ground.
We made the ship sprite stretch based on movement direction to give it a nice 3d effect
and Ddorn used his input system to smooth the movement of the ship. The player
handling felt really good.
Ddorn developed an AI system, allowing spawning of batches
of enemies in different moving configurations and shapes. In my opinion,
this is the most visually impressive parts of the project, and his
math work on this gave us lots of variety in the levels.
Ddorn's text narration during the levels was also great, and had exactly the
comedic tone I'd hoped for.
We did 3 different types of enemies and a boss. AI varied within the
initial butterfly type as well. We didn't get to fully use the Flyers and
ButtaBombers, and the ButtaBombers explosive behavior wasn't finished.
We didn't get everything we wanted to finished, but we were quite happy
with what we got considering the deadline.
With the scripting, we were able to start polishing the look of the game.
We took advantage of gradients, noise functions, weather effects, particles, etc.
I wanted the game to look solid from the start to give a good first impression.
All of the music was written quickly, probably within a half hour.
I wanted to spend my time on code, but the game needed a memorable soundtrack
to give it the first impression we needed. I wrote 3 songs,
and I was quite happy with it considering the fast-approaching deadline.
We had some concerns about frame rate near the end. We had a memory bug which was a bit of a scare, since at one point the game was filling up
most of my RAM and causing bad fps. We logged like crazy and couldn't find much. This issue only
sometimes happened on certain computers, which made it harder to find.
I'm pretty sure that a different bug I fixed had removed the worst of it right before
causing it to not appear for us during testing, which was super confusing. We added some last-minute optimizations and hoped that it ran well for others.
We added an fps stabilizer that would disable some of the
effects if the frame rate dipped too low. We fixed the slow intro and score screen
by time-limiting the amount of effects updates, and improved the noise generation using caching.
I think everyone felt it was a fun project (except for the scary last-minute debugging),
and I'm happy we were selected as a winner. I think everyone learned some new things during this.
I know I certainly did. We had some great competition,
but we gained an advantage due to the separation of the Team and Individual categories.
Thanks to my team for all the hard work, PyWeek for hosting this, and everyone who played our game. We've discussed
expanding the game moving forward. We've talked about moving the renderer to OpenGL and adding more levels. A few of the systems we created during this can definitely be reused in the future as well.
Project GitHub: https://github.com/PythonixCoders/PyWeek29
If you want to contact me, my discord is: flipcoder#8604. All of us are in the pyweek discord.
Hope to see you all next time!