Glauron post-mortem

(Cross-posted to the Ludum Dare blog.)

This is Glauron, my Compo entry for Ludum Dare 33, themed You Are The Monster:

Glauron animation

This was my eighth time participating in Ludum Dare, and I feel it’s my best yet. I’m very happy with what I got done, and there was even time left on Sunday for a relaxed dinner and quiet evening with my girlfriend.

All my LD entries have been in JavaScript, because I want the barrier for players to be as low as possible. Further tools I use are TypeScript for rapid feedback, Inkscape or Gimp for graphics, and jfxr and/or Audacity for audio. I also have a simple Ruby server script that rebuilds assets when I refresh the page, although I didn’t use it this time.

One change was that I used CraftyJS for the first time. It’s a simple 2D game engine focused on simple and quick development. It probably saved me hours in figuring out rendering, collision detection and asset loading.

Gameplay

Having learned from previous projects, where I got stuck with a nice looking tech demo that was no fun, I decided to focus on gameplay first of all. Crafty makes it super easy to render coloured boxes, so that’s what I started with. This screenshot is from Saturday, around 6 PM:

Boxed Glauron

I’ve been reading the book Game Mechanics: Advanced Game Design lately, and it talks a lot about resources and feedback loops. The theory applies here nicely. Speed towards the ground is a “resource” in the sense that it allows you to breathe fire downwards and hit your enemies. However, having speed towards the ground drains another “resource”, namely altitude, and when that hits 0 it’s game over. So there is an interesting tradeoff here, and there are actually different strategies possible: swooping down from on high for easy targeting but risking a crash, or staying low to the ground, safer from crashing but making it a bit harder to hit anything. I think this tradeoff is a large part of what makes the game fun (although this is an after-the-fact rationalization, and I didn’t think about it in these terms while I was designing it).

Notice how the dragon already has a tail here. I added that because I initially didn’t have hilly terrain, so it was hard to gauge your horizonal velocity relative to the ground. At first, these were simply boxes that were delayed by 5, 10, 15, … frames relative to the “head”, which of course meant that the dragon would stretch when it moved faster.

The game turned out to be pretty monotonous with a flat ground, so I added the hills. It instantly looked much better and was much more interesting to play. The terrain consists of a chain of sections, each section having a slope computed from the previous section roughly like this:

slopeNext = 0.5 * slopePrevious + randomFloat(-30, 30)

So it tends to bend towards the horizontal, but shakes it up with 30° of randomness. There is a bit more to it (to avoid getting too high or too low) but that’s the basic idea.

This version also already had arrows getting stuck in the dragon’s body. It took me all of two minutes to code, because Crafty lets you reparent entities without having to do coordinate transformations, but I think it adds a nice touch. (Arrows falling back out when you pick up hearts came later; I had to do that to make the dragon not look like a porcupine after a few levels.)

Graphics

Being happy with the gameplay, I turned to graphics. First up was of course our protagonist, the dragon. I liked the look of the tail and wanted to keep that dynamic aspect. So I first drew a dragon, taking care to keep its body on a straight line, and annotating it with red circles where I wanted to make it flex:

Dragon sprite

Then I cut it into little pieces to form the individual sprites:

Dragon sprite

Each piece has two “pivot points” where it connects with the previous and next pieces. The red circles are made black here again, and duplicated to both of the connecting pieces to cover up any gaps between them. Physics and player input are actually applied only to the head; the tail simply follows, by moving the pieces into place from front to back whenever the head is moved. Simple enough to do, but very effective.

I wanted to make the archers move as well, tracking the dragon with their bow, so I did something similar by splitting off the upper part of their body:

Archer sprite

The terrain is also made out of a chain of sprites, which look like this:

Ground sprite

The “skirts” on the sides are again to cover up holes between the sprites where the terrain curves upwards. Below each of these sprites, a black rectangle is added that goes to the bottom of the screen. Working with silhouettes is so convenient!

The dragon’s fire was the last thing I tackled. Surprisingly, it almost looked better as orange boxes than as fire! The problem was that it consists of distinct “fireballs” rather than a continuous stream. I tried making it a continuous wall of fire simply by spawning more fireballs, but this made the game far too easy! So I stuck with the fireballs and made them look somewhat good. It may be hard to notice, but they “cool down” as they fly: first the white core fades, then the yellow, and finally the orange and red.

Sound

Since the look of the game is fairly realistic, I didn’t want to settle for generated sound effects, so I used Audacity to record some from the microphone. Most of the sound was just me grunting, yelling and making swooshing noises, with shifted pitch and/or tempo, and the occasional reverb thrown in. The sound of the hearts was made by tapping a crystal champagne glass.

I’m not super happy with the soundscape, but I have too little knowledge and experience to do a better job. It’s still better than total silence, I’m sure.

Playtesting and tweaking

At this point, the game looked and felt done, but there wasn’t much of a sense of progression. A friend suggested that I needed more feedback, so I added the score counter at the top, and implemented a multiplier (where kills in rapid succession get you ×2, ×3, … the points for each next kill).

For variety, I also added the silver arrows (which cannot be burned by the dragon’s fire), and churches which spawn three hearts.

But if you got good enough, you could still just play forever, so I added some levels of increasing difficulty. The level definitions look something like this:

{archer: 4, house: 4, village: 2},
{archer: 2, longbowman: 1, house: 3, village: 2, army: 2},
...

So on the first level, the game will spawn four archers, four houses and two villages (a group of houses defended by archers). These do spawn in a random order, and the exact number of buildings and archers in a cluster varies, so it still wasn’t designed by hand down to the last detail.

This made the game more interesting, and hard enough that most people can’t get past level 5 or 6 (out of 10). This was good, because the game didn’t have an ending. After Level 10, the level counter keeps increasing, but all you get is repeats of level 10 which consists solely of large armies and no way to replenish your health.

CraftyJS gripes

Overall, I found working with CraftyJS very productive. The entity system is particularly great, and exploits the dynamic nature of JavaScript to the maximum. The API is well documented and held few surprises. But there were a few issues that I ran into:

  • Audio support sucks, because it uses the <audio> element. There are plans to move to the WebAudio API, but it hasn’t happened yet. When I noticed that most of my sounds weren’t being played, I switched over to Howler.js which always works great.

  • You have no control over the rendering engine. Any post-processing effects are out of the question (but aren’t supported by canvas2d anyway). Additive blending (which would really have helped to spice up the fire effects) is not supported.

  • There is only a single camera (“viewport”). If you want your game world to scroll but your HUD to stay put, you have to move your HUD inside the game world.

  • Parent-child relationships between entities are pretty weird. Instead of a proper tree, there is a flat list of entities, and the child registers an event listener to update its own position when the parent moves. As a result, you cannot specify child coordinates within the parent’s reference frame; you have to convert to world coordinates first.

  • There is no mention of performance anywhere in the documentation. I imagine that Crafty’s model breaks down at a smaller number of entities than most other engines.

Conclusion

Thanks for reading this far! I hope you enjoyed this look behind the scenes even half as much as I enjoyed making it. Now go play and rate the game!