I spent the last month doing post-compo enhancements to my Ludum Dare 24 entry, Karyote, and I have to say, I am actually becoming pleased with the way my game has come together. I’m surprised, really, because for the longest time I felt that the game just wasn’t very good.
Having it finished and feeling complete is an important goal for me — I have too many projects that I’ve worked on for a little bit and not completed. What I had delivered by the compo deadline was really a rough sketch with a lot of design issues, not to mention a few bugs. It was playable, but missing many features. Rectifying these issues makes me feel a lot better about my effort. I think that the most important lesson here is that a weak effort and even a weak idea can (sometimes) be fixed with a little effort.
Identifying “issues” and prioritizing them is a difficult skill. It takes a very fine eye for detail. Having a good design sense is critical. Knowing exactly what your program code does, how, and why, is also critical.
I wish I could articulate better how I look at games and pick apart elements of their design, decide what works and what doesn’t, and come up with guesses for what might work better. Being able to mentally model this stuff is not easy. Each change you make can have effects that are not easy to foresee.
It’s really best to come up with a list of things, figure out what the most important things are, and re-evaluate that list after each item is crossed off. The process is something like this:
And that’s a loop, so every time you build, go back and experience how things have changed as a result of what you just built. The changes brought by the latest build will change the experience, change your evaluation of what’s needed, and change your decisions about what to build next.
There were quite a few bugs in the compo version. I wisely chose to fix these before I looked at implementing missing features.
The Depopulation bug
The most noticeable bug was the spawning problem — after a while, no new enemies spawn and you end up all alone. Originally my design was to have a new enemy spawn somewhere in the nearby vicinity (not necessarily on screen) anytime an enemy gets destroyed — so there would always be a constant number of enemies in the game.
One thing I noticed thanks to the bug was that I liked the fact that the enemies would get sparse after a while. It felt more natural to have areas of high density and low density, and as well it would give the player a varying pace and rhythm, which would break up the monotony. It’s good when you can learn things about your design from experiencing a bug. Design is not all about having all the right answers in your head — it’s about learning from your experiences.
To get a little better insight into what was happening, I added a counter to the dashboard that kept track of the number of living enemies. I expected this number to remain constantly at 100, but wanted to confirm that. It was possible that the number could have actually dropped to zero, and I wanted to make sure that my respawning worked as expected.
To my surprise the number actually climbed gradually. At that point, I realized I actually had two bugs.
I believe the count increase may have been due to enemies on rare occasion experiencing two collisions at the same time, which would trigger two rebirths for one death of the original enemy. I didn’t worry about fixing this, as the only consequence would be a very, very slow memory leak over a timescale too large to matter. But a later design change ended up taking care of this for me.
Since the enemy count was (basically) constant, I realized that the problem wasn’t that the enemies failed to respawn. Rather, the enemies just got so spread out over time that it was very unlikely you could ever find them, and so you would fly around forever in a very sparsely populated field, the chances of encountering an enemy decreasing more the longer the game went on.
There’s no radar or direction finder to help you, which made it even more difficult, but I didn’t want to add those features to the game. I didn’t want the player to have to fly for minutes at a time through boring, empty space, trying to chase down distant enemies that they could see on the radar.
Instead, I had the enemy objects check for their proximity to the player, and self-destruct if their distance was greater than a small multiple of the screen width. Destruction would cause it to respawn in a region nearer to the player. This should have been easy to get working but for some reason, oddly, it didn’t seem to work. I probably had made some stupid logic error somewhere, but I came up with another idea, and rather than debug, gave up on this approach, and wrote a different check that destroyed all enemies if the nearest one to the player was too far away.
At first, I just used a large constant value for the “too far” distance, but it didn’t take long to realize that varying player speed made this value need to be adjustable. The first refinement was to dynamically calculate the “too far away” distance based on the player’s current speed, so that no matter how fast or slow the Karyote is, you’ll only have a few seconds of flight time before the die-off/mass respawn gets triggered. This would trigger all enemies to respawn at once, nearby the player. Best of all, it would create conditions that would give a varying density to the room, with clusters of enemies surrounding the player when they spawn, and sparse areas when enough get eaten/drift away/the player travels to an empty region.
This worked. But it still wasn’t quite right. During the mass respawn dozens of enemies would appear onscreen all at once, often too close to the Player. I didn’t like that, because I wanted at least the vast majority of spawning to take place offscreen so that it gave the impression that the enemies were always there, so it felt like you discovered a living colony of them, not like they were magically created just for you.
I didn’t mind the occasional 1-2 on-screen spawns, as it seemed to add to the mystery of where did they come from/how did they get here. But suddenly popping in a couple dozen gave the impression of teleportation, and felt too much like Geometry Wars, which is a great game, but I didn’t want the player to get the impression that I was ripping it off.
I really wanted to keep that sense of clustering and changing enemy density in the game. So I modified my code so that enemy death did not immediately result in a new birth. Instead, the game checked the total population and when it dipped below a certain number, it would respawn a random number between this respawn number and 100. That way, enemies would respawn when the enemy count dipped below the respawn threshold (which would result in the field becoming sparse), or if you travel too far away from all the enemies.
This created just the right kind of flow of enemies, with varying enemy densities, and I didn’t have to code any kind of flocking or clustering behavior to achieve it. Win.
The next most important thing for me was to fix the mutation math, the mathematical model that governs how your karyote changes each time it mutates.
I struggled with the math model quite a bit. I had only a vague idea of what I wanted, and a few goals which were contradictory. This wasn’t so much a bug as it was I just didn’t know what the game needed, and so I had to experiment a lot. I had some vague goals:
- The model should give rise to changes in the player that feel like mutation (as opposed to a generic progression of power-ups).
- Mutation should result in evolutionary advantage or disadvantage. (As an experiment, I wasn’t going for a traditional challenge curve. Rather I wanted to reinforce the Evolution theme by showing how mutations aren’t always beneficial, and evolution isn’t a linear path from inferior to superior.)
- Mutation should be somewhat random, but still influenced by the player’s choices during the game.
- The pace of mutation should feel “right” – not too fast/drastic, not too slow/subtle.
What I ended up with achieved those goals, but I still think it is flawed in that the result isn’t really as fun as I’d hoped.
- Deviating from the usual challenge curve makes the game very hit-or-miss.
- Beyond that, the game doesn’t really get any more challenging or less challenging. You just get faster (or slower) or more or less nimble, or have a longer or shorter health bar. The enemies do change with you as you mutate, which was supposed to impart a sense of “level progression” but these changes don’t really make them any more challenging.
- Even though what you do does affect mutation outcomes, there’s not enough influence to give you a enough of a feeling of control. While the randomness is constrained to acceptable, playable values, there’s still too much out of the player’s hands.
The basic idea for the model was simple: eating different types of foods gives you points for different attributes. The more points you get during the mutation cycle, the more your abilities could change when you mutate.
Getting the model to feel right took a huge amount of trial and error and testing different approaches. Over a week into the project, in fact, before I came up with something that I felt was right. The game had been playable up until that point, but I think that the effort to make the math just right was important. And even now, I am still wrestling with doubts over it. I’m at the point now where I’m satisfied with my experiments, in that I learned some things and did make improvements to the model over a series of iterations, but I’m still not completely satisfied with the resulting game, and I got to a decision point where I could say I learned from my experiment and that was good, and walk away from the project, or I could continue putting time into experimenting with the math model until I figured out something that made the game design more successful.
If I did want to put more time into the game, I’d probably yank out all the math one more time, and implement something that felt more like progression than like random mutation, and add more challenging enemies. As a game, simply going around chasing food and avoiding eating too much poison while randomly changing your abilities between values that might be fun or might kindof suck just doesn’t feel like a great game to me. But I am satisfied that I learned enough through my experiments that I grew as a developer as a result of working on this game.
Getting to Feature Complete
I had so many things that I didn’t implement by the Compo deadline. I felt like it was a feat to get enough built that it felt playable, and given the lack of productivity and creativity on my part during the weekend, it was. But I also felt very dissatisfied, and this compelled me to put more work on the game to make it closer to what I’d hoped to make before I walked away from it.
First, I never got to implement the “Enemies” in the game. Most of the objects in the game are really “Food”. They’re no threat to you, except the poisonous ones, which are easily avoided once you learn which ones they are.
The design of my food objects, I’m pretty satisfied with. They followed a very simple progression which feels sufficiently evolutionary: sitting stationary, jiggling, moving in a straight line, moving in a wavy, oscillating line, moving in a curved line, seeking the nearest object (and eating it).
I came up with the design by taking the simplest, easiest thing to implement (just sitting there) and adding one concept to its behavior, and then adding one concept to that, and so on. The starfish-like Homeys were the most sophisticated “Food” – they had evolved sufficiently to be able to eat other foods.
I gave the Foods a visual appearance which suggested their behavior, and their appearance/behavior suggested the type of mutation points they gave when you ate them. Overall, I felt this was quite successful. I spent the least amount of time possible on drawing their sprites. None of them are animated, and they’re all simple scribbles or geometric shapes. Most took less than 5 minutes to draw.
Post-compo, I added a few more Foods: zig-zag motion, alternating between spinning and straight-line motion.
The game I submitted for the compo was a stark black and white game. This aesthetic evokes a 1977-79 vintage black and white arcade game, especially considering how crude the sprites are. And some people liked it.
Truthfully, however, I had always intended for there to be color. I actually wanted there to be a dynamically generated color system that would convey the relative strengths and abilities of the player as well as the enemies. I just ran out of time and didn’t get to implement it. I had a tough time coming up with a color scheme.
I wanted it to be attractive as well as convey information. I could use hue, saturation, and value or I could use RGB. I could also use alpha channel transparency. I needed to employ color psychology to create associations, but what color connotes “speed” or “rotation”? What would be a good color for “food” and “poison”? I still don’t really know.
At some point, I got tired of trying to figure out how best to color my sprites, and moved on to creating particle systems. I wasn’t sure what I wanted here, either, so I just started playing around, trying random things until I started to get a sense of the capabilities of particles, then started looking for things that I liked or thought would be useful, and refined from there.
Thankfully there are a variety of very handy generator applications that make creating and testing particle systems very easy and fun. Eventually I got a few ideas. So there are bursts of particles when something gets eaten, and when you mutate, and when something spawns. I tried not to go overboard with it, because I don’t like when games overwhelm the player with visual candy that obscures the action and makes the game harder to play.
Unfortunately, Game Maker HTML5 does not currently have the capability of additive blending, which limited me. Blending allows for very attractive sprite systems. The Windows build supports it, but the particle effects in the HTML5 build are all single color, and not as impressive.
I wanted the sprites to be more than just eye candy — they needed to be to convey information to the player, and to draw the eye to points in the game where something important was going on.
I think I succeeded here reasonably well. This was the first game I’ve used particles in, so I learned quite a bit about the code used to generate them. There’s a lot of potential with particles, and I plan to use them often in my games.
One of the tricks I discovered is that when the player eats a food, the resulting particle burst works best when it originates from a position that is the average of the position of the predator and prey. I tried centering them on the predator first, then on the prey, and neither looked quite right. But averaging the two works to convey what’s happening, right at the point of collision.
The final feature
The true Enemies were going to be previous forms of the karyote, and the code to generate these enemies was only an idea by the compo deadline. The idea was, when you mutated, your previous-generation form became available to the game as an enemy AI, which would begin spawning and then you’d have things to compete with for food. I had confidence that what I wanted to do was within my capability, but I had not done it before, and it took some time to work out how to do what I wanted. It ended up being the very last thing I implemented.
It was actually easier than I thought it would be. I had to do several things:
- Create a data structure to store the progression of the enemies, so that every generation of karyote would remain available throughout the game.
- Create AI routines that would give the Enemies their behavior.
- Create a sprite generating system to give each generation of enemy a unique appearance.
The sprite generator was the hardest and most complicated thing to implement. The approach I took to create it was fun: I set up a demo room with an object that draws a static picture of the sprite using drawing functions, using parameters which vary according to parameters that I added controls so I could adjust them. I got it drawing the body correctly, then worked on getting it to draw the other parts.
Once I had it drawing everything correctly on the screen, I needed to draw the object to a Surface, rather than to the screen. This was easy. Also easy was creating the new sprite from the Surface. Once created, the sprite needed to be stored and addressed so that I could use it, which was also easy. I simply used an array to store the values of the sprite_index, max_life, speed, and turn of each generation of the Player. For the enemy, I simply created an Object with some AI in it, and assigned it a Generation that it came from, and this Generation corresponded with an array index where it obtained its attributes. At first I thought I would need to create an array of objects_indexes as well as sprite_indexes, but it turned out not to be necessary, and I just handled everything with a single Object, which I named Formerly.
The biggest disappointment I have with how the game turned out was with Formerly. The HTML5 build of the game has some framework bugs which prevent the sprites from being as good as they are on the Windows build. The collision mask is not sized properly, and so collisions are not working as intended, and the play experience suffers as a result. It’s a lot better to play the Windows version now, as a result.
I replaced the 3-second music loop that I had created with an original composition by my friend Ian Faleer. This music is almost too good for the game. I can’t take any credit for it, but it was interesting to work it into the game. To date, this is my first game with background music in it, and of course music is very important to games, and can make a huge difference in the way a game feels. Ian is a wonderful talent and great to work with, and we are planning to work together on more projects in the future.
Overall, I’m very happy with the progress I made as a developer and the new things I did with this game. Even if I’m not entirely happy with the game as a game, the experimental things that I tried taught me a great deal, and will be useful to me in my future projects.